// Copyright 2018 The CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package ast declares the types used to represent syntax trees for CUE
// packages.
package ast

import (
	"fmt"
	"iter"
	"strings"

	"cuelang.org/go/cue/literal"
	"cuelang.org/go/cue/token"
)

// ----------------------------------------------------------------------------
// Interfaces
//
// There are three main classes of nodes: expressions, clauses, and declaration
// nodes. The node names usually match the corresponding CUE spec production
// names to which they correspond. The node fields correspond to the individual
// parts of the respective productions.
//
// All nodes contain position information marking the beginning of the
// corresponding source text segment; it is accessible via the Pos accessor
// method. Nodes may contain additional position info for language constructs
// where comments may be found between parts of the construct (typically any
// larger, parenthesized subpart). That position information is needed to
// properly position comments when printing the construct.

// A Node represents any node in the abstract syntax tree.
type Node interface {
	Pos() token.Pos // position of first character belonging to the node
	End() token.Pos // position of first character immediately after the node

	// pos reports the pointer to the position of first character belonging to
	// the node or nil if there is no such position.
	pos() *token.Pos

	// Deprecated: use [Comments]
	Comments() []*CommentGroup

	// Deprecated: use [AddComment]
	AddComment(*CommentGroup)
	commentInfo() *comments
}

// Name describes the type of n.
func Name(n Node) string {
	s := fmt.Sprintf("%T", n)
	return strings.ToLower(s[strings.Index(s, "ast.")+4:])
}

func getPos(n Node) token.Pos {
	p := n.pos()
	if p == nil {
		return token.NoPos
	}
	return *p
}

// SetPos sets a node to the given position, if possible.
func SetPos(n Node, p token.Pos) {
	ptr := n.pos()
	if ptr == nil {
		return
	}
	*ptr = p
}

// SetRelPos sets the relative position of a node without modifying its
// file position. Setting it to token.NoRelPos allows a node to adopt default
// formatting.
func SetRelPos(n Node, p token.RelPos) {
	ptr := n.pos()
	if ptr == nil {
		return
	}
	pos := *ptr
	*ptr = pos.WithRel(p)
}

// An Expr is implemented by all expression nodes.
type Expr interface {
	Node
	declNode() // An expression can be used as a declaration.
	exprNode()
}

type expr struct{ decl }

func (expr) exprNode() {}

// A Decl node is implemented by all declarations.
type Decl interface {
	Node
	declNode()
}

type decl struct{}

func (decl) declNode() {}

// A Label is any production that can be used as an LHS label.
type Label interface {
	Node
	labelNode()
}

type label struct{}

func (l label) labelNode() {}

// Clause nodes are part of comprehensions.
type Clause interface {
	Node
	clauseNode()
}

type clause struct{}

func (clause) clauseNode() {}

// Comments

type comments struct {
	groups *[]*CommentGroup
}

func (c *comments) commentInfo() *comments { return c }

func (c *comments) Comments() []*CommentGroup {
	if c.groups == nil {
		return []*CommentGroup{}
	}
	return *c.groups
}

func (c *comments) AddComment(cg *CommentGroup) {
	if cg == nil {
		return
	}
	if c.groups == nil {
		a := []*CommentGroup{cg}
		c.groups = &a
		return
	}

	*c.groups = append(*c.groups, cg)
	a := *c.groups
	for i := len(a) - 2; i >= 0 && a[i].Position > cg.Position; i-- {
		a[i], a[i+1] = a[i+1], a[i]
	}
}

func (c *comments) SetComments(cgs []*CommentGroup) {
	if c.groups == nil {
		if cgs == nil {
			// Replacing no comments with a nil slice is a no-op.
			// Avoid allocating below.
			// Note that we continue for other zero-length slices,
			// as the caller may want to reuse memory.
			return
		}
		a := cgs
		c.groups = &a
		return
	}
	*c.groups = cgs
}

// A Comment node represents a single //-style comment.
type Comment struct {
	Slash token.Pos // position of "/" starting the comment
	Text  string    // comment text excluding '\n'
}

func (c *Comment) Comments() []*CommentGroup { return nil }
func (c *Comment) AddComment(*CommentGroup)  {}
func (c *Comment) commentInfo() *comments    { return nil }

