package syntax

import (
	"bytes"
	"fmt"
	"math/big"
	"unicode"
	"unicode/utf8"

	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/hclsyntax"
	"github.com/zclconf/go-cty/cty"
)

var tokenStrings = map[hclsyntax.TokenType]string{
	hclsyntax.TokenOBrace: "{",
	hclsyntax.TokenCBrace: "}",
	hclsyntax.TokenOBrack: "[",
	hclsyntax.TokenCBrack: "]",
	hclsyntax.TokenOParen: "(",
	hclsyntax.TokenCParen: ")",
	hclsyntax.TokenOQuote: `"`,
	hclsyntax.TokenCQuote: `"`,

	hclsyntax.TokenStar:    "*",
	hclsyntax.TokenSlash:   "/",
	hclsyntax.TokenPlus:    "+",
	hclsyntax.TokenMinus:   "-",
	hclsyntax.TokenPercent: "%",

	hclsyntax.TokenEqual:         "=",
	hclsyntax.TokenEqualOp:       "==",
	hclsyntax.TokenNotEqual:      "!=",
	hclsyntax.TokenLessThan:      "<",
	hclsyntax.TokenLessThanEq:    "<=",
	hclsyntax.TokenGreaterThan:   ">",
	hclsyntax.TokenGreaterThanEq: ">=",

	hclsyntax.TokenAnd:  "&&",
	hclsyntax.TokenOr:   "||",
	hclsyntax.TokenBang: "!",

	hclsyntax.TokenDot:   ".",
	hclsyntax.TokenComma: ",",

	hclsyntax.TokenEllipsis: "...",
	hclsyntax.TokenFatArrow: "=>",

	hclsyntax.TokenQuestion: "?",
	hclsyntax.TokenColon:    ":",

	hclsyntax.TokenTemplateInterp:  "${",
	hclsyntax.TokenTemplateControl: "%{",
	hclsyntax.TokenTemplateSeqEnd:  "}",

	hclsyntax.TokenNewline: "\n",
}

// Trivia represents bytes in a source file that are not syntactically meaningful. This includes whitespace and
// comments.
type Trivia interface {
	// Range returns the range of the trivia in the source file.
	Range() hcl.Range
	// Bytes returns the raw bytes that comprise the trivia.
	Bytes() []byte

	isTrivia()
}

// TriviaList is a list of trivia.
type TriviaList []Trivia

func (trivia TriviaList) LeadingWhitespace() TriviaList {
	end := 0
	for i, t := range trivia {
		if _, ok := t.(Whitespace); !ok {
			break
		}
		end = i
	}
	if end == 0 {
		return nil
	}
	return append(TriviaList(nil), trivia[0:end]...)
}

func (trivia TriviaList) TrailingWhitespace() TriviaList {
	start := len(trivia)
	for i := len(trivia) - 1; i >= 0; i-- {
		if _, ok := trivia[i].(Whitespace); !ok {
			break
		}
		start = i
	}
	if start == len(trivia) {
		return nil
	}
	return append(TriviaList(nil), trivia[start:]...)
}

func (trivia TriviaList) CollapseWhitespace() TriviaList {
	result := make(TriviaList, 0, len(trivia))
	for _, t := range trivia {
		if ws, ok := t.(Whitespace); ok {
			if len(result) != 0 {
				ws.bytes = []byte{' '}
				result = append(result, ws)
			}
		} else {
			result = append(result, t)
		}
	}
	return result
}

func (trivia TriviaList) EndsOnNewLine() bool {
	for _, trivia := range trivia {
		b := trivia.Bytes()
		for len(b) > 0 {
			r, sz := utf8.DecodeLastRune(b)
			if r == '\n' {
				return true
			}
			if !unicode.IsSpace(r) {
				return false
			}
			b = b[:len(b)-sz]
		}
	}
	return false
}

func (trivia TriviaList) Index(sep string) (Trivia, int) {
	s := []byte(sep)
	for _, trivia := range trivia {
		if i := bytes.Index(trivia.Bytes(), s); i != -1 {
			return trivia, i
		}
	}
	return nil, -1
}

func (trivia TriviaList) Format(f fmt.State, c rune) {
	for _, trivia := range trivia {
		_, err := f.Write(trivia.Bytes())
		if err != nil {
			panic(err)
		}
	}
}

// Comment is a piece of trivia that represents a line or block comment in a source file.
type Comment struct {
	// Lines contains the lines of the comment without leading comment characters or trailing newlines.
	Lines []string

	rng   hcl.Range
	bytes []byte
}

// Range returns the range of the comment in the source file.
func (c Comment) Range() hcl.Range {
	return c.rng
}

// Bytes returns the raw bytes that comprise the comment.
func (c Comment) Bytes() []byte {
	return c.bytes
}

