// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package inspector

import (
	"fmt"
	"go/ast"
	"go/token"
	"iter"
	"reflect"

	"golang.org/x/tools/go/ast/edge"
)

// A Cursor represents an [ast.Node]. It is immutable.
//
// Two Cursors compare equal if they represent the same node.
//
// Call [Inspector.Root] to obtain a valid cursor for the virtual root
// node of the traversal.
//
// Use the following methods to navigate efficiently around the tree:
//   - for ancestors, use [Cursor.Parent] and [Cursor.Enclosing];
//   - for children, use [Cursor.Child], [Cursor.Children],
//     [Cursor.FirstChild], and [Cursor.LastChild];
//   - for siblings, use [Cursor.PrevSibling] and [Cursor.NextSibling];
//   - for descendants, use [Cursor.FindByPos], [Cursor.FindNode],
//     [Cursor.Inspect], and [Cursor.Preorder].
//
// Use the [Cursor.ChildAt] and [Cursor.ParentEdge] methods for
// information about the edges in a tree: which field (and slice
// element) of the parent node holds the child.
type Cursor struct {
	in    *Inspector
	index int32 // index of push node; -1 for virtual root node
}

// Root returns a cursor for the virtual root node,
// whose children are the files provided to [New].
//
// Its [Cursor.Node] method return nil.
func (in *Inspector) Root() Cursor {
	return Cursor{in, -1}
}

// At returns the cursor at the specified index in the traversal,
// which must have been obtained from [Cursor.Index] on a Cursor
// belonging to the same Inspector (see [Cursor.Inspector]).
func (in *Inspector) At(index int32) Cursor {
	if index < 0 {
		panic("negative index")
	}
	if int(index) >= len(in.events) {
		panic("index out of range for this inspector")
	}
	if in.events[index].index < index {
		panic("invalid index") // (a push, not a pop)
	}
	return Cursor{in, index}
}

// Inspector returns the cursor's Inspector.
func (c Cursor) Inspector() *Inspector { return c.in }

// Index returns the index of this cursor position within the package.
//
// Clients should not assume anything about the numeric Index value
// except that it increases monotonically throughout the traversal.
// It is provided for use with [At].
//
// Index must not be called on the Root node.
func (c Cursor) Index() int32 {
	if c.index < 0 {
		panic("Index called on Root node")
	}
	return c.index
}

// Node returns the node at the current cursor position,
// or nil for the cursor returned by [Inspector.Root].
func (c Cursor) Node() ast.Node {
	if c.index < 0 {
		return nil
	}
	return c.in.events[c.index].node
}

// String returns information about the cursor's node, if any.
func (c Cursor) String() string {
	if c.in == nil {
		return "(invalid)"
	}
	if c.index < 0 {
		return "(root)"
	}
	return reflect.TypeOf(c.Node()).String()
}

// indices return the [start, end) half-open interval of event indices.
func (c Cursor) indices() (int32, int32) {
	if c.index < 0 {
		return 0, int32(len(c.in.events)) // root: all events
	} else {
		return c.index, c.in.events[c.index].index + 1 // just one subtree
	}
}

// Preorder returns an iterator over the nodes of the subtree
// represented by c in depth-first order. Each node in the sequence is
// represented by a Cursor that allows access to the Node, but may
// also be used to start a new traversal, or to obtain the stack of
// nodes enclosing the cursor.
//
// The traversal sequence is determined by [ast.Inspect]. The types
// argument, if non-empty, enables type-based filtering of events. The
// function f if is called only for nodes whose type matches an
// element of the types slice.
//
// If you need control over descent into subtrees,
// or need both pre- and post-order notifications, use [Cursor.Inspect]
func (c Cursor) Preorder(types ...ast.Node) iter.Seq[Cursor] {
	mask := maskOf(types)

	return func(yield func(Cursor) bool) {
		events := c.in.events

		for i, limit := c.indices(); i < limit; {
			ev := events[i]
			if ev.index > i { // push?
				if ev.typ&mask != 0 && !yield(Cursor{c.in, i}) {
					break
				}
				pop := ev.index
				if events[pop].typ&mask == 0 {
					// Subtree does not contain types: skip.
					i = pop + 1
					continue
				}
			}
			i++
		}
	}
}

