package json

import (
	"errors"
	"fmt"
	"io"
	"strconv"
	"unsafe"

	"github.com/aws/smithy-go/internal/serde"
)

// matches stdlib
const maxDepth = 10_000

const (
	inObject int8 = iota + 1
	inArray
)

type parseState uint8

const (
	stValue parseState = iota
	stObjectKey
	stObjectColon
	stObjectComma
	stObjectKeyAfterComma
	stArrayValue
	stArrayComma
)

func errUnexpectedToken(c byte) error {
	return fmt.Errorf("unexpected token '%c'", c)
}

// parser is a combined scanner+validator that pulls tokens from a JSON body.
// It integrates tokenization and grammar validation into a single loop to
// minimize function call overhead on the hot path.
type parser struct {
	p     []byte
	i     int
	stack serde.Stack[int8]
	done  bool
	state parseState

	// set by scanString: true if the last string token contained escapes
	escaped bool
}

func (p *parser) Next() ([]byte, error) {
	if p.done {
		return nil, io.EOF
	}

	for {
		// inline whitespace scanning + token identification
		i := p.i
		buf := p.p
		for ; i < len(buf); i++ {
			if charClass[buf[i]] != ccWhite {
				break
			}
		}
		if i >= len(buf) {
			p.i = i
			return nil, fmt.Errorf("unexpected end of JSON input")
		}

		c := buf[i]
		p.i = i

		// get the token
		var next []byte
		var err error
		if charClass[c] == ccDelim {
			p.i = i + 1
			next = buf[i : i+1]
		} else {
			switch c {
			case '"':
				next, err = p.scanString()
			case 't':
				next, err = p.scanLiteral("true")
			case 'f':
				next, err = p.scanLiteral("false")
			case 'n':
				next, err = p.scanLiteral("null")
			default:
				next, err = p.scanNumber()
			}
			if err != nil {
				return nil, err
			}
		}

		// validate grammar
		switch p.state {
		case stValue:
			switch next[0] {
			case '{':
				p.state = stObjectKey
				p.stack.Push(inObject)
				if p.stack.Len() > maxDepth {
					return nil, errors.New("exceeded max nesting depth")
				}
				return next, nil
			case '[':
				p.state = stArrayValue
				p.stack.Push(inArray)
				if p.stack.Len() > maxDepth {
					return nil, errors.New("exceeded max nesting depth")
				}
				return next, nil
			case ',', ':', '}', ']':
				return nil, errUnexpectedToken(next[0])
			}
			p.afterValue()
			return next, nil

		case stObjectKey:
			switch next[0] {
			case '"':
				p.state = stObjectColon
				return next, nil
			case '}':
				p.close()
				return next, nil
			default:
				return nil, errUnexpectedToken(next[0])
			}

		case stObjectColon:
			if next[0] != ':' {
				return nil, errUnexpectedToken(next[0])
			}
			p.state = stValue
			continue

		case stObjectComma:
			switch next[0] {
			case ',':
				p.state = stObjectKeyAfterComma
				continue
			case '}':
				p.close()
				return next, nil
			default:
				return nil, errUnexpectedToken(next[0])
			}

		case stObjectKeyAfterComma:
			if next[0] != '"' {
				return nil, errUnexpectedToken(next[0])
			}
			p.state = stObjectColon
			return next, nil

		case stArrayValue:
			if next[0] == ']' {
				p.close()
				return next, nil
			}
			// handle as value
			switch next[0] {
			case '{':
				p.state = stObjectKey
				p.stack.Push(inObject)
				if p.stack.Len() > maxDepth {
					return nil, errors.New("exceeded max nesting depth")
				}
				return next, nil
			case '[':
				p.state = stArrayValue
				p.stack.Push(inArray)
				if p.stack.Len() > maxDepth {
					return nil, errors.New("exceeded max nesting depth")
				}
				return next, nil
			case ',', ':', '}':
				return nil, errUnexpectedToken(next[0])
			}
			p.afterValue()
			return next, nil

		case stArrayComma:
			switch next[0] {
			case ',':
				p.state = stValue
				continue
			case ']':
				p.close()
				return next, nil
			default:
				return nil, errUnexpectedToken(next[0])
			}
		}
	}
}

func (p *parser) Skip() error {
	var depth int
	for {
		tok, err := p.Next()
		if err != nil {
			return err
		}
		switch tok[0] {
		case '{', '[':
			depth++
		case '}', ']':
			depth--
		}
		if depth == 0 {
			return nil
		}
	}
}

func (p *parser) Value() (any, error) {
	tok, err := p.Next()
	if err != nil {
		return nil, err
	}
	return p.value(tok)
}

func (p *parser) value(tok []byte) (any, error) {
	switch tok[0] {
	case 'n':
		return nil, nil
	case 't':
		return true, nil
	case 'f':
		return false, nil
	case '"':
		return unquote(tok)
	case '{':
		m := map[string]any{}
		for {
			ktok, err := p.Next()
			if err != nil {
				return nil, err
			}
			if ktok[0] == '}' {
				return m, nil
			}
			key, err := unquote(ktok)
			if err != nil {
				return nil, err
			}
			val, err := p.Value()
			if err != nil {
				return nil, err
			}
			m[key] = val
		}
	case '[':
		var list []any
		for {
			tok, err := p.Next()
			if err != nil {
				return nil, err
			}
			if tok[0] == ']' {
				if list == nil {
					list = []any{}
				}
				return list, nil
			}
			val, err := p.value(tok)
			if err != nil {
				return nil, err
			}
			list = append(list, val)
		}
	default:
		return strconv.ParseFloat(string(tok), 64)
	}
}

