// Copyright 2020-2025 Buf Technologies, Inc.
//
// 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

import (
	"fmt"
	"math"
	"strings"
)

// ValueNode is an AST node that represents a literal value.
//
// It also includes references (e.g. IdentifierValueNode), which can be
// used as values in some contexts, such as describing the default value
// for a field, which can refer to an enum value.
//
// This also allows NoSourceNode to be used in place of a real value node
// for some usages.
type ValueNode interface {
	Node
	// Value returns a Go representation of the value. For scalars, this
	// will be a string, int64, uint64, float64, or bool. This could also
	// be an Identifier (e.g. IdentValueNodes). It can also be a composite
	// literal:
	//   * For array literals, the type returned will be []ValueNode
	//   * For message literals, the type returned will be []*MessageFieldNode
	//
	// If the ValueNode is a NoSourceNode, indicating that there is no actual
	// source code (and thus not AST information), then this method always
	// returns nil.
	Value() any
}

var _ ValueNode = (*IdentNode)(nil)
var _ ValueNode = (*CompoundIdentNode)(nil)
var _ ValueNode = (*StringLiteralNode)(nil)
var _ ValueNode = (*CompoundStringLiteralNode)(nil)
var _ ValueNode = (*UintLiteralNode)(nil)
var _ ValueNode = (*NegativeIntLiteralNode)(nil)
var _ ValueNode = (*FloatLiteralNode)(nil)
var _ ValueNode = (*SpecialFloatLiteralNode)(nil)
var _ ValueNode = (*SignedFloatLiteralNode)(nil)
var _ ValueNode = (*ArrayLiteralNode)(nil)
var _ ValueNode = (*MessageLiteralNode)(nil)
var _ ValueNode = (*NoSourceNode)(nil)

// StringValueNode is an AST node that represents a string literal.
// Such a node can be a single literal (*StringLiteralNode) or a
// concatenation of multiple literals (*CompoundStringLiteralNode).
type StringValueNode interface {
	ValueNode
	AsString() string
}

var _ StringValueNode = (*StringLiteralNode)(nil)
var _ StringValueNode = (*CompoundStringLiteralNode)(nil)

// StringLiteralNode represents a simple string literal. Example:
//
//	"proto2"
type StringLiteralNode struct {
	terminalNode
	// Val is the actual string value that the literal indicates.
	Val string
}

// NewStringLiteralNode creates a new *StringLiteralNode with the given val.
func NewStringLiteralNode(val string, tok Token) *StringLiteralNode {
	return &StringLiteralNode{
		terminalNode: tok.asTerminalNode(),
		Val:          val,
	}
}

func (n *StringLiteralNode) Value() any {
	return n.AsString()
}

func (n *StringLiteralNode) AsString() string {
	return n.Val
}

// CompoundStringLiteralNode represents a compound string literal, which is
// the concatenaton of adjacent string literals. Example:
//
//	"this "  "is"   " all one "   "string"
type CompoundStringLiteralNode struct {
	compositeNode
	Val string
}

// NewCompoundLiteralStringNode creates a new *CompoundStringLiteralNode that
// consists of the given string components. The components argument may not be
// empty.
func NewCompoundLiteralStringNode(components ...*StringLiteralNode) *CompoundStringLiteralNode {
	if len(components) == 0 {
		panic("must have at least one component")
	}
	children := make([]Node, len(components))
	var b strings.Builder
	for i, comp := range components {
		children[i] = comp
		b.WriteString(comp.Val)
	}
	return &CompoundStringLiteralNode{
		compositeNode: compositeNode{
			children: children,
		},
		Val: b.String(),
	}
}

func (n *CompoundStringLiteralNode) Value() any {
	return n.AsString()
}

func (n *CompoundStringLiteralNode) AsString() string {
	return n.Val
}