func (c *Comment) Pos() token.Pos  { return c.Slash }
func (c *Comment) pos() *token.Pos { return &c.Slash }
func (c *Comment) End() token.Pos  { return c.Slash.Add(len(c.Text)) }

// A CommentGroup represents a sequence of comments
// with no other tokens and no empty lines between.
type CommentGroup struct {
	// TODO: remove and use the token position of the first comment.
	Doc  bool
	Line bool // true if it is on the same line as the node's end pos.

	// Position indicates where a comment should be attached if a node has
	// multiple tokens. 0 means before the first token, 1 means before the
	// second, etc. For instance, for a field, the positions are:
	//    <0> Label <1> ":" <2> Expr <3> "," <4>
	Position int8
	List     []*Comment // len(List) > 0

	decl
}

func (g *CommentGroup) Pos() token.Pos  { return getPos(g) }
func (g *CommentGroup) pos() *token.Pos { return g.List[0].pos() }
func (g *CommentGroup) End() token.Pos  { return g.List[len(g.List)-1].End() }

func (g *CommentGroup) Comments() []*CommentGroup { return nil }
func (g *CommentGroup) AddComment(*CommentGroup)  {}
func (g *CommentGroup) commentInfo() *comments    { return nil }

func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' }

func stripTrailingWhitespace(s string) string {
	i := len(s)
	for i > 0 && isWhitespace(s[i-1]) {
		i--
	}
	return s[0:i]
}

// Text returns the text of the comment.
// Comment markers ("//"), the first space of a line comment, and
// leading and trailing empty lines are removed. Multiple empty lines are
// reduced to one, and trailing space on lines is trimmed. Unless the result
// is empty, it is newline-terminated.
func (g *CommentGroup) Text() string {
	if g == nil {
		return ""
	}
	comments := make([]string, len(g.List))
	for i, c := range g.List {
		comments[i] = c.Text
	}

	lines := make([]string, 0, 10) // most comments are less than 10 lines
	for _, c := range comments {
		// Remove comment markers.
		// The parser has given us exactly the comment text.
		c = c[2:]
		// strip first space - required for Example tests
		if len(c) > 0 && c[0] == ' ' {
			c = c[1:]
		}

		// Split on newlines.
		cl := strings.SplitSeq(c, "\n")

		// Walk lines, stripping trailing white space and adding to list.
		for l := range cl {
			lines = append(lines, stripTrailingWhitespace(l))
		}
	}

	// Remove leading blank lines; convert runs of
	// interior blank lines to a single blank line.
	n := 0
	for _, line := range lines {
		if line != "" || n > 0 && lines[n-1] != "" {
			lines[n] = line
			n++
		}
	}
	lines = lines[0:n]

	// Add final "" entry to get trailing newline from Join.
	if n > 0 && lines[n-1] != "" {
		lines = append(lines, "")
	}

	return strings.Join(lines, "\n")
}

// An Attribute provides meta data about a field.
type Attribute struct {
	At   token.Pos
	Text string // must be a valid attribute format.

	comments
	decl
}

func (a *Attribute) Pos() token.Pos  { return a.At }
func (a *Attribute) pos() *token.Pos { return &a.At }
func (a *Attribute) End() token.Pos  { return a.At.Add(len(a.Text)) }

func (a *Attribute) Split() (key, body string) {
	s := a.Text
	p := strings.IndexByte(s, '(')
	if p < 0 || !strings.HasPrefix(s, "@") || !strings.HasSuffix(s, ")") {
		return "", ""
	}
	return a.Text[1:p], a.Text[p+1 : len(s)-1]
}

// A Field represents a field declaration in a struct.
type Field struct {
	// TODO(mvdan): remove the deprecated fields below in early 2026.

	Label Label         // must have at least one element.
	Alias *PostfixAlias // optional postfix alias (nil if no alias)
	// Deprecated: use [Field.Constraint]
	Optional   token.Pos
	Constraint token.Token // token.ILLEGAL, token.OPTION, or token.NOT

	// No TokenPos: Value must be an StructLit with one field.
	TokenPos token.Pos
	// Deprecated: the value is always [token.COLON]
	Token token.Token

	Value Expr // the value associated with this field.

	Attrs []*Attribute

	comments
	decl
}

