package css_ast

import (
	"github.com/evanw/esbuild/internal/ast"
	"github.com/evanw/esbuild/internal/css_lexer"
	"github.com/evanw/esbuild/internal/logger"
)

// CSS syntax comes in two layers: a minimal syntax that generally accepts
// anything that looks vaguely like CSS, and a large set of built-in rules
// (the things browsers actually interpret). That way CSS parsers can read
// unknown rules and skip over them without having to stop due to errors.
//
// This AST format is mostly just the minimal syntax. It parses unknown rules
// into a tree with enough information that it can write them back out again.
// There are some additional layers of syntax including selectors and @-rules
// which allow for better pretty-printing and minification.
//
// Most of the AST just references ranges of the original file by keeping the
// original "Token" values around from the lexer. This is a memory-efficient
// representation that helps provide good parsing and printing performance.

type AST struct {
	ImportRecords []ast.ImportRecord
	Rules         []R
}

// We create a lot of tokens, so make sure this layout is memory-efficient.
// The layout here isn't optimal because it biases for convenience (e.g.
// "string" could be shorter) but at least the ordering of fields was
// deliberately chosen to minimize size.
type Token struct {
	// This is the raw contents of the token most of the time. However, it
	// contains the decoded string contents for "TString" tokens.
	Text string // 16 bytes

	// Contains the child tokens for component values that are simple blocks.
	// These are either "(", "{", "[", or function tokens. The closing token is
	// implicit and is not stored.
	Children *[]Token // 8 bytes

	// URL tokens have an associated import record at the top-level of the AST.
	// This index points to that import record.
	ImportRecordIndex uint32 // 4 bytes

	// The division between the number and the unit for "TDimension" tokens.
	UnitOffset uint16 // 2 bytes

	// This will never be "TWhitespace" because whitespace isn't stored as a
	// token directly. Instead it is stored in "HasWhitespaceAfter" on the
	// previous token. This is to make it easier to pattern-match against
	// tokens when handling CSS rules, since whitespace almost always doesn't
	// matter. That way you can pattern match against e.g. "rgb(r, g, b)" and
	// not have to handle all possible combinations of embedded whitespace
	// tokens.
	//
	// There is one exception to this: when in verbatim whitespace mode and
	// the token list is non-empty and is only whitespace tokens. In that case
	// a single whitespace token is emitted. This is because otherwise there
	// would be no tokens to attach the whitespace before/after flags to.
	Kind css_lexer.T // 1 byte

	// These flags indicate the presence of a "TWhitespace" token before or after
	// this token. There should be whitespace printed between two tokens if either
	// token indicates that there should be whitespace. Note that whitespace may
	// be altered by processing in certain situations (e.g. minification).
	Whitespace WhitespaceFlags // 1 byte
}

type WhitespaceFlags uint8

const (
	WhitespaceBefore WhitespaceFlags = 1 << iota
	WhitespaceAfter
)

func (t Token) PercentValue() string {
	return t.Text[:len(t.Text)-1]
}

func (t Token) DimensionValue() string {
	return t.Text[:t.UnitOffset]
}

func (t Token) DimensionUnit() string {
	return t.Text[t.UnitOffset:]
}

// This interface is never called. Its purpose is to encode a variant type in
// Go's type system.
type R interface {
	isRule()
}

type RAtCharset struct {
	Encoding string
}

type RAtImport struct {
	ImportRecordIndex uint32
	ImportConditions  []Token
}

type RAtKeyframes struct {
	AtToken string
	Name    string
	Blocks  []KeyframeBlock
}

type KeyframeBlock struct {
	Selectors []string
	Rules     []R
}

type RKnownAt struct {
	AtToken string
	Prelude []Token
	Rules   []R
}

type RUnknownAt struct {
	AtToken string
	Prelude []Token
	Block   []Token
}

type RSelector struct {
	Selectors []ComplexSelector
	Rules     []R
}

type RQualified struct {
	Prelude []Token
	Rules   []R
}

type RDeclaration struct {
	KeyText   string
	Value     []Token
	KeyRange  logger.Range
	Key       D // Compare using this instead of "Key" for speed
	Important bool
}

type RBadDeclaration struct {
	Tokens []Token
}

func (*RAtCharset) isRule()      {}
func (*RAtImport) isRule()       {}
func (*RAtKeyframes) isRule()    {}
func (*RKnownAt) isRule()        {}
func (*RUnknownAt) isRule()      {}
func (*RSelector) isRule()       {}
func (*RQualified) isRule()      {}
func (*RDeclaration) isRule()    {}
func (*RBadDeclaration) isRule() {}

type ComplexSelector struct {
	Selectors []CompoundSelector
}

type CompoundSelector struct {
	HasNestPrefix        bool   // "&"
	Combinator           string // Optional, may be ""
	TypeSelector         *NamespacedName
	SubclassSelectors    []SS
	PseudoClassSelectors []SSPseudoClass // If present, these follow a ":" character
}

type NameToken struct {
	Kind css_lexer.T
	Text string
}

type NamespacedName struct {
	// If present, this is an identifier or "*" and is followed by a "|" character
	NamespacePrefix *NameToken

	// This is an identifier or "*" or "&"
	Name NameToken
}

// This interface is never called. Its purpose is to encode a variant type in
// Go's type system.
type SS interface {
	isSubclassSelector()
}

type SSHash struct {
	Name string
}

type SSClass struct {
	Name string
}

type SSAttribute struct {
	NamespacedName  NamespacedName
	MatcherOp       string
	MatcherValue    string
	MatcherModifier byte
}

type SSPseudoClass struct {
	Name string
	Args []Token
}

func (*SSHash) isSubclassSelector()        {}
func (*SSClass) isSubclassSelector()       {}
func (*SSAttribute) isSubclassSelector()   {}
func (*SSPseudoClass) isSubclassSelector() {}