// IntValueNode is an AST node that represents an integer literal. If
// an integer literal is too large for an int64 (or uint64 for
// positive literals), it is represented instead by a FloatValueNode.
type IntValueNode interface {
	ValueNode
	AsInt64() (int64, bool)
	AsUint64() (uint64, bool)
}

// AsInt32 range checks the given int value and returns its value is
// in the range or 0, false if it is outside the range.
func AsInt32(n IntValueNode, minVal, maxVal int32) (int32, bool) {
	i, ok := n.AsInt64()
	if !ok {
		return 0, false
	}
	if i < int64(minVal) || i > int64(maxVal) {
		return 0, false
	}
	return int32(i), true
}

var _ IntValueNode = (*UintLiteralNode)(nil)
var _ IntValueNode = (*NegativeIntLiteralNode)(nil)

// UintLiteralNode represents a simple integer literal with no sign character.
type UintLiteralNode struct {
	terminalNode
	// Val is the numeric value indicated by the literal
	Val uint64
}

// NewUintLiteralNode creates a new *UintLiteralNode with the given val.
func NewUintLiteralNode(val uint64, tok Token) *UintLiteralNode {
	return &UintLiteralNode{
		terminalNode: tok.asTerminalNode(),
		Val:          val,
	}
}

func (n *UintLiteralNode) Value() any {
	return n.Val
}

func (n *UintLiteralNode) AsInt64() (int64, bool) {
	if n.Val > math.MaxInt64 {
		return 0, false
	}
	return int64(n.Val), true
}

func (n *UintLiteralNode) AsUint64() (uint64, bool) {
	return n.Val, true
}

func (n *UintLiteralNode) AsFloat() float64 {
	return float64(n.Val)
}

// NegativeIntLiteralNode represents an integer literal with a negative (-) sign.
type NegativeIntLiteralNode struct {
	compositeNode
	Minus *RuneNode
	Uint  *UintLiteralNode
	Val   int64
}

// NewNegativeIntLiteralNode creates a new *NegativeIntLiteralNode. Both
// arguments must be non-nil.
func NewNegativeIntLiteralNode(sign *RuneNode, i *UintLiteralNode) *NegativeIntLiteralNode {
	if sign == nil {
		panic("sign is nil")
	}
	if i == nil {
		panic("i is nil")
	}
	children := []Node{sign, i}
	return &NegativeIntLiteralNode{
		compositeNode: compositeNode{
			children: children,
		},
		Minus: sign,
		Uint:  i,
		Val:   -int64(i.Val),
	}
}

func (n *NegativeIntLiteralNode) Value() any {
	return n.Val
}

func (n *NegativeIntLiteralNode) AsInt64() (int64, bool) {
	return n.Val, true
}

func (n *NegativeIntLiteralNode) AsUint64() (uint64, bool) {
	if n.Val < 0 {
		return 0, false
	}
	return uint64(n.Val), true
}

// FloatValueNode is an AST node that represents a numeric literal with
// a floating point, in scientific notation, or too large to fit in an
// int64 or uint64.
type FloatValueNode interface {
	ValueNode
	AsFloat() float64
}

var _ FloatValueNode = (*FloatLiteralNode)(nil)
var _ FloatValueNode = (*SpecialFloatLiteralNode)(nil)
var _ FloatValueNode = (*UintLiteralNode)(nil)

// FloatLiteralNode represents a floating point numeric literal.
type FloatLiteralNode struct {
	terminalNode
	// Val is the numeric value indicated by the literal
	Val float64
}

// NewFloatLiteralNode creates a new *FloatLiteralNode with the given val.
func NewFloatLiteralNode(val float64, tok Token) *FloatLiteralNode {
	return &FloatLiteralNode{
		terminalNode: tok.asTerminalNode(),
		Val:          val,
	}
}

func (n *FloatLiteralNode) Value() any {
	return n.AsFloat()
}

func (n *FloatLiteralNode) AsFloat() float64 {
	return n.Val
}