func (d *Field) Pos() token.Pos  { return d.Label.Pos() }
func (d *Field) pos() *token.Pos { return d.Label.pos() }
func (d *Field) End() token.Pos {
	if len(d.Attrs) > 0 {
		return d.Attrs[len(d.Attrs)-1].End()
	}
	return d.Value.End()
}

// TODO: make Alias a type of Field. This is possible now we have different
// separator types.

// An Alias binds another field to the alias name in the current struct.
type Alias struct {
	Ident *Ident    // field name, always an Ident
	Equal token.Pos // position of "="
	Expr  Expr      // An Ident or SelectorExpr

	comments
	clause
	decl
	expr
	label
}

func (a *Alias) Pos() token.Pos  { return a.Ident.Pos() }
func (a *Alias) pos() *token.Pos { return a.Ident.pos() }
func (a *Alias) End() token.Pos  { return a.Expr.End() }

// A PostfixAlias represents the new postfix alias syntax using ~.
// It appears in field declarations after the label.
//
// Simple form: label~X where X captures the field reference
// Dual form: label~(K,V) where K captures the label name string and V captures the field reference
type PostfixAlias struct {
	Tilde token.Pos // position of "~"

	// Dual form: ~(K,V)
	Lparen token.Pos // position of "(" (invalid if simple form)
	Label  *Ident    // K: label name capture (nil if simple form)
	Comma  token.Pos // position of "," (invalid if simple form)
	Rparen token.Pos // position of ")" (invalid if simple form)

	// Both forms: the field reference (always non-nil)
	Field *Ident // X or V: captures the field reference

	comments
}

func (a *PostfixAlias) Pos() token.Pos  { return a.Tilde }
func (a *PostfixAlias) pos() *token.Pos { return &a.Tilde }
func (a *PostfixAlias) End() token.Pos {
	if a.Rparen.IsValid() {
		return a.Rparen.Add(1)
	}
	return a.Field.End()
}

// A Comprehension node represents a comprehension declaration.
type Comprehension struct {
	Clauses []Clause // There must be at least one clause.
	Value   Expr     // Must be a struct TODO: change to Struct

	comments
	decl
	expr // TODO: only allow Comprehension in "Embedding" productions.
}

func (x *Comprehension) Pos() token.Pos  { return getPos(x) }
func (x *Comprehension) pos() *token.Pos { return x.Clauses[0].pos() }
func (x *Comprehension) End() token.Pos {
	return x.Value.End()
}

// ----------------------------------------------------------------------------
// Expressions and types
//
// An expression is represented by a tree consisting of one
// or more of the following concrete expression nodes.

// A BadExpr node is a placeholder for expressions containing
// syntax errors for which no correct expression nodes can be
// created. This is different from an ErrorExpr which represents
// an explicitly marked error in the source.
type BadExpr struct {
	From, To token.Pos // position range of bad expression

	comments
	expr
}

// A BottomLit indicates an error.
type BottomLit struct {
	Bottom token.Pos

	comments
	expr
}

// An Ident node represents a left-hand side identifier,
// including the underscore "_" identifier to represent top.
type Ident struct {
	NamePos token.Pos // identifier position

	// This LHS path element may be an identifier. Possible forms:
	//  foo:    a normal identifier
	//  "foo":  JSON compatible
	Name string

	Scope Node // scope in which node was found or nil if referring directly
	Node  Node // node referenced by this identifier, if any; see [cuelang.org/go/cue/ast/astutil.Resolve]

	comments
	label
	expr
}

// A BasicLit node represents a literal of basic type.
type BasicLit struct {
	ValuePos token.Pos   // literal position
	Kind     token.Token // INT, FLOAT, DURATION, or STRING
	Value    string      // literal string; e.g. 42, 0x7f, 3.14, 1_234_567, 1e-9, 2.4i, 'a', '\x7f', "foo", or '\m\n\o'

	comments
	expr
	label
}

// TODO: introduce and use NewBytes and perhaps NewText (in the
// later case NewString would return a string or bytes type) to distinguish from
// NewString. Consider how to pass indentation information.