func (Comment) isTrivia() {}

// Whitespace is a piece of trivia that represents a sequence of whitespace characters in a source file.
type Whitespace struct {
	rng   hcl.Range
	bytes []byte
}

// NewWhitespace returns a new piece of whitespace trivia with the given contents.
func NewWhitespace(bytes ...byte) Whitespace {
	return Whitespace{bytes: bytes}
}

// Range returns the range of the whitespace in the source file.
func (w Whitespace) Range() hcl.Range {
	return w.rng
}

// Bytes returns the raw bytes that comprise the whitespace.
func (w Whitespace) Bytes() []byte {
	return w.bytes
}

func (Whitespace) isTrivia() {}

// TemplateDelimiter is a piece of trivia that represents a token that demarcates an interpolation or control sequence
// inside of a template.
type TemplateDelimiter struct {
	// Type is the type of the delimiter (e.g. hclsyntax.TokenTemplateInterp)
	Type hclsyntax.TokenType

	rng   hcl.Range
	bytes []byte
}

// NewTemplateDelimiter creates a new TemplateDelimiter value with the given delimiter type. If the token type is not a
// template delimiter, this function will panic.
func NewTemplateDelimiter(typ hclsyntax.TokenType) TemplateDelimiter {
	var s string
	switch typ {
	case hclsyntax.TokenTemplateInterp:
		s = "${"
	case hclsyntax.TokenTemplateControl:
		s = "%{"
	case hclsyntax.TokenTemplateSeqEnd:
		s = "}"
	default:
		panic(fmt.Errorf("%v is not a template delimiter", typ))
	}
	return TemplateDelimiter{
		Type:  typ,
		bytes: []byte(s),
	}
}

// Range returns the range of the delimiter in the source file.
func (t TemplateDelimiter) Range() hcl.Range {
	return t.rng
}

// Bytes returns the raw bytes that comprise the delimiter.
func (t TemplateDelimiter) Bytes() []byte {
	return t.bytes
}

func (TemplateDelimiter) isTrivia() {}

// Token represents an HCL2 syntax token with attached leading trivia.
type Token struct {
	Raw            hclsyntax.Token
	LeadingTrivia  TriviaList
	TrailingTrivia TriviaList
}

func (t Token) Format(f fmt.State, c rune) {
	if t.LeadingTrivia != nil {
		t.LeadingTrivia.Format(f, c)
	} else if f.Flag(' ') {
		if _, err := f.Write([]byte{' '}); err != nil {
			panic(err)
		}
	}
	bytes := t.Raw.Bytes
	if str, ok := tokenStrings[t.Raw.Type]; ok {
		bytes = []byte(str)
	}
	if _, err := f.Write(bytes); err != nil {
		panic(err)
	}
	t.TrailingTrivia.Format(f, c)
}

func (t Token) AllTrivia() TriviaList {
	result := make(TriviaList, len(t.LeadingTrivia)+len(t.TrailingTrivia))
	for i, trivia := range t.LeadingTrivia {
		result[i] = trivia
	}
	for i, trivia := range t.TrailingTrivia {
		result[len(t.LeadingTrivia)+i] = trivia
	}
	return result
}

// Range returns the total range covered by this token and any leading trivia.
func (t Token) Range() hcl.Range {
	start := t.Raw.Range.Start
	if len(t.LeadingTrivia) > 0 {
		start = t.LeadingTrivia[0].Range().Start
	}
	end := t.Raw.Range.End
	if len(t.TrailingTrivia) > 0 {
		end = t.TrailingTrivia[len(t.TrailingTrivia)-1].Range().End
	}
	return hcl.Range{Filename: t.Raw.Range.Filename, Start: start, End: end}
}

func (t Token) withIdent(s string) Token {
	if string(t.Raw.Bytes) == s {
		return t
	}
	t.Raw.Bytes = []byte(s)
	return t
}

func (t Token) withOperation(operation *hclsyntax.Operation) Token {
	typ := OperationTokenType(operation)
	if t.Raw.Type == typ {
		return t
	}
	t.Raw.Type = typ
	return t
}