// Inspect visits the nodes of the subtree represented by c in
// depth-first order. It calls f(n) for each node n before it
// visits n's children. If f returns true, Inspect invokes f
// recursively for each of the non-nil children of the node.
//
// Each node is represented by a Cursor that allows access to the
// Node, but may also be used to start a new traversal, or to obtain
// the stack of nodes enclosing the cursor.
//
// The complete traversal sequence is determined by [ast.Inspect].
// The types argument, if non-empty, enables type-based filtering of
// events. The function f if is called only for nodes whose type
// matches an element of the types slice.
func (c Cursor) Inspect(types []ast.Node, f func(c Cursor) (descend bool)) {
	mask := maskOf(types)
	events := c.in.events
	for i, limit := c.indices(); i < limit; {
		ev := events[i]
		if ev.index > i {
			// push
			pop := ev.index
			if ev.typ&mask != 0 && !f(Cursor{c.in, i}) ||
				events[pop].typ&mask == 0 {
				// The user opted not to descend, or the
				// subtree does not contain types:
				// skip past the pop.
				i = pop + 1
				continue
			}
		}
		i++
	}
}

// Enclosing returns an iterator over the nodes enclosing the current
// current node, starting with the Cursor itself.
//
// Enclosing must not be called on the Root node (whose [Cursor.Node] returns nil).
//
// The types argument, if non-empty, enables type-based filtering of
// events: the sequence includes only enclosing nodes whose type
// matches an element of the types slice.
func (c Cursor) Enclosing(types ...ast.Node) iter.Seq[Cursor] {
	if c.index < 0 {
		panic("Cursor.Enclosing called on Root node")
	}

	mask := maskOf(types)

	return func(yield func(Cursor) bool) {
		events := c.in.events
		for i := c.index; i >= 0; i = events[i].parent {
			if events[i].typ&mask != 0 && !yield(Cursor{c.in, i}) {
				break
			}
		}
	}
}

// Parent returns the parent of the current node.
//
// Parent must not be called on the Root node (whose [Cursor.Node] returns nil).
func (c Cursor) Parent() Cursor {
	if c.index < 0 {
		panic("Cursor.Parent called on Root node")
	}

	return Cursor{c.in, c.in.events[c.index].parent}
}

// ParentEdge returns the identity of the field in the parent node
// that holds this cursor's node, and if it is a list, the index within it.
//
// For example, f(x, y) is a CallExpr whose three children are Idents.
// f has edge kind [edge.CallExpr_Fun] and index -1.
// x and y have kind [edge.CallExpr_Args] and indices 0 and 1, respectively.
//
// If called on a child of the Root node, it returns ([edge.Invalid], -1).
//
// ParentEdge must not be called on the Root node (whose [Cursor.Node] returns nil).
func (c Cursor) ParentEdge() (edge.Kind, int) {
	if c.index < 0 {
		panic("Cursor.ParentEdge called on Root node")
	}
	events := c.in.events
	pop := events[c.index].index
	return unpackEdgeKindAndIndex(events[pop].parent)
}

// ChildAt returns the cursor for the child of the
// current node identified by its edge and index.
// The index must be -1 if the edge.Kind is not a slice.
// The indicated child node must exist.
//
// ChildAt must not be called on the Root node (whose [Cursor.Node] returns nil).
//
// Invariant: c.Parent().ChildAt(c.ParentEdge()) == c.
func (c Cursor) ChildAt(k edge.Kind, idx int) Cursor {
	target := packEdgeKindAndIndex(k, idx)

	// Unfortunately there's no shortcut to looping.
	events := c.in.events
	i := c.index + 1
	for {
		pop := events[i].index
		if pop < i {
			break
		}
		if events[pop].parent == target {
			return Cursor{c.in, i}
		}
		i = pop + 1
	}
	panic(fmt.Sprintf("ChildAt(%v, %d): no such child of %v", k, idx, c))
}