// NewStringLabel creates a new string label with the given string,
// quoting it as a string literal only if necessary,
// as outlined in [StringLabelNeedsQuoting].
//
// To create labels for definition or hidden fields, use [NewIdent].
func NewStringLabel(name string) Label {
	if StringLabelNeedsQuoting(name) {
		return NewString(name)
	}
	return NewIdent(name)
}

// NewString creates a new BasicLit with a string value without position.
// It quotes the given string.
// Useful for ASTs generated by code other than the CUE parser.
func NewString(str string) *BasicLit {
	str = literal.String.Quote(str)
	return &BasicLit{Kind: token.STRING, ValuePos: token.NoPos, Value: str}
}

// NewNull creates a new BasicLit configured to be a null value.
// Useful for ASTs generated by code other than the CUE parser.
func NewNull() *BasicLit {
	return &BasicLit{Kind: token.NULL, Value: "null"}
}

// NewLit creates a new BasicLit with from a token type and string without
// position.
// Useful for ASTs generated by code other than the CUE parser.
func NewLit(tok token.Token, s string) *BasicLit {
	return &BasicLit{Kind: tok, Value: s}
}

// NewBool creates a new BasicLit with a bool value without position.
// Useful for ASTs generated by code other than the CUE parser.
func NewBool(b bool) *BasicLit {
	x := &BasicLit{}
	if b {
		x.Kind = token.TRUE
		x.Value = "true"
	} else {
		x.Kind = token.FALSE
		x.Value = "false"
	}
	return x
}

// TODO:
// - use CUE-specific quoting (hoist functionality in export)
// - NewBytes

// A Interpolation node represents a string or bytes interpolation.
type Interpolation struct {
	Elts []Expr // interleaving of strings and expressions.

	comments
	expr
	label
}

// A Func node represents a function type.
//
// This is an experimental type and the contents will change without notice.
type Func struct {
	Func token.Pos // position of "func"
	Args []Expr    // list of elements; or nil
	Ret  Expr      // return type, must not be nil

	comments
	expr
}

// A StructLit node represents a literal struct.
type StructLit struct {
	Lbrace token.Pos // position of "{"
	Elts   []Decl    // list of elements; or nil
	Rbrace token.Pos // position of "}"

	comments
	expr
}

// NewStruct creates a struct from the given fields.
//
// A field is either a *Field, an *Ellipsis, *LetClause, a *CommentGroup, or a
// Label, optionally followed by a token.OPTION or token.NOT to indicate the
// field is optional or required, followed by an expression for the field value.
//
// It will panic if a values not matching these patterns are given. Useful for
// ASTs generated by code other than the CUE parser.
func NewStruct(fields ...interface{}) *StructLit {
	s := &StructLit{
		// Set default positions so that comment attachment is as expected.
		Lbrace: token.NoSpace.Pos(),
	}
	for i := 0; i < len(fields); i++ {
		var (
			label      Label
			optional   = token.NoPos
			constraint = token.ILLEGAL
			expr       Expr
		)

		switch x := fields[i].(type) {
		case *Field:
			s.Elts = append(s.Elts, x)
			continue
		case *CommentGroup:
			s.Elts = append(s.Elts, x)
			continue
		case *Ellipsis:
			s.Elts = append(s.Elts, x)
			continue
		case *LetClause:
			s.Elts = append(s.Elts, x)
			continue
		case *embedding:
			s.Elts = append(s.Elts, (*EmbedDecl)(x))
			continue
		case Label:
			label = x
		case string:
			label = NewString(x)
		default:
			panic(fmt.Sprintf("unsupported label type %T", x))
		}

	inner:
		for i++; i < len(fields); i++ {
			switch x := (fields[i]).(type) {
			case Expr:
				expr = x
				break inner
			case token.Token:
				switch x {
				case token.OPTION:
					constraint = x
					optional = token.Blank.Pos()
				case token.NOT:
					constraint = x
				case token.COLON, token.ILLEGAL:
				default:
					panic(fmt.Sprintf("invalid token %s", x))
				}
			default:
				panic(fmt.Sprintf("unsupported expression type %T", x))
			}
		}
		if expr == nil {
			panic("label not matched with expression")
		}
		s.Elts = append(s.Elts, &Field{
			Label:      label,
			Optional:   optional,
			Constraint: constraint,
			Value:      expr,
		})
	}
	return s
}