func OperationTokenType(operation *hclsyntax.Operation) hclsyntax.TokenType {
	switch operation {
	case hclsyntax.OpAdd:
		return hclsyntax.TokenPlus
	case hclsyntax.OpDivide:
		return hclsyntax.TokenSlash
	case hclsyntax.OpEqual:
		return hclsyntax.TokenEqualOp
	case hclsyntax.OpGreaterThan:
		return hclsyntax.TokenGreaterThan
	case hclsyntax.OpGreaterThanOrEqual:
		return hclsyntax.TokenGreaterThanEq
	case hclsyntax.OpLessThan:
		return hclsyntax.TokenLessThan
	case hclsyntax.OpLessThanOrEqual:
		return hclsyntax.TokenLessThanEq
	case hclsyntax.OpLogicalAnd:
		return hclsyntax.TokenAnd
	case hclsyntax.OpLogicalNot:
		return hclsyntax.TokenBang
	case hclsyntax.OpLogicalOr:
		return hclsyntax.TokenOr
	case hclsyntax.OpModulo:
		return hclsyntax.TokenPercent
	case hclsyntax.OpMultiply:
		return hclsyntax.TokenStar
	case hclsyntax.OpNegate:
		return hclsyntax.TokenMinus
	case hclsyntax.OpNotEqual:
		return hclsyntax.TokenNotEqual
	case hclsyntax.OpSubtract:
		return hclsyntax.TokenMinus
	}
	return hclsyntax.TokenInvalid
}

func newRawToken(typ hclsyntax.TokenType, text ...string) hclsyntax.Token {
	bytes := []byte(tokenStrings[typ])
	if len(text) != 0 {
		bytes = []byte(text[0])
	}
	return hclsyntax.Token{
		Type:  typ,
		Bytes: bytes,
	}
}

// NodeTokens is a closed interface that is used to represent arbitrary *Tokens types in this package.
type NodeTokens interface {
	isNodeTokens()
}

// Parentheses records enclosing parenthesis tokens for expressions.
type Parentheses struct {
	Open  []Token
	Close []Token
}

func (parens Parentheses) Any() bool {
	return len(parens.Open) > 0
}

func (parens Parentheses) GetLeadingTrivia() TriviaList {
	if !parens.Any() {
		return nil
	}
	return parens.Open[0].LeadingTrivia
}

func (parens Parentheses) SetLeadingTrivia(trivia TriviaList) {
	if parens.Any() {
		parens.Open[0].LeadingTrivia = trivia
	}
}

func (parens Parentheses) GetTrailingTrivia() TriviaList {
	if !parens.Any() {
		return nil
	}
	return parens.Close[0].TrailingTrivia
}

func (parens Parentheses) SetTrailingTrivia(trivia TriviaList) {
	if parens.Any() {
		parens.Close[0].TrailingTrivia = trivia
	}
}

func (parens Parentheses) Format(f fmt.State, c rune) {
	switch c {
	case '(':
		for i := len(parens.Open) - 1; i >= 0; i-- {
			if _, err := fmt.Fprintf(f, "%v", parens.Open[i]); err != nil {
				panic(err)
			}
		}
	case ')':
		for _, p := range parens.Close {
			if _, err := fmt.Fprintf(f, "%v", p); err != nil {
				panic(err)
			}
		}
	default:
		if _, err := fmt.Fprintf(f, "%v%v", parens.Open, parens.Close); err != nil {
			panic(err)
		}
	}
}

func exprRange(filename string, parens Parentheses, start, end hcl.Pos) hcl.Range {
	if parens.Any() {
		start = parens.Open[len(parens.Open)-1].Range().Start
		end = parens.Close[len(parens.Close)-1].Range().End
	}
	return hcl.Range{
		Filename: filename,
		Start:    start,
		End:      end,
	}
}

// AttributeTokens records the tokens associated with an *hclsyntax.Attribute.
type AttributeTokens struct {
	Name   Token
	Equals Token
}

func NewAttributeTokens(name string) *AttributeTokens {
	var t *AttributeTokens
	return &AttributeTokens{
		Name:   t.GetName(name),
		Equals: t.GetEquals(),
	}
}

func (t *AttributeTokens) GetName(name string) Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenIdent, name)}
	}
	return t.Name.withIdent(name)
}

func (t *AttributeTokens) GetEquals() Token {
	if t == nil {
		return Token{
			Raw:           newRawToken(hclsyntax.TokenEqual),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		}
	}
	return t.Equals
}

func (*AttributeTokens) isNodeTokens() {}

// BinaryOpTokens records the tokens associated with an *hclsyntax.BinaryOpExpr.
type BinaryOpTokens struct {
	Parentheses Parentheses

	Operator Token
}

func NewBinaryOpTokens(operation *hclsyntax.Operation) *BinaryOpTokens {
	var t *BinaryOpTokens
	return &BinaryOpTokens{Operator: t.GetOperator(operation)}
}

func (t *BinaryOpTokens) GetParentheses() Parentheses {
	if t == nil {
		return Parentheses{}
	}
	return t.Parentheses
}

func (t *BinaryOpTokens) GetOperator(operation *hclsyntax.Operation) Token {
	if t == nil {
		return Token{
			Raw:           newRawToken(OperationTokenType(operation)),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		}
	}
	return t.Operator.withOperation(operation)
}

func (*BinaryOpTokens) isNodeTokens() {}