// Child returns the cursor for n, which must be a direct child of c's Node.
//
// Child must not be called on the Root node (whose [Cursor.Node] returns nil).
func (c Cursor) Child(n ast.Node) Cursor {
	if c.index < 0 {
		panic("Cursor.Child called on Root node")
	}

	if false {
		// reference implementation
		for child := range c.Children() {
			if child.Node() == n {
				return child
			}
		}

	} else {
		// optimized implementation
		events := c.in.events
		for i := c.index + 1; events[i].index > i; i = events[i].index + 1 {
			if events[i].node == n {
				return Cursor{c.in, i}
			}
		}
	}
	panic(fmt.Sprintf("Child(%T): not a child of %v", n, c))
}

// NextSibling returns the cursor for the next sibling node in the same list
// (for example, of files, decls, specs, statements, fields, or expressions) as
// the current node. It returns (zero, false) if the node is the last node in
// the list, or is not part of a list.
//
// NextSibling must not be called on the Root node.
//
// See note at [Cursor.Children].
func (c Cursor) NextSibling() (Cursor, bool) {
	if c.index < 0 {
		panic("Cursor.NextSibling called on Root node")
	}

	events := c.in.events
	i := events[c.index].index + 1 // after corresponding pop
	if i < int32(len(events)) {
		if events[i].index > i { // push?
			return Cursor{c.in, i}, true
		}
	}
	return Cursor{}, false
}

// PrevSibling returns the cursor for the previous sibling node in the
// same list (for example, of files, decls, specs, statements, fields,
// or expressions) as the current node. It returns zero if the node is
// the first node in the list, or is not part of a list.
//
// It must not be called on the Root node.
//
// See note at [Cursor.Children].
func (c Cursor) PrevSibling() (Cursor, bool) {
	if c.index < 0 {
		panic("Cursor.PrevSibling called on Root node")
	}

	events := c.in.events
	i := c.index - 1
	if i >= 0 {
		if j := events[i].index; j < i { // pop?
			return Cursor{c.in, j}, true
		}
	}
	return Cursor{}, false
}

// FirstChild returns the first direct child of the current node,
// or zero if it has no children.
func (c Cursor) FirstChild() (Cursor, bool) {
	events := c.in.events
	i := c.index + 1                                   // i=0 if c is root
	if i < int32(len(events)) && events[i].index > i { // push?
		return Cursor{c.in, i}, true
	}
	return Cursor{}, false
}

// LastChild returns the last direct child of the current node,
// or zero if it has no children.
func (c Cursor) LastChild() (Cursor, bool) {
	events := c.in.events
	if c.index < 0 { // root?
		if len(events) > 0 {
			// return push of final event (a pop)
			return Cursor{c.in, events[len(events)-1].index}, true
		}
	} else {
		j := events[c.index].index - 1 // before corresponding pop
		// Inv: j == c.index if c has no children
		//  or  j is last child's pop.
		if j > c.index { // c has children
			return Cursor{c.in, events[j].index}, true
		}
	}
	return Cursor{}, false
}