// Embed can be used in conjunction with NewStruct to embed values.
func Embed(x Expr) *embedding {
	return (*embedding)(&EmbedDecl{Expr: x})
}

type embedding EmbedDecl

// A ListLit node represents a literal list.
type ListLit struct {
	Lbrack token.Pos // position of "["

	// TODO: change to embedding or similar.
	Elts   []Expr    // list of composite elements; or nil
	Rbrack token.Pos // position of "]"

	comments
	expr
	label
}

// NewList creates a list of Expressions.
// Useful for ASTs generated by code other than the CUE parser.
func NewList(exprs ...Expr) *ListLit {
	return &ListLit{Elts: exprs}
}

type Ellipsis struct {
	Ellipsis token.Pos // open list if set
	Type     Expr      // type for the remaining elements

	comments
	decl
	expr
}

// A ForClause node represents a for clause in a comprehension.
type ForClause struct {
	For token.Pos
	Key *Ident // allow pattern matching?
	// TODO: change to Comma
	Colon  token.Pos
	Value  *Ident // allow pattern matching?
	In     token.Pos
	Source Expr

	comments
	clause
}

// A IfClause node represents an if guard clause in a comprehension.
type IfClause struct {
	If        token.Pos
	Condition Expr

	comments
	clause
}

// A LetClause node represents a let clause in a comprehension.
type LetClause struct {
	Let   token.Pos
	Ident *Ident
	Equal token.Pos
	Expr  Expr

	comments
	clause
	decl
}

// A ParenExpr node represents a parenthesized expression.
type ParenExpr struct {
	Lparen token.Pos // position of "("
	X      Expr      // parenthesized expression
	Rparen token.Pos // position of ")"

	comments
	expr
	label
}

// A SelectorExpr node represents an expression followed by a selector.
type SelectorExpr struct {
	X   Expr  // expression
	Sel Label // field selector

	comments
	expr
}

// NewSel creates a sequence of selectors.
// Useful for ASTs generated by code other than the CUE parser.
func NewSel(x Expr, sel ...string) Expr {
	for _, s := range sel {
		x = &SelectorExpr{X: x, Sel: NewIdent(s)}
	}
	return x
}

// An IndexExpr node represents an expression followed by an index.
type IndexExpr struct {
	X      Expr      // expression
	Lbrack token.Pos // position of "["
	Index  Expr      // index expression
	Rbrack token.Pos // position of "]"

	comments
	expr
}

// An SliceExpr node represents an expression followed by slice indices.
type SliceExpr struct {
	X      Expr      // expression
	Lbrack token.Pos // position of "["
	Low    Expr      // begin of slice range; or nil
	High   Expr      // end of slice range; or nil
	Rbrack token.Pos // position of "]"

	comments
	expr
}

// A CallExpr node represents an expression followed by an argument list.
type CallExpr struct {
	Fun    Expr      // function expression
	Lparen token.Pos // position of "("
	Args   []Expr    // function arguments; or nil
	Rparen token.Pos // position of ")"

	comments
	expr
}

// NewCall creates a new CallExpr.
// Useful for ASTs generated by code other than the CUE parser.
func NewCall(fun Expr, args ...Expr) *CallExpr {
	return &CallExpr{Fun: fun, Args: args}
}

// A UnaryExpr node represents a unary expression.
type UnaryExpr struct {
	OpPos token.Pos   // position of Op
	Op    token.Token // operator
	X     Expr        // operand

	comments
	expr
}

// A BinaryExpr node represents a binary expression.
type BinaryExpr struct {
	X     Expr        // left operand
	OpPos token.Pos   // position of Op
	Op    token.Token // operator
	Y     Expr        // right operand

	comments
	expr
}

// A PostfixExpr node represents an expression followed by a postfix operator.
type PostfixExpr struct {
	X     Expr        // expression
	Op    token.Token // postfix operator // ... or ?
	OpPos token.Pos   // position of operator

	comments
	expr
}

