// Copyright 2020 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.

//go:build !goexperiment.jsonv2 || !go1.25

package jsontext

import (
	"bytes"
	"errors"
	"math"
	"strconv"

	"github.com/go-json-experiment/json/internal/jsonflags"
	"github.com/go-json-experiment/json/internal/jsonwire"
)

// NOTE: Token is analogous to v1 json.Token.

const (
	maxInt64  = math.MaxInt64
	minInt64  = math.MinInt64
	maxUint64 = math.MaxUint64
	minUint64 = 0 // for consistency and readability purposes

	invalidTokenPanic = "invalid jsontext.Token; it has been voided by a subsequent json.Decoder call"
)

var errInvalidToken = errors.New("invalid jsontext.Token")

// Token represents a lexical JSON token, which may be one of the following:
//   - a JSON literal (i.e., null, true, or false)
//   - a JSON string (e.g., "hello, world!")
//   - a JSON number (e.g., 123.456)
//   - a begin or end delimiter for a JSON object (i.e., { or } )
//   - a begin or end delimiter for a JSON array (i.e., [ or ] )
//
// A Token cannot represent entire array or object values, while a [Value] can.
// There is no Token to represent commas and colons since
// these structural tokens can be inferred from the surrounding context.
//
// A Token stores data in one of two forms:
//
//   - As raw JSON text: backed by the internal buffer of the [Decoder]
//     and only ever produced by [Decoder.ReadToken].
//     Such a token is only valid until the next call to any method on that
//     [Decoder] (e.g., [Decoder.PeekKind], [Decoder.ReadToken],
//     [Decoder.ReadValue], or [Decoder.SkipValue]).
//     Call [Token.Clone] to copy the raw text into an independent allocation
//     that persists beyond subsequent [Decoder] calls.
//
//   - As a typed Go value: a self-contained representation produced by
//     the constructor functions (e.g., [String], [Int], [Uint], [Float]).
//     Such tokens are valid indefinitely and do not need to be cloned.
type Token struct {
	nonComparable

	// Tokens can exist in either a "raw" or an "exact" form.
	// Tokens produced by the Decoder are in the "raw" form.
	// Tokens returned by constructors are usually in the "exact" form.
	// The Encoder accepts Tokens in either the "raw" or "exact" form.
	//
	// The following chart shows the possible values for each Token type:
	//	╔══════════════════╦════════════╤════════════╤════════════╗
	//	║ Token type       ║ raw field  │ str field  │ num field  ║
	//	╠══════════════════╬════════════╪════════════╪════════════╣
	//	║ null   (raw)     ║ "null"     │ ""         │ 0          ║
	//	║ false  (raw)     ║ "false"    │ ""         │ 0          ║
	//	║ true   (raw)     ║ "true"     │ ""         │ 0          ║
	//	║ string (raw)     ║ non-empty  │ ""         │ offset     ║
	//	║ string (string)  ║ nil        │ non-empty  │ 0          ║
	//	║ number (raw)     ║ non-empty  │ ""         │ offset     ║
	//	║ number (float32) ║ nil        │ "F"        │ non-zero   ║
	//	║ number (float64) ║ nil        │ "f"        │ non-zero   ║
	//	║ number (int64)   ║ nil        │ "i"        │ non-zero   ║
	//	║ number (uint64)  ║ nil        │ "u"        │ non-zero   ║
	//	║ object (delim)   ║ "{" or "}" │ ""         │ 0          ║
	//	║ array  (delim)   ║ "[" or "]" │ ""         │ 0          ║
	//	╚══════════════════╩════════════╧════════════╧════════════╝
	//
	// Notes:
	//   - For tokens stored in "raw" form, the num field contains the
	//     absolute offset determined by raw.previousOffsetStart().
	//     The buffer itself is stored in raw.previousBuffer().
	//   - JSON literals and structural characters are always in the "raw" form.
	//   - JSON strings and numbers can be in either "raw" or "exact" forms.
	//   - The exact zero value of JSON strings and numbers in the "exact" forms
	//     have ambiguous representation. Thus, they are always represented
	//     in the "raw" form.

	// raw contains a reference to the raw decode buffer.
	// If non-nil, then its value takes precedence over str and num.
	// It is only valid if num == raw.previousOffsetStart().
	raw *decodeBuffer

	// str is the unescaped JSON string if num is zero.
	// Otherwise, it is "F", "f", "i", or "u" if num should be interpreted
	// as a float32, float64, int64, or uint64, respectively.
	str string

	// num is a float32, float64, int64, or uint64 stored as a uint64 value.
	// For floating-point values, it stores the raw IEEE-754 bit-pattern.
	// It is non-zero for any JSON number in the "exact" form.
	num uint64
}