// SpecialFloatLiteralNode represents a special floating point numeric literal
// for "inf" and "nan" values.
type SpecialFloatLiteralNode struct {
	*KeywordNode
	Val float64
}

// NewSpecialFloatLiteralNode returns a new *SpecialFloatLiteralNode for the
// given keyword. The given keyword should be "inf", "infinity", or "nan"
// in any case.
func NewSpecialFloatLiteralNode(name *KeywordNode) *SpecialFloatLiteralNode {
	var f float64
	switch strings.ToLower(name.Val) {
	case "inf", "infinity":
		f = math.Inf(1)
	default:
		f = math.NaN()
	}
	return &SpecialFloatLiteralNode{
		KeywordNode: name,
		Val:         f,
	}
}

func (n *SpecialFloatLiteralNode) Value() any {
	return n.AsFloat()
}

func (n *SpecialFloatLiteralNode) AsFloat() float64 {
	return n.Val
}

// SignedFloatLiteralNode represents a signed floating point number.
type SignedFloatLiteralNode struct {
	compositeNode
	Sign  *RuneNode
	Float FloatValueNode
	Val   float64
}

// NewSignedFloatLiteralNode creates a new *SignedFloatLiteralNode. Both
// arguments must be non-nil.
func NewSignedFloatLiteralNode(sign *RuneNode, f FloatValueNode) *SignedFloatLiteralNode {
	if sign == nil {
		panic("sign is nil")
	}
	if f == nil {
		panic("f is nil")
	}
	children := []Node{sign, f}
	val := f.AsFloat()
	if sign.Rune == '-' {
		val = -val
	}
	return &SignedFloatLiteralNode{
		compositeNode: compositeNode{
			children: children,
		},
		Sign:  sign,
		Float: f,
		Val:   val,
	}
}

func (n *SignedFloatLiteralNode) Value() any {
	return n.Val
}

func (n *SignedFloatLiteralNode) AsFloat() float64 {
	return n.Val
}

// ArrayLiteralNode represents an array literal, which is only allowed inside of
// a MessageLiteralNode, to indicate values for a repeated field. Example:
//
//	["foo", "bar", "baz"]
type ArrayLiteralNode struct {
	compositeNode
	OpenBracket *RuneNode
	Elements    []ValueNode
	// Commas represent the separating ',' characters between elements. The
	// length of this slice must be exactly len(Elements)-1, with each item
	// in Elements having a corresponding item in this slice *except the last*
	// (since a trailing comma is not allowed).
	Commas       []*RuneNode
	CloseBracket *RuneNode
}

// NewArrayLiteralNode creates a new *ArrayLiteralNode. The openBracket and
// closeBracket args must be non-nil and represent the "[" and "]" runes that
// surround the array values. The given commas arg must have a length that is
// one less than the length of the vals arg. However, vals may be empty, in
// which case commas must also be empty.
func NewArrayLiteralNode(openBracket *RuneNode, vals []ValueNode, commas []*RuneNode, closeBracket *RuneNode) *ArrayLiteralNode {
	if openBracket == nil {
		panic("openBracket is nil")
	}
	if closeBracket == nil {
		panic("closeBracket is nil")
	}
	if len(vals) == 0 && len(commas) != 0 {
		panic("vals is empty but commas is not")
	}
	if len(vals) > 0 && len(commas) != len(vals)-1 {
		panic(fmt.Sprintf("%d vals requires %d commas, not %d", len(vals), len(vals)-1, len(commas)))
	}
	children := make([]Node, 0, len(vals)*2+1)
	children = append(children, openBracket)
	for i, val := range vals {
		if i > 0 {
			if commas[i-1] == nil {
				panic(fmt.Sprintf("commas[%d] is nil", i-1))
			}
			children = append(children, commas[i-1])
		}
		if val == nil {
			panic(fmt.Sprintf("vals[%d] is nil", i))
		}
		children = append(children, val)
	}
	children = append(children, closeBracket)

	return &ArrayLiteralNode{
		compositeNode: compositeNode{
			children: children,
		},
		OpenBracket:  openBracket,
		Elements:     vals,
		Commas:       commas,
		CloseBracket: closeBracket,
	}
}