// BlockTokens records the tokens associated with an *hclsyntax.Block.
type BlockTokens struct {
	Type       Token
	Labels     []Token
	OpenBrace  Token
	CloseBrace Token
}

func NewBlockTokens(typ string, labels ...string) *BlockTokens {
	var t *BlockTokens
	return &BlockTokens{
		Type:       t.GetType(typ),
		Labels:     t.GetLabels(labels),
		OpenBrace:  t.GetOpenBrace(),
		CloseBrace: t.GetCloseBrace(),
	}
}

func (t *BlockTokens) GetType(typ string) Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenIdent, typ)}
	}
	return t.Type.withIdent(typ)
}

func (t *BlockTokens) GetLabels(labels []string) []Token {
	if t == nil {
		labelTokens := make([]Token, len(labels))
		for i, l := range labels {
			var raw hclsyntax.Token
			if hclsyntax.ValidIdentifier(l) {
				raw = newRawToken(hclsyntax.TokenIdent, l)
			} else {
				raw = newRawToken(hclsyntax.TokenQuotedLit, fmt.Sprintf("%q", l))
			}
			labelTokens[i] = Token{
				Raw:           raw,
				LeadingTrivia: TriviaList{NewWhitespace(' ')},
			}
		}
		return labelTokens
	}
	return t.Labels
}

func (t *BlockTokens) GetOpenBrace() Token {
	if t == nil {
		return Token{
			Raw:            newRawToken(hclsyntax.TokenOBrace),
			LeadingTrivia:  TriviaList{NewWhitespace(' ')},
			TrailingTrivia: TriviaList{NewWhitespace('\n')},
		}
	}
	return t.OpenBrace
}

func (t *BlockTokens) GetCloseBrace() Token {
	if t == nil {
		return Token{
			Raw:            newRawToken(hclsyntax.TokenCBrace),
			LeadingTrivia:  TriviaList{NewWhitespace('\n')},
			TrailingTrivia: TriviaList{NewWhitespace('\n')},
		}
	}
	return t.CloseBrace
}

func (*BlockTokens) isNodeTokens() {}

// BodyTokens records the tokens associated with an *hclsyntax.Body.
type BodyTokens struct {
	EndOfFile *Token
}

func (t *BodyTokens) GetEndOfFile() *Token {
	if t == nil {
		return nil
	}
	return t.EndOfFile
}

func (*BodyTokens) isNodeTokens() {}

// ConditionalTokens records the tokens associated with an *hclsyntax.ConditionalExpr of the form "a ? t : f".
type ConditionalTokens struct {
	Parentheses Parentheses

	QuestionMark Token
	Colon        Token
}

func NewConditionalTokens() *ConditionalTokens {
	return &ConditionalTokens{
		QuestionMark: Token{
			Raw:           newRawToken(hclsyntax.TokenQuestion),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		},
		Colon: Token{
			Raw:           newRawToken(hclsyntax.TokenColon),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		},
	}
}

func (*ConditionalTokens) isNodeTokens() {}

// TemplateConditionalTokens records the tokens associated with an *hclsyntax.ConditionalExpr inside a template
// expression.
type TemplateConditionalTokens struct {
	OpenIf     Token
	If         Token
	CloseIf    Token
	OpenElse   *Token
	Else       *Token
	CloseElse  *Token
	OpenEndif  Token
	Endif      Token
	CloseEndif Token
}

func NewTemplateConditionalTokens(hasElse bool) *TemplateConditionalTokens {
	var openElseT, elseT, closeElseT *Token
	if hasElse {
		openElseT = &Token{Raw: newRawToken(hclsyntax.TokenTemplateControl)}
		elseT = &Token{Raw: newRawToken(hclsyntax.TokenIdent, "else")}
		closeElseT = &Token{Raw: newRawToken(hclsyntax.TokenTemplateSeqEnd)}
	}
	return &TemplateConditionalTokens{
		OpenIf:     Token{Raw: newRawToken(hclsyntax.TokenTemplateControl)},
		If:         Token{Raw: newRawToken(hclsyntax.TokenIdent, "if")},
		CloseIf:    Token{Raw: newRawToken(hclsyntax.TokenTemplateSeqEnd)},
		OpenElse:   openElseT,
		Else:       elseT,
		CloseElse:  closeElseT,
		OpenEndif:  Token{Raw: newRawToken(hclsyntax.TokenTemplateControl)},
		Endif:      Token{Raw: newRawToken(hclsyntax.TokenIdent, "endif")},
		CloseEndif: Token{Raw: newRawToken(hclsyntax.TokenTemplateSeqEnd)},
	}
}

func (*TemplateConditionalTokens) isNodeTokens() {}

// ForTokens records the tokens associated with an *hclsyntax.ForExpr.
type ForTokens struct {
	Parentheses Parentheses

	Open  Token
	For   Token
	Key   *Token
	Comma *Token
	Value Token
	In    Token
	Colon Token
	Arrow *Token
	Group *Token
	If    *Token
	Close Token
}