// TODO: Does representing 1-byte delimiters as *decodeBuffer cause performance issues?

var (
	Null  Token = rawToken("null")
	False Token = rawToken("false")
	True  Token = rawToken("true")

	BeginObject Token = rawToken("{")
	EndObject   Token = rawToken("}")
	BeginArray  Token = rawToken("[")
	EndArray    Token = rawToken("]")

	zeroString Token = rawToken(`""`)
	zeroNumber Token = rawToken(`0`)

	nanString  Token = String("NaN")
	pinfString Token = String("Infinity")
	ninfString Token = String("-Infinity")
)

func rawToken(s string) Token {
	return Token{raw: &decodeBuffer{buf: []byte(s), prevStart: 0, prevEnd: len(s)}}
}

// Bool constructs a Token representing a JSON boolean.
func Bool(b bool) Token {
	if b {
		return True
	}
	return False
}

// String constructs a Token representing a JSON string.
// The provided string should contain valid UTF-8, otherwise invalid characters
// may be mangled as the Unicode replacement character.
func String(s string) Token {
	if len(s) == 0 {
		return zeroString
	}
	return Token{str: s}
}

// Float32 constructs a Token representing a JSON number as
// a 32-bit floating-point number formatted according to
// ECMA-262, 10th edition, section 7.1.12.1,
// with the exception that -0 is still formatted as -0.
// The values NaN, +Inf, and -Inf will be represented
// as a JSON string with the values "NaN", "Infinity", and "-Infinity".
//
// Note that most JSON libraries and standards assume that JSON numbers
// are 64-bit floating-point numbers. Use of 32-bit precision should
// only be used if the corresponding decoder knows that
// this JSON number token is expected to only have 32-bit precision.
// For all other situations, prefer using the [Float] constructor instead.
func Float32(n float32) Token {
	if n != 0 && !math.IsNaN(float64(n)) && !math.IsInf(float64(n), 0) {
		return Token{str: "F", num: uint64(math.Float32bits(n))}
	}
	return Float(float64(n)) // handles ±0, NaN, and ±Inf
}

// Float constructs a Token representing a JSON number as
// a 64-bit floating-point number formatted according to
// ECMA-262, 10th edition, section 7.1.12.1 and RFC 8785, section 3.2.2.3.
// with the exception that -0 is still formatted as -0.
// The values NaN, +Inf, and -Inf will be represented
// as a JSON string with the values "NaN", "Infinity", and "-Infinity".
func Float(n float64) Token {
	switch {
	case math.Float64bits(n) == 0:
		return zeroNumber
	case math.IsNaN(n):
		return nanString
	case math.IsInf(n, +1):
		return pinfString
	case math.IsInf(n, -1):
		return ninfString
	}
	return Token{str: "f", num: math.Float64bits(n)}
}

// Int constructs a Token representing a JSON number from an int64.
func Int(n int64) Token {
	if n == 0 {
		return zeroNumber
	}
	return Token{str: "i", num: uint64(n)}
}

// Uint constructs a Token representing a JSON number from a uint64.
func Uint(n uint64) Token {
	if n == 0 {
		return zeroNumber
	}
	return Token{str: "u", num: uint64(n)}
}