func (n *ArrayLiteralNode) Value() any {
	return n.Elements
}

// MessageLiteralNode represents a message literal, which is compatible with the
// protobuf text format and can be used for custom options with message types.
// Example:
//
//	{ foo:1 foo:2 foo:3 bar:<name:"abc" id:123> }
type MessageLiteralNode struct {
	compositeNode
	Open     *RuneNode // should be '{' or '<'
	Elements []*MessageFieldNode
	// Separator characters between elements, which can be either ','
	// or ';' if present. This slice must be exactly len(Elements) in
	// length, with each item in Elements having one corresponding item
	// in Seps. Separators in message literals are optional, so a given
	// item in this slice may be nil to indicate absence of a separator.
	Seps  []*RuneNode
	Close *RuneNode // should be '}' or '>', depending on Open
}

// NewMessageLiteralNode creates a new *MessageLiteralNode. The openSym and
// closeSym runes must not be nil and should be "{" and "}" or "<" and ">".
//
// Unlike separators (dots and commas) used for other AST nodes that represent
// a list of elements, the seps arg must be the SAME length as vals, and it may
// contain nil values to indicate absence of a separator (in fact, it could be
// all nils).
func NewMessageLiteralNode(openSym *RuneNode, vals []*MessageFieldNode, seps []*RuneNode, closeSym *RuneNode) *MessageLiteralNode {
	if openSym == nil {
		panic("openSym is nil")
	}
	if closeSym == nil {
		panic("closeSym is nil")
	}
	if len(seps) != len(vals) {
		panic(fmt.Sprintf("%d vals requires %d commas, not %d", len(vals), len(vals), len(seps)))
	}
	numChildren := len(vals) + 2
	for _, sep := range seps {
		if sep != nil {
			numChildren++
		}
	}
	children := make([]Node, 0, numChildren)
	children = append(children, openSym)
	for i, val := range vals {
		if val == nil {
			panic(fmt.Sprintf("vals[%d] is nil", i))
		}
		children = append(children, val)
		if seps[i] != nil {
			children = append(children, seps[i])
		}
	}
	children = append(children, closeSym)

	return &MessageLiteralNode{
		compositeNode: compositeNode{
			children: children,
		},
		Open:     openSym,
		Elements: vals,
		Seps:     seps,
		Close:    closeSym,
	}
}

func (n *MessageLiteralNode) Value() any {
	return n.Elements
}

// MessageFieldNode represents a single field (name and value) inside of a
// message literal. Example:
//
//	foo:"bar"
type MessageFieldNode struct {
	compositeNode
	Name *FieldReferenceNode
	// Sep represents the ':' separator between the name and value. If
	// the value is a message or list literal (and thus starts with '<',
	// '{', or '['), then the separator may be omitted and this field may
	// be nil.
	Sep *RuneNode
	Val ValueNode
}

// NewMessageFieldNode creates a new *MessageFieldNode. All args except sep
// must be non-nil.
func NewMessageFieldNode(name *FieldReferenceNode, sep *RuneNode, val ValueNode) *MessageFieldNode {
	if name == nil {
		panic("name is nil")
	}
	if val == nil {
		panic("val is nil")
	}
	numChildren := 2
	if sep != nil {
		numChildren++
	}
	children := make([]Node, 0, numChildren)
	children = append(children, name)
	if sep != nil {
		children = append(children, sep)
	}
	children = append(children, val)

	return &MessageFieldNode{
		compositeNode: compositeNode{
			children: children,
		},
		Name: name,
		Sep:  sep,
		Val:  val,
	}
}