func NewForTokens(keyVariable, valueVariable string, mapFor, group, conditional bool) *ForTokens {
	var keyT, commaT, arrowT, groupT, ifT *Token
	if keyVariable != "" {
		keyT = &Token{
			Raw:           newRawToken(hclsyntax.TokenIdent, keyVariable),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		}
		commaT = &Token{Raw: newRawToken(hclsyntax.TokenComma)}
	}
	if mapFor {
		arrowT = &Token{
			Raw:           newRawToken(hclsyntax.TokenFatArrow),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		}
	}
	if group {
		groupT = &Token{Raw: newRawToken(hclsyntax.TokenEllipsis)}
	}
	if conditional {
		ifT = &Token{
			Raw:           newRawToken(hclsyntax.TokenIdent, "if"),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		}
	}

	return &ForTokens{
		Open:  Token{Raw: newRawToken(hclsyntax.TokenOBrack)},
		For:   Token{Raw: newRawToken(hclsyntax.TokenIdent, "for")},
		Key:   keyT,
		Comma: commaT,
		Value: Token{
			Raw:           newRawToken(hclsyntax.TokenIdent, valueVariable),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		},
		In: Token{
			Raw:           newRawToken(hclsyntax.TokenIdent, "in"),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		},
		Colon: Token{Raw: newRawToken(hclsyntax.TokenColon)},
		Arrow: arrowT,
		Group: groupT,
		If:    ifT,
		Close: Token{Raw: newRawToken(hclsyntax.TokenCBrack)},
	}
}

func (*ForTokens) isNodeTokens() {}

// TemplateForTokens records the tokens associated with an *hclsyntax.ForExpr inside a template.
type TemplateForTokens struct {
	OpenFor     Token
	For         Token
	Key         *Token
	Comma       *Token
	Value       Token
	In          Token
	CloseFor    Token
	OpenEndfor  Token
	Endfor      Token
	CloseEndfor Token
}

func NewTemplateForTokens(keyVariable, valueVariable string) *TemplateForTokens {
	var keyT, commaT *Token
	if keyVariable != "" {
		keyT = &Token{
			Raw:           newRawToken(hclsyntax.TokenIdent, keyVariable),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		}
		commaT = &Token{Raw: newRawToken(hclsyntax.TokenComma)}
	}

	return &TemplateForTokens{
		OpenFor: Token{Raw: newRawToken(hclsyntax.TokenTemplateControl)},
		For:     Token{Raw: newRawToken(hclsyntax.TokenIdent, "for")},
		Key:     keyT,
		Comma:   commaT,
		Value: Token{
			Raw:           newRawToken(hclsyntax.TokenIdent, valueVariable),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		},
		In: Token{
			Raw:           newRawToken(hclsyntax.TokenIdent, "in"),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		},
		CloseFor:    Token{Raw: newRawToken(hclsyntax.TokenTemplateSeqEnd)},
		OpenEndfor:  Token{Raw: newRawToken(hclsyntax.TokenTemplateControl)},
		Endfor:      Token{Raw: newRawToken(hclsyntax.TokenIdent, "endfor")},
		CloseEndfor: Token{Raw: newRawToken(hclsyntax.TokenTemplateSeqEnd)},
	}
}

func (*TemplateForTokens) isNodeTokens() {}

// FunctionCallTokens records the tokens associated with an *hclsyntax.FunctionCallExpr.
type FunctionCallTokens struct {
	Parentheses Parentheses

	Name       Token
	OpenParen  Token
	Commas     []Token
	CloseParen Token
}

func NewFunctionCallTokens(name string, argCount int) *FunctionCallTokens {
	var t *FunctionCallTokens
	return &FunctionCallTokens{
		Name:       t.GetName(name),
		OpenParen:  t.GetOpenParen(),
		Commas:     t.GetCommas(argCount),
		CloseParen: t.GetCloseParen(),
	}
}

func (t *FunctionCallTokens) GetParentheses() Parentheses {
	if t == nil {
		return Parentheses{}
	}
	return t.Parentheses
}

func (t *FunctionCallTokens) GetName(name string) Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenIdent, name)}
	}
	return t.Name.withIdent(name)
}

func (t *FunctionCallTokens) GetOpenParen() Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenOParen)}
	}
	return t.OpenParen
}

func (t *FunctionCallTokens) GetCommas(argCount int) []Token {
	if t == nil {
		if argCount == 0 {
			return nil
		}

		commas := make([]Token, argCount-1)
		for i := 0; i < len(commas); i++ {
			commas[i] = Token{Raw: newRawToken(hclsyntax.TokenComma)}
		}
		return commas
	}
	return t.Commas
}