// Clone returns a copy of the token with a value that is not backed by the
// [Decoder] buffer and therefore remains valid past subsequent [Decoder] calls.
// It has no effect on tokens produced by constructor functions,
// since those are already self-contained.
func (t Token) Clone() Token {
	// TODO: Allow caller to avoid any allocations?
	if raw := t.raw; raw != nil {
		// Avoid copying globals.
		if t.raw.prevStart == 0 {
			switch t.raw {
			case Null.raw:
				return Null
			case False.raw:
				return False
			case True.raw:
				return True
			case BeginObject.raw:
				return BeginObject
			case EndObject.raw:
				return EndObject
			case BeginArray.raw:
				return BeginArray
			case EndArray.raw:
				return EndArray
			}
		}

		// This only ever needs to clone strings and numbers.
		if uint64(raw.previousOffsetStart()) != t.num {
			panic(invalidTokenPanic)
		}
		buf := bytes.Clone(raw.previousBuffer())
		return Token{raw: &decodeBuffer{buf: buf, prevStart: 0, prevEnd: len(buf)}}
	}
	return t
}

// Bool returns the value for a JSON boolean.
// It panics if the token kind is not a JSON boolean.
func (t Token) Bool() bool {
	switch t.raw {
	case True.raw:
		return true
	case False.raw:
		return false
	default:
		panic("invalid JSON token kind: " + t.Kind().String())
	}
}

// appendString appends a JSON string to dst and returns it.
// It panics if t is not a JSON string.
func (t Token) appendString(dst []byte, flags *jsonflags.Flags) ([]byte, error) {
	if raw := t.raw; raw != nil {
		// Handle raw string value.
		buf := raw.previousBuffer()
		if Kind(buf[0]) == '"' {
			if jsonwire.ConsumeSimpleString(buf) == len(buf) {
				return append(dst, buf...), nil
			}
			dst, _, err := jsonwire.ReformatString(dst, buf, flags)
			return dst, err
		}
	} else if len(t.str) != 0 && t.num == 0 {
		// Handle exact string value.
		return jsonwire.AppendQuote(dst, []byte(t.str), flags)
	}

	panic("invalid JSON token kind: " + t.Kind().String())
}

// String returns the unescaped string value for a JSON string.
// For other JSON kinds, this returns the raw JSON representation.
func (t Token) String() string {
	// This is inlinable to take advantage of "function outlining".
	// This avoids an allocation for the string(b) conversion
	// if the caller does not use the string in an escaping manner.
	// See https://blog.filippo.io/efficient-go-apis-with-the-inliner/
	s, b := t.string()
	if len(b) > 0 {
		return string(b)
	}
	return s
}
func (t Token) string() (string, []byte) {
	if raw := t.raw; raw != nil {
		if uint64(raw.previousOffsetStart()) != t.num {
			panic(invalidTokenPanic)
		}
		buf := raw.previousBuffer()
		if buf[0] == '"' {
			// TODO: Preserve ValueFlags in Token?
			isVerbatim := jsonwire.ConsumeSimpleString(buf) == len(buf)
			return "", jsonwire.UnquoteMayCopy(buf, isVerbatim)
		}
		// Handle tokens that are not JSON strings for fmt.Stringer.
		return "", buf
	}
	if len(t.str) != 0 && t.num == 0 {
		return t.str, nil
	}
	// Handle tokens that are not JSON strings for fmt.Stringer.
	if t.num > 0 {
		switch t.str[0] {
		case 'F':
			return string(jsonwire.AppendFloat(nil, float64(math.Float32frombits(uint32(t.num))), 32)), nil
		case 'f':
			return string(jsonwire.AppendFloat(nil, float64(math.Float64frombits(uint64(t.num))), 64)), nil
		case 'i':
			return strconv.FormatInt(int64(t.num), 10), nil
		case 'u':
			return strconv.FormatUint(uint64(t.num), 10), nil
		}
	}
	return "<invalid jsontext.Token>", nil
}

