// 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 edge defines identifiers for each field of an ast.Node
// struct type that refers to another Node.
package edge

import (
	"fmt"
	"go/ast"
	"reflect"
)

// A Kind describes a field of an [ast.Node] struct.
type Kind uint8

// String returns a description of the edge kind.
func (k Kind) String() string {
	if k == Invalid {
		return "<invalid>"
	}
	info := fieldInfos[k]
	return fmt.Sprintf("%v.%s", info.nodeType.Elem().Name(), info.name)
}

// NodeType returns the pointer-to-struct type of the ast.Node implementation.
func (k Kind) NodeType() reflect.Type { return fieldInfos[k].nodeType }

// FieldName returns the name of the field.
func (k Kind) FieldName() string { return fieldInfos[k].name }

// FieldType returns the declared type of the field.
func (k Kind) FieldType() reflect.Type { return fieldInfos[k].fieldType }

// Get returns the direct child of n identified by (k, idx).
// n's type must match k.NodeType().
// idx must be a valid slice index, or -1 for a non-slice.
func (k Kind) Get(n ast.Node, idx int) ast.Node {
	if k.NodeType() != reflect.TypeOf(n) {
		panic(fmt.Sprintf("%v.Get(%T): invalid node type", k, n))
	}
	v := reflect.ValueOf(n).Elem().Field(fieldInfos[k].index)

	if v.Kind() == reflect.Slice {
		v = v.Index(idx) // asserts valid idx
	} else if idx != -1 {
		panic(fmt.Sprintf("%v, Get(%T, %d): cannot index non-slice", v, n, idx))
	}

	out, _ := v.Interface().(ast.Node) // may be nil
	return out
}

// Each [Kind] is named Type_Field, where Type is the
// [ast.Node] struct type and Field is the name of the field
const (
	Invalid Kind = iota // for nodes at the root of the traversal

	// As of Go1.26 these kinds are sorted alphabetically, but
	// numbering must be stable, so any new addition of const should
	// use a new value (be added at the end of the list).

	ArrayType_Elt
	ArrayType_Len
	AssignStmt_Lhs
	AssignStmt_Rhs
	BinaryExpr_X
	BinaryExpr_Y
	BlockStmt_List
	BranchStmt_Label
	CallExpr_Args
	CallExpr_Fun
	CaseClause_Body
	CaseClause_List
	ChanType_Value
	CommClause_Body
	CommClause_Comm
	CommentGroup_List
	CompositeLit_Elts
	CompositeLit_Type
	DeclStmt_Decl
	DeferStmt_Call
	Ellipsis_Elt
	ExprStmt_X
	FieldList_List
	Field_Comment
	Field_Doc
	Field_Names
	Field_Tag
	Field_Type
	File_Decls
	File_Doc
	File_Name
	ForStmt_Body
	ForStmt_Cond
	ForStmt_Init
	ForStmt_Post
	FuncDecl_Body
	FuncDecl_Doc
	FuncDecl_Name
	FuncDecl_Recv
	FuncDecl_Type
	FuncLit_Body
	FuncLit_Type
	FuncType_Params
	FuncType_Results
	FuncType_TypeParams
	GenDecl_Doc
	GenDecl_Specs
	GoStmt_Call
	IfStmt_Body
	IfStmt_Cond
	IfStmt_Else
	IfStmt_Init
	ImportSpec_Comment
	ImportSpec_Doc
	ImportSpec_Name
	ImportSpec_Path
	IncDecStmt_X
	IndexExpr_Index
	IndexExpr_X
	IndexListExpr_Indices
	IndexListExpr_X
	InterfaceType_Methods
	KeyValueExpr_Key
	KeyValueExpr_Value
	LabeledStmt_Label
	LabeledStmt_Stmt
	MapType_Key
	MapType_Value
	ParenExpr_X
	RangeStmt_Body
	RangeStmt_Key
	RangeStmt_Value
	RangeStmt_X
	ReturnStmt_Results
	SelectStmt_Body
	SelectorExpr_Sel
	SelectorExpr_X
	SendStmt_Chan
	SendStmt_Value
	SliceExpr_High
	SliceExpr_Low
	SliceExpr_Max
	SliceExpr_X
	StarExpr_X
	StructType_Fields
	SwitchStmt_Body
	SwitchStmt_Init
	SwitchStmt_Tag
	TypeAssertExpr_Type
	TypeAssertExpr_X
	TypeSpec_Comment
	TypeSpec_Doc
	TypeSpec_Name
	TypeSpec_Type
	TypeSpec_TypeParams
	TypeSwitchStmt_Assign
	TypeSwitchStmt_Body
	TypeSwitchStmt_Init
	UnaryExpr_X
	ValueSpec_Comment
	ValueSpec_Doc
	ValueSpec_Names
	ValueSpec_Type
	ValueSpec_Values

	maxKind
)