// NewBinExpr creates for list of expressions of length 2 or greater a chained
// binary expression of the form (((x1 op x2) op x3) ...). For lists of length
// 1 it returns the expression itself. It panics for empty lists.
// Useful for ASTs generated by code other than the CUE parser.
func NewBinExpr(op token.Token, operands ...Expr) Expr {
	if len(operands) == 0 {
		return nil
	}
	expr := operands[0]
	for _, e := range operands[1:] {
		expr = &BinaryExpr{X: expr, Op: op, Y: e}
	}
	return expr
}

// token.Pos and End implementations for expression/type nodes.

func (x *BadExpr) Pos() token.Pos        { return x.From }
func (x *BadExpr) pos() *token.Pos       { return &x.From }
func (x *Ident) Pos() token.Pos          { return x.NamePos }
func (x *Ident) pos() *token.Pos         { return &x.NamePos }
func (x *BasicLit) Pos() token.Pos       { return x.ValuePos }
func (x *BasicLit) pos() *token.Pos      { return &x.ValuePos }
func (x *Interpolation) Pos() token.Pos  { return x.Elts[0].Pos() }
func (x *Interpolation) pos() *token.Pos { return x.Elts[0].pos() }
func (x *Func) Pos() token.Pos           { return x.Func }
func (x *Func) pos() *token.Pos          { return &x.Func }
func (x *StructLit) Pos() token.Pos      { return getPos(x) }
func (x *StructLit) pos() *token.Pos {
	if x.Lbrace == token.NoPos && len(x.Elts) > 0 {
		return x.Elts[0].pos()
	}
	return &x.Lbrace
}

func (x *ListLit) Pos() token.Pos       { return x.Lbrack }
func (x *ListLit) pos() *token.Pos      { return &x.Lbrack }
func (x *Ellipsis) Pos() token.Pos      { return x.Ellipsis }
func (x *Ellipsis) pos() *token.Pos     { return &x.Ellipsis }
func (x *LetClause) Pos() token.Pos     { return x.Let }
func (x *LetClause) pos() *token.Pos    { return &x.Let }
func (x *ForClause) Pos() token.Pos     { return x.For }
func (x *ForClause) pos() *token.Pos    { return &x.For }
func (x *IfClause) Pos() token.Pos      { return x.If }
func (x *IfClause) pos() *token.Pos     { return &x.If }
func (x *ParenExpr) Pos() token.Pos     { return x.Lparen }
func (x *ParenExpr) pos() *token.Pos    { return &x.Lparen }
func (x *SelectorExpr) Pos() token.Pos  { return x.X.Pos() }
func (x *SelectorExpr) pos() *token.Pos { return x.X.pos() }
func (x *IndexExpr) Pos() token.Pos     { return x.X.Pos() }
func (x *IndexExpr) pos() *token.Pos    { return x.X.pos() }
func (x *SliceExpr) Pos() token.Pos     { return x.X.Pos() }
func (x *SliceExpr) pos() *token.Pos    { return x.X.pos() }
func (x *CallExpr) Pos() token.Pos      { return x.Fun.Pos() }
func (x *CallExpr) pos() *token.Pos     { return x.Fun.pos() }
func (x *UnaryExpr) Pos() token.Pos     { return x.OpPos }
func (x *UnaryExpr) pos() *token.Pos    { return &x.OpPos }
func (x *BinaryExpr) Pos() token.Pos    { return x.X.Pos() }
func (x *BinaryExpr) pos() *token.Pos   { return x.X.pos() }
func (x *PostfixExpr) Pos() token.Pos   { return x.X.Pos() }
func (x *PostfixExpr) pos() *token.Pos  { return x.X.pos() }
func (x *BottomLit) Pos() token.Pos     { return x.Bottom }
func (x *BottomLit) pos() *token.Pos    { return &x.Bottom }

func (x *BadExpr) End() token.Pos { return x.To }
func (x *Ident) End() token.Pos {
	return x.NamePos.Add(len(x.Name))
}
func (x *BasicLit) End() token.Pos { return x.ValuePos.Add(len(x.Value)) }