func (t *FunctionCallTokens) GetCloseParen() Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenCParen)}
	}
	return t.CloseParen
}

func (*FunctionCallTokens) isNodeTokens() {}

// IndexTokens records the tokens associated with an *hclsyntax.IndexExpr.
type IndexTokens struct {
	Parentheses Parentheses

	OpenBracket  Token
	CloseBracket Token
}

func NewIndexTokens() *IndexTokens {
	var t *IndexTokens
	return &IndexTokens{
		OpenBracket:  t.GetOpenBracket(),
		CloseBracket: t.GetCloseBracket(),
	}
}

func (t *IndexTokens) GetParentheses() Parentheses {
	if t == nil {
		return Parentheses{}
	}
	return t.Parentheses
}

func (t *IndexTokens) GetOpenBracket() Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenOBrack)}
	}
	return t.OpenBracket
}

func (t *IndexTokens) GetCloseBracket() Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenCBrack)}
	}
	return t.CloseBracket
}

func (*IndexTokens) isNodeTokens() {}

// LiteralValueTokens records the tokens associated with an *hclsyntax.LiteralValueExpr.
type LiteralValueTokens struct {
	Parentheses Parentheses

	Value []Token
}

func rawLiteralValueToken(value cty.Value) hclsyntax.Token {
	rawType, rawText := hclsyntax.TokenIdent, ""
	switch value.Type() {
	case cty.Bool:
		rawText = "false"
		if value.True() {
			rawText = "true"
		}
	case cty.Number:
		rawType = hclsyntax.TokenNumberLit

		bf := value.AsBigFloat()
		i, acc := bf.Int64()
		if acc == big.Exact {
			rawText = fmt.Sprintf("%v", i)
		} else {
			d, _ := bf.Float64()
			rawText = fmt.Sprintf("%g", d)
		}
	case cty.String:
		rawText = value.AsString()
	}
	return newRawToken(rawType, rawText)
}

func NewLiteralValueTokens(value cty.Value) *LiteralValueTokens {
	var t *LiteralValueTokens
	return &LiteralValueTokens{
		Value: t.GetValue(value),
	}
}

func (t *LiteralValueTokens) GetParentheses() Parentheses {
	if t == nil {
		return Parentheses{}
	}
	return t.Parentheses
}

func (t *LiteralValueTokens) GetValue(value cty.Value) []Token {
	if t == nil {
		return []Token{{Raw: rawLiteralValueToken(value)}}
	}
	return t.Value
}

func (*LiteralValueTokens) isNodeTokens() {}

// ObjectConsItemTokens records the tokens associated with an hclsyntax.ObjectConsItem.
type ObjectConsItemTokens struct {
	Equals Token
	Comma  *Token
}

func NewObjectConsItemTokens(last bool) ObjectConsItemTokens {
	var comma *Token
	if !last {
		comma = &Token{
			Raw:            newRawToken(hclsyntax.TokenComma),
			TrailingTrivia: TriviaList{NewWhitespace('\n')},
		}

	}
	return ObjectConsItemTokens{
		Equals: Token{
			Raw:           newRawToken(hclsyntax.TokenEqual),
			LeadingTrivia: TriviaList{NewWhitespace(' ')},
		},
		Comma: comma,
	}
}

// ObjectConsTokens records the tokens associated with an *hclsyntax.ObjectConsExpr.
type ObjectConsTokens struct {
	Parentheses Parentheses

	OpenBrace  Token
	Items      []ObjectConsItemTokens
	CloseBrace Token
}

func NewObjectConsTokens(itemCount int) *ObjectConsTokens {
	var t *ObjectConsTokens
	return &ObjectConsTokens{
		OpenBrace:  t.GetOpenBrace(itemCount),
		Items:      t.GetItems(itemCount),
		CloseBrace: t.GetCloseBrace(),
	}
}

func (t *ObjectConsTokens) GetParentheses() Parentheses {
	if t == nil {
		return Parentheses{}
	}
	return t.Parentheses
}

func (t *ObjectConsTokens) GetOpenBrace(itemCount int) Token {
	if t == nil {
		var openBraceTrailingTrivia TriviaList
		if itemCount > 0 {
			openBraceTrailingTrivia = TriviaList{NewWhitespace('\n')}
		}
		return Token{
			Raw:            newRawToken(hclsyntax.TokenOBrace),
			TrailingTrivia: openBraceTrailingTrivia,
		}
	}
	return t.OpenBrace
}

func (t *ObjectConsTokens) GetItems(itemCount int) []ObjectConsItemTokens {
	if t == nil {
		items := make([]ObjectConsItemTokens, itemCount)
		for i := 0; i < len(items); i++ {
			items[i] = NewObjectConsItemTokens(i == len(items)-1)
		}
		return items
	}
	return t.Items
}