// Assert that the encoding fits in 7 bits,
// as the inspector relies on this.
// (We are currently at 104.)
var _ = [1 << 7]struct{}{}[maxKind]

type fieldInfo struct {
	nodeType  reflect.Type // pointer-to-struct type of ast.Node implementation
	name      string
	index     int
	fieldType reflect.Type
}

func info[N ast.Node](fieldName string) fieldInfo {
	nodePtrType := reflect.TypeFor[N]()
	f, ok := nodePtrType.Elem().FieldByName(fieldName)
	if !ok {
		panic(fieldName)
	}
	return fieldInfo{nodePtrType, fieldName, f.Index[0], f.Type}
}

var fieldInfos = [...]fieldInfo{
	Invalid:               {},
	ArrayType_Elt:         info[*ast.ArrayType]("Elt"),
	ArrayType_Len:         info[*ast.ArrayType]("Len"),
	AssignStmt_Lhs:        info[*ast.AssignStmt]("Lhs"),
	AssignStmt_Rhs:        info[*ast.AssignStmt]("Rhs"),
	BinaryExpr_X:          info[*ast.BinaryExpr]("X"),
	BinaryExpr_Y:          info[*ast.BinaryExpr]("Y"),
	BlockStmt_List:        info[*ast.BlockStmt]("List"),
	BranchStmt_Label:      info[*ast.BranchStmt]("Label"),
	CallExpr_Args:         info[*ast.CallExpr]("Args"),
	CallExpr_Fun:          info[*ast.CallExpr]("Fun"),
	CaseClause_Body:       info[*ast.CaseClause]("Body"),
	CaseClause_List:       info[*ast.CaseClause]("List"),
	ChanType_Value:        info[*ast.ChanType]("Value"),
	CommClause_Body:       info[*ast.CommClause]("Body"),
	CommClause_Comm:       info[*ast.CommClause]("Comm"),
	CommentGroup_List:     info[*ast.CommentGroup]("List"),
	CompositeLit_Elts:     info[*ast.CompositeLit]("Elts"),
	CompositeLit_Type:     info[*ast.CompositeLit]("Type"),
	DeclStmt_Decl:         info[*ast.DeclStmt]("Decl"),
	DeferStmt_Call:        info[*ast.DeferStmt]("Call"),
	Ellipsis_Elt:          info[*ast.Ellipsis]("Elt"),
	ExprStmt_X:            info[*ast.ExprStmt]("X"),
	FieldList_List:        info[*ast.FieldList]("List"),
	Field_Comment:         info[*ast.Field]("Comment"),
	Field_Doc:             info[*ast.Field]("Doc"),
	Field_Names:           info[*ast.Field]("Names"),
	Field_Tag:             info[*ast.Field]("Tag"),
	Field_Type:            info[*ast.Field]("Type"),
	File_Decls:            info[*ast.File]("Decls"),
	File_Doc:              info[*ast.File]("Doc"),
	File_Name:             info[*ast.File]("Name"),
	ForStmt_Body:          info[*ast.ForStmt]("Body"),
	ForStmt_Cond:          info[*ast.ForStmt]("Cond"),
	ForStmt_Init:          info[*ast.ForStmt]("Init"),
	ForStmt_Post:          info[*ast.ForStmt]("Post"),
	FuncDecl_Body:         info[*ast.FuncDecl]("Body"),
	FuncDecl_Doc:          info[*ast.FuncDecl]("Doc"),
	FuncDecl_Name:         info[*ast.FuncDecl]("Name"),
	FuncDecl_Recv:         info[*ast.FuncDecl]("Recv"),
	FuncDecl_Type:         info[*ast.FuncDecl]("Type"),
	FuncLit_Body:          info[*ast.FuncLit]("Body"),
	FuncLit_Type:          info[*ast.FuncLit]("Type"),
	FuncType_Params:       info[*ast.FuncType]("Params"),
	FuncType_Results:      info[*ast.FuncType]("Results"),
	FuncType_TypeParams:   info[*ast.FuncType]("TypeParams"),
	GenDecl_Doc:           info[*ast.GenDecl]("Doc"),
	GenDecl_Specs:         info[*ast.GenDecl]("Specs"),
	GoStmt_Call:           info[*ast.GoStmt]("Call"),
	IfStmt_Body:           info[*ast.IfStmt]("Body"),
	IfStmt_Cond:           info[*ast.IfStmt]("Cond"),
	IfStmt_Else:           info[*ast.IfStmt]("Else"),
	IfStmt_Init:           info[*ast.IfStmt]("Init"),
	ImportSpec_Comment:    info[*ast.ImportSpec]("Comment"),
	ImportSpec_Doc:        info[*ast.ImportSpec]("Doc"),
	ImportSpec_Name:       info[*ast.ImportSpec]("Name"),
	ImportSpec_Path:       info[*ast.ImportSpec]("Path"),
	IncDecStmt_X:          info[*ast.IncDecStmt]("X"),
	IndexExpr_Index:       info[*ast.IndexExpr]("Index"),
	IndexExpr_X:           info[*ast.IndexExpr]("X"),
	IndexListExpr_Indices: info[*ast.IndexListExpr]("Indices"),
	IndexListExpr_X:       info[*ast.IndexListExpr]("X"),
	InterfaceType_Methods: info[*ast.InterfaceType]("Methods"),
	KeyValueExpr_Key:      info[*ast.KeyValueExpr]("Key"),
	KeyValueExpr_Value:    info[*ast.KeyValueExpr]("Value"),
	LabeledStmt_Label:     info[*ast.LabeledStmt]("Label"),
	LabeledStmt_Stmt:      info[*ast.LabeledStmt]("Stmt"),
	MapType_Key:           info[*ast.MapType]("Key"),
	MapType_Value:         info[*ast.MapType]("Value"),
	ParenExpr_X:           info[*ast.ParenExpr]("X"),
	RangeStmt_Body:        info[*ast.RangeStmt]("Body"),
	RangeStmt_Key:         info[*ast.RangeStmt]("Key"),
	RangeStmt_Value:       info[*ast.RangeStmt]("Value"),
	RangeStmt_X:           info[*ast.RangeStmt]("X"),
	ReturnStmt_Results:    info[*ast.ReturnStmt]("Results"),
	SelectStmt_Body:       info[*ast.SelectStmt]("Body"),
	SelectorExpr_Sel:      info[*ast.SelectorExpr]("Sel"),
	SelectorExpr_X:        info[*ast.SelectorExpr]("X"),
	SendStmt_Chan:         info[*ast.SendStmt]("Chan"),
	SendStmt_Value:        info[*ast.SendStmt]("Value"),
	SliceExpr_High:        info[*ast.SliceExpr]("High"),
	SliceExpr_Low:         info[*ast.SliceExpr]("Low"),
	SliceExpr_Max:         info[*ast.SliceExpr]("Max"),
	SliceExpr_X:           info[*ast.SliceExpr]("X"),
	StarExpr_X:            info[*ast.StarExpr]("X"),
	StructType_Fields:     info[*ast.StructType]("Fields"),
	SwitchStmt_Body:       info[*ast.SwitchStmt]("Body"),
	SwitchStmt_Init:       info[*ast.SwitchStmt]("Init"),
	SwitchStmt_Tag:        info[*ast.SwitchStmt]("Tag"),
	TypeAssertExpr_Type:   info[*ast.TypeAssertExpr]("Type"),
	TypeAssertExpr_X:      info[*ast.TypeAssertExpr]("X"),
	TypeSpec_Comment:      info[*ast.TypeSpec]("Comment"),
	TypeSpec_Doc:          info[*ast.TypeSpec]("Doc"),
	TypeSpec_Name:         info[*ast.TypeSpec]("Name"),
	TypeSpec_Type:         info[*ast.TypeSpec]("Type"),
	TypeSpec_TypeParams:   info[*ast.TypeSpec]("TypeParams"),
	TypeSwitchStmt_Assign: info[*ast.TypeSwitchStmt]("Assign"),
	TypeSwitchStmt_Body:   info[*ast.TypeSwitchStmt]("Body"),
	TypeSwitchStmt_Init:   info[*ast.TypeSwitchStmt]("Init"),
	UnaryExpr_X:           info[*ast.UnaryExpr]("X"),
	ValueSpec_Comment:     info[*ast.ValueSpec]("Comment"),
	ValueSpec_Doc:         info[*ast.ValueSpec]("Doc"),
	ValueSpec_Names:       info[*ast.ValueSpec]("Names"),
	ValueSpec_Type:        info[*ast.ValueSpec]("Type"),
	ValueSpec_Values:      info[*ast.ValueSpec]("Values"),
}