func (x *Interpolation) End() token.Pos { return x.Elts[len(x.Elts)-1].Pos() }
func (x *Func) End() token.Pos          { return x.Ret.End() }
func (x *StructLit) End() token.Pos {
	if x.Rbrace == token.NoPos && len(x.Elts) > 0 {
		return x.Elts[len(x.Elts)-1].End()
	}
	return x.Rbrace.Add(1)
}
func (x *ListLit) End() token.Pos { return x.Rbrack.Add(1) }
func (x *Ellipsis) End() token.Pos {
	if x.Type != nil {
		return x.Type.End()
	}
	return x.Ellipsis.Add(3) // len("...")
}
func (x *LetClause) End() token.Pos    { return x.Expr.End() }
func (x *ForClause) End() token.Pos    { return x.Source.End() }
func (x *IfClause) End() token.Pos     { return x.Condition.End() }
func (x *ParenExpr) End() token.Pos    { return x.Rparen.Add(1) }
func (x *SelectorExpr) End() token.Pos { return x.Sel.End() }
func (x *IndexExpr) End() token.Pos    { return x.Rbrack.Add(1) }
func (x *SliceExpr) End() token.Pos    { return x.Rbrack.Add(1) }
func (x *CallExpr) End() token.Pos     { return x.Rparen.Add(1) }
func (x *UnaryExpr) End() token.Pos    { return x.X.End() }
func (x *BinaryExpr) End() token.Pos   { return x.Y.End() }
func (x *PostfixExpr) End() token.Pos {
	switch x.Op {
	case token.ELLIPSIS:
		return x.OpPos.Add(3) // len("...")
	default:
		return x.OpPos.Add(1) // most single-char operators
	}
}
func (x *BottomLit) End() token.Pos { return x.Bottom.Add(1) }

// ----------------------------------------------------------------------------
// Convenience functions for Idents

// NewIdent creates a new Ident without position.
// Useful for ASTs generated by code other than the CUE parser.
func NewIdent(name string) *Ident {
	return &Ident{token.NoPos, name, nil, nil, comments{}, label{}, expr{}}
}

func (id *Ident) String() string {
	if id != nil {
		return id.Name
	}
	return "<nil>"
}

// ----------------------------------------------------------------------------
// Declarations

// An ImportSpec node represents a single package import.
type ImportSpec struct {
	Name   *Ident    // local package name (including "."); or nil
	Path   *BasicLit // import path
	EndPos token.Pos // end of spec (overrides Path.Pos if nonzero)

	comments
}

func (*ImportSpec) specNode() {}

func NewImport(name *Ident, importPath string) *ImportSpec {
	importPath = literal.String.Quote(importPath)
	path := &BasicLit{Kind: token.STRING, Value: importPath}
	return &ImportSpec{Name: name, Path: path}
}

// Pos and End implementations for spec nodes.

func (s *ImportSpec) Pos() token.Pos { return getPos(s) }
func (s *ImportSpec) pos() *token.Pos {
	if s.Name != nil {
		return s.Name.pos()
	}
	return s.Path.pos()
}

func (s *ImportSpec) End() token.Pos {
	if s.EndPos != token.NoPos {
		return s.EndPos
	}
	return s.Path.End()
}

// A BadDecl node is a placeholder for declarations containing
// syntax errors for which no correct declaration nodes can be
// created.
type BadDecl struct {
	From, To token.Pos // position range of bad declaration

	comments
	decl
}

// A ImportDecl node represents a series of import declarations. A valid
// Lparen position (Lparen.Line > 0) indicates a parenthesized declaration.
type ImportDecl struct {
	Import token.Pos
	Lparen token.Pos // position of '(', if any
	Specs  []*ImportSpec
	Rparen token.Pos // position of ')', if any

	comments
	decl
}

type Spec interface {
	Node
	specNode()
}

// An EmbedDecl node represents a single expression used as a declaration.
// The expressions in this declaration is what will be emitted as
// configuration output.
//
// An EmbedDecl may only appear at the top level.
type EmbedDecl struct {
	Expr Expr

	comments
	decl
}

// Pos and End implementations for declaration nodes.

func (d *BadDecl) Pos() token.Pos     { return d.From }
func (d *BadDecl) pos() *token.Pos    { return &d.From }
func (d *ImportDecl) Pos() token.Pos  { return d.Import }
func (d *ImportDecl) pos() *token.Pos { return &d.Import }
func (d *EmbedDecl) Pos() token.Pos   { return d.Expr.Pos() }
func (d *EmbedDecl) pos() *token.Pos  { return d.Expr.pos() }