func (t *ObjectConsTokens) GetCloseBrace() Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenCBrace)}
	}
	return t.CloseBrace
}

func (*ObjectConsTokens) isNodeTokens() {}

// TraverserTokens is a closed interface implemented by DotTraverserTokens and BracketTraverserTokens
type TraverserTokens interface {
	Range() hcl.Range

	isTraverserTokens()
}

func NewTraverserTokens(traverser hcl.Traverser) TraverserTokens {
	switch traverser := traverser.(type) {
	case hcl.TraverseAttr:
		return NewDotTraverserTokens(traverser.Name)
	case hcl.TraverseIndex:
		return NewBracketTraverserTokens(string(rawLiteralValueToken(traverser.Key).Bytes))
	default:
		return nil
	}
}

// DotTraverserTokens records the tokens associated with dotted traverser (i.e. '.' <attr>).
type DotTraverserTokens struct {
	Parentheses Parentheses

	Dot   Token
	Index Token
}

func NewDotTraverserTokens(index string) *DotTraverserTokens {
	indexType := hclsyntax.TokenIdent
	_, err := cty.ParseNumberVal(index)
	if err == nil {
		indexType = hclsyntax.TokenNumberLit
	}
	return &DotTraverserTokens{
		Dot:   Token{Raw: newRawToken(hclsyntax.TokenDot)},
		Index: Token{Raw: newRawToken(indexType, index)},
	}
}

func (t *DotTraverserTokens) Range() hcl.Range {
	filename := t.Dot.Range().Filename
	return exprRange(filename, t.Parentheses, t.Dot.Range().Start, t.Index.Range().End)
}

func (*DotTraverserTokens) isTraverserTokens() {}

// BracketTraverserTokens records the tokens associated with a bracketed traverser (i.e. '[' <index> ']').
type BracketTraverserTokens struct {
	Parentheses Parentheses

	OpenBracket  Token
	Index        Token
	CloseBracket Token
}

func NewBracketTraverserTokens(index string) *BracketTraverserTokens {
	indexType := hclsyntax.TokenIdent
	_, err := cty.ParseNumberVal(index)
	if err == nil {
		indexType = hclsyntax.TokenNumberLit
	}
	return &BracketTraverserTokens{
		OpenBracket:  Token{Raw: newRawToken(hclsyntax.TokenOBrack)},
		Index:        Token{Raw: newRawToken(indexType, index)},
		CloseBracket: Token{Raw: newRawToken(hclsyntax.TokenCBrack)},
	}
}

func (t *BracketTraverserTokens) Range() hcl.Range {
	filename := t.OpenBracket.Range().Filename
	return exprRange(filename, t.Parentheses, t.OpenBracket.Range().Start, t.CloseBracket.Range().End)
}

func (*BracketTraverserTokens) isTraverserTokens() {}

func newRelativeTraversalTokens(traversal hcl.Traversal) []TraverserTokens {
	result := make([]TraverserTokens, len(traversal))
	for i, t := range traversal {
		result[i] = NewTraverserTokens(t)
	}
	return result
}

// RelativeTraversalTokens records the tokens associated with an *hclsyntax.RelativeTraversalExpr.
type RelativeTraversalTokens struct {
	Parentheses Parentheses

	Traversal []TraverserTokens
}

func NewRelativeTraversalTokens(traversal hcl.Traversal) *RelativeTraversalTokens {
	return &RelativeTraversalTokens{
		Traversal: newRelativeTraversalTokens(traversal),
	}
}

func (t *RelativeTraversalTokens) GetParentheses() Parentheses {
	if t == nil {
		return Parentheses{}
	}
	return t.Parentheses
}

func (t *RelativeTraversalTokens) GetTraversal(traversal hcl.Traversal) []TraverserTokens {
	if t == nil {
		return newRelativeTraversalTokens(traversal)
	}
	return t.Traversal
}

func (*RelativeTraversalTokens) isNodeTokens() {}

// ScopeTraversalTokens records the tokens associated with an *hclsyntax.ScopeTraversalExpr.
type ScopeTraversalTokens struct {
	Parentheses Parentheses

	Root      Token
	Traversal []TraverserTokens
}

func NewScopeTraversalTokens(traversal hcl.Traversal) *ScopeTraversalTokens {
	var t *ScopeTraversalTokens
	return &ScopeTraversalTokens{
		Root:      t.GetRoot(traversal),
		Traversal: t.GetTraversal(traversal),
	}
}

func (t *ScopeTraversalTokens) GetParentheses() Parentheses {
	if t == nil {
		return Parentheses{}
	}
	return t.Parentheses
}

func (t *ScopeTraversalTokens) GetRoot(traversal hcl.Traversal) Token {
	if t == nil {
		rootName := traversal[0].(hcl.TraverseRoot).Name
		return Token{Raw: newRawToken(hclsyntax.TokenIdent, rootName)}
	}
	return t.Root
}