// appendNumber appends a JSON number to dst and returns it.
// It panics if t is not a JSON number.
func (t Token) appendNumber(dst []byte, flags *jsonflags.Flags) ([]byte, error) {
	if raw := t.raw; raw != nil {
		// Handle raw number value.
		buf := raw.previousBuffer()
		if Kind(buf[0]).normalize() == '0' {
			dst, _, err := jsonwire.ReformatNumber(dst, buf, flags)
			return dst, err
		}
	} else if t.num != 0 {
		// Handle exact number value.
		switch t.str[0] {
		case 'F':
			return jsonwire.AppendFloat(dst, float64(math.Float32frombits(uint32(t.num))), 32), nil
		case 'f':
			return jsonwire.AppendFloat(dst, float64(math.Float64frombits(uint64(t.num))), 64), nil
		case 'i':
			return strconv.AppendInt(dst, int64(t.num), 10), nil
		case 'u':
			return strconv.AppendUint(dst, uint64(t.num), 10), nil
		}
	}

	panic("invalid JSON token kind: " + t.Kind().String())
}

// Float32 returns the floating-point value for a JSON number
// parsed according to 32 bits of precision.
//
// If the JSON number is outside the representable range of a float32,
// it returns +Inf or -Inf along with an error
// that matches [strconv.ErrRange] according to [errors.Is].
//
// It returns a NaN, +Inf, or -Inf value for any JSON string
// with the values "NaN", "Infinity", or "-Infinity".
//
// It panics if the token kind is not a JSON number
// or a JSON string with the aforementioned values.
//
// Note that most JSON libraries and standards assume that JSON numbers
// are 64-bit floating-point numbers.
// This method should only be used if the caller knows
// from other context that this token is a JSON number
// formatted only to 32 bits of precision (such as being encoded
// using the [Float32] constructor). For all other situations,
// prefer using the [Token.Float] accessor instead.
func (t Token) Float32() (float32, error) {
	f, err := t.float(32)
	return float32(f), err
}

// Float returns the floating-point value for a JSON number
// parsed according to 64 bits of precision.
//
// If the JSON number is outside the representable range of a float64,
// it returns +Inf or -Inf along with an error
// that matches [strconv.ErrRange] according to [errors.Is].
//
// It returns a NaN, +Inf, or -Inf value for any JSON string
// with the values "NaN", "Infinity", or "-Infinity".
//
// It panics if the token kind is not a JSON number
// or a JSON string with the aforementioned values.
func (t Token) Float() (float64, error) {
	f, err := t.float(64)
	return float64(f), err
}

func (t Token) float(bits int) (float64, error) {
	if raw := t.raw; raw != nil {
		// Handle raw JSON number value.
		if uint64(raw.previousOffsetStart()) != t.num {
			panic(invalidTokenPanic)
		}
		buf := raw.previousBuffer()
		if Kind(buf[0]).normalize() == '0' {
			fv, err := strconv.ParseFloat(string(buf), bits)
			if err != nil {
				err = &numError{accessor: "Float", value: t.String(), err: errors.Unwrap(err)} // only ever ErrRange
			}
			return fv, err
		}
	} else if t.num != 0 {
		// Handle typed Go number value.
		switch t.str[0] {
		case 'F':
			return float64(math.Float32frombits(uint32(t.num))), nil
		case 'f':
			f64 := float64(math.Float64frombits(uint64(t.num)))
			if bits == 32 && !math.IsInf(f64, 0) && math.IsInf(float64(float32(f64)), 0) {
				return f64, &numError{accessor: "Float", value: t.String(), err: strconv.ErrRange}
			}
			return f64, nil
		case 'i':
			return float64(int64(t.num)), nil // NOTE: This may lead to loss of precision.
		case 'u':
			return float64(uint64(t.num)), nil // NOTE: This may lead to loss of precision.
		}
	}

	// Handle string values with "NaN", "Infinity", or "-Infinity".
	if t.Kind() == '"' {
		switch t.String() {
		case "NaN":
			return math.NaN(), nil
		case "Infinity":
			return math.Inf(+1), nil
		case "-Infinity":
			return math.Inf(-1), nil
		}
		// TODO: Should this be a error instead of a panic?
		// We can safely switch from a panic to an error in the future.
	}

	panic("invalid JSON token kind: " + t.Kind().String())
}