// Children returns an iterator over the direct children of the
// current node, if any.
//
// When using Children, NextChild, and PrevChild, bear in mind that a
// Node's children may come from different fields, some of which may
// be lists of nodes without a distinguished intervening container
// such as [ast.BlockStmt].
//
// For example, [ast.CaseClause] has a field List of expressions and a
// field Body of statements, so the children of a CaseClause are a mix
// of expressions and statements. Other nodes that have "uncontained"
// list fields include:
//
//   - [ast.ValueSpec] (Names, Values)
//   - [ast.CompositeLit] (Type, Elts)
//   - [ast.IndexListExpr] (X, Indices)
//   - [ast.CallExpr] (Fun, Args)
//   - [ast.AssignStmt] (Lhs, Rhs)
//
// So, do not assume that the previous sibling of an ast.Stmt is also
// an ast.Stmt, or if it is, that they are executed sequentially,
// unless you have established that, say, its parent is a BlockStmt
// or its [Cursor.ParentEdge] is [edge.BlockStmt_List].
// For example, given "for S1; ; S2 {}", the predecessor of S2 is S1,
// even though they are not executed in sequence.
func (c Cursor) Children() iter.Seq[Cursor] {
	return func(yield func(Cursor) bool) {
		c, ok := c.FirstChild()
		for ok && yield(c) {
			c, ok = c.NextSibling()
		}
	}
}

// Contains reports whether c contains or is equal to c2.
//
// Both Cursors must belong to the same [Inspector];
// neither may be its Root node.
func (c Cursor) Contains(c2 Cursor) bool {
	if c.in != c2.in {
		panic("different inspectors")
	}
	events := c.in.events
	return c.index <= c2.index && events[c2.index].index <= events[c.index].index
}

// FindNode returns the cursor for node n if it belongs to the subtree
// rooted at c. It returns zero if n is not found.
func (c Cursor) FindNode(n ast.Node) (Cursor, bool) {

	// FindNode is equivalent to this code,
	// but more convenient and 15-20% faster:
	if false {
		for candidate := range c.Preorder(n) {
			if candidate.Node() == n {
				return candidate, true
			}
		}
		return Cursor{}, false
	}

	// TODO(adonovan): opt: should we assume Node.Pos is accurate
	// and combine type-based filtering with position filtering
	// like FindByPos?

	mask := maskOf([]ast.Node{n})
	events := c.in.events

	for i, limit := c.indices(); i < limit; i++ {
		ev := events[i]
		if ev.index > i { // push?
			if ev.typ&mask != 0 && ev.node == n {
				return Cursor{c.in, i}, true
			}
			pop := ev.index
			if events[pop].typ&mask == 0 {
				// Subtree does not contain type of n: skip.
				i = pop
			}
		}
	}
	return Cursor{}, false
}

// FindByPos returns the cursor for the innermost node n in the tree
// rooted at c such that n.Pos() <= start && end <= n.End().
// (For an *ast.File, it uses the bounds n.FileStart-n.FileEnd.)
//
// It returns zero if none is found.
// Precondition: start <= end.
//
// See also [astutil.PathEnclosingInterval], which
// tolerates adjoining whitespace.
func (c Cursor) FindByPos(start, end token.Pos) (Cursor, bool) {
	if end < start {
		panic("end < start")
	}
	events := c.in.events

	// This algorithm could be implemented using c.Inspect,
	// but it is about 2.5x slower.

	best := int32(-1) // push index of latest (=innermost) node containing range
	for i, limit := c.indices(); i < limit; i++ {
		ev := events[i]
		if ev.index > i { // push?
			n := ev.node
			var nodeEnd token.Pos
			if file, ok := n.(*ast.File); ok {
				nodeEnd = file.FileEnd
				// Note: files may be out of Pos order.
				if file.FileStart > start {
					i = ev.index // disjoint, after; skip to next file
					continue
				}
			} else {
				nodeEnd = n.End()
				if n.Pos() > start {
					break // disjoint, after; stop
				}
			}
			// Inv: node.{Pos,FileStart} <= start
			if end <= nodeEnd {
				// node fully contains target range
				best = i
			} else if nodeEnd < start {
				i = ev.index // disjoint, before; skip forward
			}
		}
	}
	if best >= 0 {
		return Cursor{c.in, best}, true
	}
	return Cursor{}, false
}