func (t *ScopeTraversalTokens) GetTraversal(traversal hcl.Traversal) []TraverserTokens {
	if t == nil {
		return newRelativeTraversalTokens(traversal[1:])
	}
	return t.Traversal
}

func (*ScopeTraversalTokens) isNodeTokens() {}

// SplatTokens records the tokens associated with an *hclsyntax.SplatExpr.
type SplatTokens struct {
	Parentheses Parentheses

	Open  Token
	Star  Token
	Close *Token
}

func NewSplatTokens(dotted bool) *SplatTokens {
	openType := hclsyntax.TokenDot
	var closeT *Token
	if !dotted {
		openType = hclsyntax.TokenOBrack
		closeT = &Token{Raw: newRawToken(hclsyntax.TokenCBrack)}
	}
	return &SplatTokens{
		Open:  Token{Raw: newRawToken(openType)},
		Star:  Token{Raw: newRawToken(hclsyntax.TokenStar)},
		Close: closeT,
	}
}

func (t *SplatTokens) GetParentheses() Parentheses {
	if t == nil {
		return Parentheses{}
	}
	return t.Parentheses
}

func (t *SplatTokens) GetOpen() Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenOBrack)}
	}
	return t.Open
}

func (t *SplatTokens) GetStar() Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenStar)}
	}
	return t.Star
}

func (t *SplatTokens) GetClose() *Token {
	if t == nil {
		return &Token{Raw: newRawToken(hclsyntax.TokenCBrack)}
	}
	return t.Close
}

func (*SplatTokens) isNodeTokens() {}

// TemplateTokens records the tokens associated with an *hclsyntax.TemplateExpr.
type TemplateTokens struct {
	Parentheses Parentheses

	Open  Token
	Close Token
}

func NewTemplateTokens() *TemplateTokens {
	var t *TemplateTokens
	return &TemplateTokens{
		Open:  t.GetOpen(),
		Close: t.GetClose(),
	}
}

func (t *TemplateTokens) GetParentheses() Parentheses {
	if t == nil {
		return Parentheses{}
	}
	return t.Parentheses
}

func (t *TemplateTokens) GetOpen() Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenOQuote)}
	}
	return t.Open
}

func (t *TemplateTokens) GetClose() Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenCQuote)}
	}
	return t.Close
}

func (*TemplateTokens) isNodeTokens() {}

// TupleConsTokens records the tokens associated with an *hclsyntax.TupleConsExpr.
type TupleConsTokens struct {
	Parentheses Parentheses

	OpenBracket  Token
	Commas       []Token
	CloseBracket Token
}

func NewTupleConsTokens(elementCount int) *TupleConsTokens {
	var t *TupleConsTokens
	return &TupleConsTokens{
		OpenBracket:  t.GetOpenBracket(),
		Commas:       t.GetCommas(elementCount),
		CloseBracket: t.GetCloseBracket(),
	}
}

func (t *TupleConsTokens) GetParentheses() Parentheses {
	if t == nil {
		return Parentheses{}
	}
	return t.Parentheses
}

func (t *TupleConsTokens) GetOpenBracket() Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenOBrack)}
	}
	return t.OpenBracket
}

func (t *TupleConsTokens) GetCommas(elementCount int) []Token {
	if t == nil {
		if elementCount == 0 {
			return nil
		}

		commas := make([]Token, elementCount-1)
		for i := 0; i < len(commas); i++ {
			commas[i] = Token{Raw: newRawToken(hclsyntax.TokenComma)}
		}
		return commas
	}
	return t.Commas
}

func (t *TupleConsTokens) GetCloseBracket() Token {
	if t == nil {
		return Token{Raw: newRawToken(hclsyntax.TokenCBrack)}
	}
	return t.CloseBracket
}

func (*TupleConsTokens) isNodeTokens() {}

// UnaryOpTokens records the tokens associated with an *hclsyntax.UnaryOpExpr.
type UnaryOpTokens struct {
	Parentheses Parentheses

	Operator Token
}

func NewUnaryOpTokens(operation *hclsyntax.Operation) *UnaryOpTokens {
	var t *UnaryOpTokens
	return &UnaryOpTokens{
		Operator: t.GetOperator(operation),
	}
}

func (t *UnaryOpTokens) GetParentheses() Parentheses {
	if t == nil {
		return Parentheses{}
	}
	return t.Parentheses
}

func (t *UnaryOpTokens) GetOperator(operation *hclsyntax.Operation) Token {
	if t == nil {
		return Token{Raw: newRawToken(OperationTokenType(operation))}
	}
	return t.Operator
}

func (*UnaryOpTokens) isNodeTokens() {}