// Int returns the signed integer value for a JSON number.
//
// It reports an error that matches [strconv.ErrSyntax] according to [errors.Is]
// if the JSON number does not match the restricted grammar of just a signed integer.
// It reports an error that matches [strconv.ErrRange] according to [errors.Is]
// if the JSON number is a signed integer, but outside the range of an int64.
// Even if an error is reported, a reasonable value is still returned.
// The fractional component of any number is ignored (truncation toward zero).
// Any number beyond the representation of an int64 will be saturated
// to the closest representable value.
//
// It panics if the token kind is not a JSON number.
func (t Token) Int() (int64, error) {
	if raw := t.raw; raw != nil {
		// Handle raw JSON number value.
		if uint64(raw.previousOffsetStart()) != t.num {
			panic(invalidTokenPanic)
		}
		buf := raw.previousBuffer()
		if len(buf) > 0 && buf[0] == '-' {
			// Prospectively parse a negative integer.
			switch abs, ok := jsonwire.ParseUint(buf[len("-"):]); {
			case abs > -minInt64:
				return minInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
			case ok:
				return -1 * int64(abs), nil
			}
		} else {
			// Prospectively parse a non-negative integer.
			switch abs, ok := jsonwire.ParseUint(buf); {
			case abs > +maxInt64:
				return maxInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
			case ok:
				return +1 * int64(abs), nil
			}
		}
		// This is not a signed integer, which implies ErrSyntax.
		if Kind(buf[0]).normalize() == '0' {
			f64, _ := strconv.ParseFloat(string(buf), 64)
			return f64toi64(f64), &numError{accessor: "Int", value: t.String(), err: strconv.ErrSyntax}
		}
	} else if t.num != 0 {
		// Handle typed Go number value.
		switch t.str[0] {
		case 'i':
			return int64(t.num), nil
		case 'u':
			if t.num > maxInt64 {
				return maxInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
			}
			return int64(t.num), nil
		case 'f', 'F':
			f64 := float64(math.Float64frombits(uint64(t.num)))
			if t.str[0] == 'F' {
				f64 = float64(math.Float32frombits(uint32(t.num)))
			}
			switch i64 := f64toi64(f64); {
			case math.IsNaN(f64), math.Trunc(f64) != f64:
				return i64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrSyntax}
			case (i64 == minInt64 && f64 < minInt64) || (i64 == maxInt64 && f64 > maxInt64):
				return i64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
			default:
				return i64, nil
			}
		}
	}

	panic("invalid JSON token kind: " + t.Kind().String())
}

func f64toi64(f64 float64) int64 {
	switch {
	case math.IsNaN(f64):
		return 0
	case f64 >= maxInt64+1:
		return maxInt64
	case f64 < minInt64:
		return minInt64
	default:
		return int64(f64) // NOTE: This may lead to loss of precision.
	}
}