func (d *BadDecl) End() token.Pos { return d.To }
func (d *ImportDecl) End() token.Pos {
	if d.Rparen.IsValid() {
		return d.Rparen.Add(1)
	}
	if len(d.Specs) == 0 {
		return token.NoPos
	}
	return d.Specs[0].End()
}
func (d *EmbedDecl) End() token.Pos { return d.Expr.End() }

// ----------------------------------------------------------------------------
// Files and packages

// A File node represents a CUE source file.
type File struct {
	Filename string
	Decls    []Decl // top-level declarations; or nil

	// Deprecated: use [File.ImportSpecs].
	// TODO(mvdan): remove in mid 2026.
	Imports []*ImportSpec // imports in this file

	Unresolved []*Ident // unresolved identifiers in this file

	// TODO remove this field: it's here as a temporary
	// entity so that tests can determine which version
	// the file was parsed with. A better approach is probably to
	// include the language version in the `token.File` so
	// it's available in every Position.
	LanguageVersion string // The language version as configured by [parser.ParseFile].

	comments
}

// Preamble returns the declarations of the preamble.
func (f *File) Preamble() []Decl {
	p := 0
outer:
	for i, d := range f.Decls {
		switch d.(type) {
		default:
			break outer

		case *Package:
			p = i + 1
		case *CommentGroup:
		case *Attribute:
		case *ImportDecl:
			p = i + 1
		}
	}
	return f.Decls[:p]
}

// VisitImports iterates through the import declarations in the file.
//
// Deprecated: use [File.ImportDecls].
func (f *File) VisitImports(fn func(d *ImportDecl)) {
	for d := range f.ImportDecls() {
		fn(d)
	}
}

// ImportDecls iterates through the import declarations in the file.
func (f *File) ImportDecls() iter.Seq[*ImportDecl] {
	return func(yield func(d *ImportDecl) bool) {
		for _, d := range f.Decls {
			switch x := d.(type) {
			case *CommentGroup:
			case *Package:
			case *Attribute:
			case *ImportDecl:
				if !yield(x) {
					return
				}
			default:
				return
			}
		}
	}
}

// ImportSpecs iterates through all the import specs from all the import decls in the file.
func (f *File) ImportSpecs() iter.Seq[*ImportSpec] {
	return func(yield func(d *ImportSpec) bool) {
		for d := range f.ImportDecls() {
			for _, spec := range d.Specs {
				if !yield(spec) {
					return
				}
			}
		}
	}
}

// PackageName returns the package name associated with this file or "" if no
// package is associated.
func (f *File) PackageName() string {
	for _, d := range f.Decls {
		switch x := d.(type) {
		case *Package:
			if x.Name.Name == "_" {
				return ""
			}
			return x.Name.Name
		case *CommentGroup, *Attribute:
		default:
			return ""
		}
	}
	return ""
}

func (f *File) Pos() token.Pos {
	if len(f.Decls) > 0 {
		return f.Decls[0].Pos()
	}
	if f.Filename != "" {
		// TODO. Do something more principled and efficient.
		return token.NewFile(f.Filename, -1, 1).Pos(0, 0)
	}
	return token.NoPos
}

func (f *File) pos() *token.Pos {
	if len(f.Decls) > 0 {
		return f.Decls[0].pos()
	}
	if f.Filename != "" {
		return nil
	}
	return nil
}

func (f *File) End() token.Pos {
	if n := len(f.Decls); n > 0 {
		return f.Decls[n-1].End()
	}
	return token.NoPos
}

// A Package represents a package clause.
type Package struct {
	PackagePos token.Pos // position of "package" pseudo-keyword
	Name       *Ident    // package name

	comments
	decl
}

func (p *Package) Pos() token.Pos { return getPos(p) }
func (p *Package) pos() *token.Pos {
	if p.PackagePos != token.NoPos {
		return &p.PackagePos
	}
	if p.Name != nil {
		return p.Name.pos()
	}
	return nil
}

func (p *Package) End() token.Pos {
	if p.Name != nil {
		return p.Name.End()
	}
	return token.NoPos
}