func (p *parser) close() {
	p.stack.Pop()
	p.afterValue()
}

func (p *parser) afterValue() {
	top := p.stack.Top()
	if top == nil {
		p.done = true
		return
	}

	switch *top {
	case inObject:
		p.state = stObjectComma
	case inArray:
		p.state = stArrayComma
	default:
		p.done = true
	}
}

// --- Integrated scanner methods ---

const (
	lo = uint64(0x0101010101010101)
	hi = uint64(0x8080808080808080)
)

func hasValue(v uint64, c byte) uint64 {
	x := v ^ (lo * uint64(c))
	return (x - lo) & ^x & hi
}

func hasLess(v uint64, n byte) uint64 {
	return (v - lo*uint64(n)) & ^v & hi
}

func (p *parser) scanString() ([]byte, error) {
	start := p.i
	i := p.i + 1 // skip opening "
	p.escaped = false

	// SWAR: scan 8 bytes at a time for '"', '\', or control chars (< 0x20)
	for i+8 <= len(p.p) {
		v := *(*uint64)(unsafe.Pointer(&p.p[i]))
		mask := hasLess(v, 0x20) | hasValue(v, '"') | hasValue(v, '\\')
		if mask != 0 {
			break
		}
		i += 8
	}

	for i < len(p.p) {
		c := p.p[i]
		if c < 0x20 {
			p.i = i
			return nil, fmt.Errorf("invalid control character at offset %d", i)
		}

		if c == '\\' {
			p.escaped = true
			p.i = i + 1
			if err := p.scanEscape(); err != nil {
				return nil, err
			}
			i = p.i
			continue
		}

		if c == '"' {
			i++
			p.i = i
			return p.p[start:i], nil
		}

		i++
	}
	p.i = i
	return nil, fmt.Errorf("unterminated string at offset %d", start)
}

func (p *parser) scanEscape() error {
	if p.i >= len(p.p) {
		return fmt.Errorf("unterminated escape at offset %d", p.i-1)
	}
	c := p.p[p.i]
	p.i++
	switch c {
	case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
		return nil
	case 'u':
		return p.scanUnicodeEscape()
	default:
		return fmt.Errorf("invalid escape character '%c' at offset %d", c, p.i-1)
	}
}

func (p *parser) scanUnicodeEscape() error {
	if p.i+4 > len(p.p) {
		return fmt.Errorf("incomplete unicode escape at offset %d", p.i-2)
	}
	for _, c := range p.p[p.i : p.i+4] {
		if !('0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F') {
			return fmt.Errorf("invalid character '%c' in unicode escape at offset %d", c, p.i-2)
		}
	}
	p.i += 4
	return nil
}

func (p *parser) scanNumber() ([]byte, error) {
	i := p.i
	if i < len(p.p) && p.p[i] == '-' {
		i++
	}
	digitStart := i
	for i < len(p.p) && p.p[i] >= '0' && p.p[i] <= '9' {
		i++
	}
	if i == digitStart {
		return nil, fmt.Errorf("unexpected token at offset %d", p.i)
	}
	if p.p[digitStart] == '0' && i-digitStart > 1 {
		return nil, fmt.Errorf("leading zeros not allowed at offset %d", digitStart)
	}

	if i < len(p.p) && p.p[i] == '.' {
		i++
		digitStart = i
		for i < len(p.p) && p.p[i] >= '0' && p.p[i] <= '9' {
			i++
		}
		if i == digitStart {
			return nil, fmt.Errorf("no digits after decimal point at offset %d", i)
		}
	}

	if i < len(p.p) && (p.p[i] == 'e' || p.p[i] == 'E') {
		i++
		if i < len(p.p) && (p.p[i] == '+' || p.p[i] == '-') {
			i++
		}
		digitStart = i
		for i < len(p.p) && p.p[i] >= '0' && p.p[i] <= '9' {
			i++
		}
		if i == digitStart {
			return nil, fmt.Errorf("no digits after exponent at offset %d", i)
		}
	}

	start := p.i
	p.i = i
	return p.p[start:i], nil
}

func (p *parser) scanLiteral(lit string) ([]byte, error) {
	end := p.i + len(lit)
	if end > len(p.p) || string(p.p[p.i:end]) != lit {
		return nil, fmt.Errorf("invalid literal at offset %d", p.i)
	}
	start := p.i
	p.i = end
	return p.p[start:end], nil
}

const (
	ccNone  byte = 0
	ccWhite byte = 1
	ccDelim byte = 2
)

var charClass = [256]byte{
	' ':  ccWhite,
	'\t': ccWhite,
	'\n': ccWhite,
	'\r': ccWhite,
	'{':  ccDelim,
	'}':  ccDelim,
	'[':  ccDelim,
	']':  ccDelim,
	':':  ccDelim,
	',':  ccDelim,
}