// Uint returns the unsigned integer value for a JSON number.
//
// It reports an error that matches [strconv.ErrSyntax] if the JSON number
// does not match the restricted grammar of just an unsigned integer.
// It reports an error that matches [strconv.ErrRange] if the JSON number
// is an unsigned integer, but outside the representable range of an uint64.
// Even if an error is reported, a reasonable value is still returned.
// The fractional component of any number is ignored (truncation toward zero).
// Any number beyond the representation of an uint64 will be saturated
// to the closest representable value.
//
// It panics if the token kind is not a JSON number.
func (t Token) Uint() (uint64, error) {
	// NOTE: This accessor returns 0 for any negative JSON number,
	// which might be surprising, but is at least consistent with the behavior
	// of saturating out-of-bounds numbers to the closest representable number.
	// We report ErrSyntax instead of ErrRange since the grammar for
	// an unsigned integer does not permit a negative sign.

	if raw := t.raw; raw != nil {
		// Handle raw JSON number value.
		if uint64(raw.previousOffsetStart()) != t.num {
			panic(invalidTokenPanic)
		}
		buf := raw.previousBuffer()
		// Prospectively parse an unsigned integer.
		switch abs, ok := jsonwire.ParseUint(buf); {
		case ok:
			return abs, nil
		case abs == maxUint64: // implies overflows
			return maxUint64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrRange}
		}
		// This is not an unsigned integer, which implies ErrSyntax.
		if Kind(buf[0]).normalize() == '0' {
			f64, _ := strconv.ParseFloat(string(buf), 64)
			return f64tou64(f64), &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax}
		}
	} else if t.num != 0 {
		// Handle typed Go number value.
		switch t.str[0] {
		case 'u':
			return t.num, nil
		case 'i':
			if int64(t.num) < minUint64 {
				return minUint64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax}
			}
			return uint64(int64(t.num)), nil
		case 'f', 'F':
			f64 := float64(math.Float64frombits(uint64(t.num)))
			if t.str[0] == 'F' {
				f64 = float64(math.Float32frombits(uint32(t.num)))
			}
			switch u64 := f64tou64(f64); {
			case math.IsNaN(f64), math.Trunc(f64) != f64, math.Signbit(f64):
				return u64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax}
			case (u64 == minUint64 && f64 < minUint64) || (u64 == maxUint64 && f64 > maxUint64):
				return u64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrRange}
			default:
				return u64, nil
			}
		}
	}

	panic("invalid JSON token kind: " + t.Kind().String())
}

func f64tou64(f64 float64) uint64 {
	switch {
	case math.IsNaN(f64):
		return 0
	case f64 >= maxUint64+1:
		return maxUint64
	case f64 < minUint64:
		return minUint64
	default:
		return uint64(f64) // NOTE: This may lead to loss of precision.
	}
}

// Kind returns the token kind.
func (t Token) Kind() Kind {
	switch {
	case t.raw != nil:
		raw := t.raw
		if uint64(raw.previousOffsetStart()) != t.num {
			panic(invalidTokenPanic)
		}
		return Kind(t.raw.buf[raw.prevStart]).normalize()
	case t.num != 0:
		return '0'
	case len(t.str) != 0:
		return '"'
	default:
		return invalidKind
	}
}

// A Kind represents the kind of a JSON token.
//
// Kind represents each possible JSON token kind with a single byte,
// which is conveniently the first byte of that kind's grammar
// with the restriction that numbers always be represented with '0'.
type Kind byte

const (
	KindInvalid     Kind = 0   // invalid kind
	KindNull        Kind = 'n' // null
	KindFalse       Kind = 'f' // false
	KindTrue        Kind = 't' // true
	KindString      Kind = '"' // string
	KindNumber      Kind = '0' // number
	KindBeginObject Kind = '{' // begin object
	KindEndObject   Kind = '}' // end object
	KindBeginArray  Kind = '[' // begin array
	KindEndArray    Kind = ']' // end array
)

const invalidKind Kind = 0

// String prints the kind in a humanly readable fashion.
func (k Kind) String() string {
	switch k {
	case 0:
		return "invalid"
	case 'n':
		return "null"
	case 'f':
		return "false"
	case 't':
		return "true"
	case '"':
		return "string"
	case '0':
		return "number"
	case '{':
		return "{"
	case '}':
		return "}"
	case '[':
		return "["
	case ']':
		return "]"
	default:
		return "<invalid jsontext.Kind: " + jsonwire.QuoteRune([]byte{byte(k)}) + ">"
	}
}

var normKind = [256]Kind{
	'n': 'n',
	'f': 'f',
	't': 't',
	'"': '"',
	'{': '{',
	'}': '}',
	'[': '[',
	']': ']',
	'-': '0',
	'0': '0',
	'1': '0',
	'2': '0',
	'3': '0',
	'4': '0',
	'5': '0',
	'6': '0',
	'7': '0',
	'8': '0',
	'9': '0',
}

// normalize coalesces all possible starting characters of a number as just '0',
// and converts all invalid kinds to 0.
func (k Kind) normalize() Kind {
	// A lookup table keeps the inlining cost as low as possible.
	return normKind[k]
}
