package text

import (
	"io"

	"github.com/templexxx/xhex"
	"lol.mleku.dev/chk"
	"lol.mleku.dev/errorf"
	"next.orly.dev/pkg/encoders/hex"
	"next.orly.dev/pkg/utils"
)

// JSONKey generates the JSON format for an object key and terminates with the semicolon.
func JSONKey(dst, k []byte) (b []byte) {
	dst = append(dst, '"')
	dst = append(dst, k...)
	dst = append(dst, '"', ':')
	b = dst
	return
}

// UnmarshalHex takes a byte string that should contain a quoted hexadecimal
// encoded value, decodes it using a SIMD hex codec and returns the decoded
// bytes in a newly allocated buffer.
func UnmarshalHex(b []byte) (h []byte, rem []byte, err error) {
	rem = b[:]
	var inQuote bool
	var start int
	for i := 0; i < len(b); i++ {
		if !inQuote {
			if b[i] == '"' {
				inQuote = true
				start = i + 1
			}
		} else if b[i] == '"' {
			hexStr := b[start:i]
			rem = b[i+1:]
			l := len(hexStr)
			if l%2 != 0 {
				err = errorf.E(
					"invalid length for hex: %d, %0x",
					len(hexStr), hexStr,
				)
				return
			}
			// Allocate a new buffer for the decoded data
			h = make([]byte, l/2)
			if err = xhex.Decode(h, hexStr); chk.E(err) {
				return
			}
			return
		}
	}
	if !inQuote {
		err = io.EOF
		return
	}
	return
}

// UnmarshalQuoted performs an in-place unquoting of NIP-01 quoted byte string.
func UnmarshalQuoted(b []byte) (content, rem []byte, err error) {
	if len(b) == 0 {
		err = io.EOF
		return
	}
	rem = b[:]
	for ; len(rem) >= 0; rem = rem[1:] {
		if len(rem) == 0 {
			err = io.EOF
			return
		}
		// advance to open quotes
		if rem[0] == '"' {
			rem = rem[1:]
			content = rem
			break
		}
	}
	if len(rem) == 0 {
		err = io.EOF
		return
	}
	var escaping bool
	var contentLen int
	for len(rem) > 0 {
		if rem[0] == '\\' {
			if !escaping {
				escaping = true
				contentLen++
				rem = rem[1:]
			} else {
				escaping = false
				contentLen++
				rem = rem[1:]
			}
		} else if rem[0] == '"' {
			if !escaping {
				rem = rem[1:]
				content = content[:contentLen]
				// Create a copy of the content to avoid corrupting the original input buffer
				contentCopy := make([]byte, len(content))
				copy(contentCopy, content)
				content = NostrUnescape(contentCopy)
				return
			}
			contentLen++
			rem = rem[1:]
			escaping = false
		} else {
			escaping = false
			switch rem[0] {
			// none of these characters are allowed inside a JSON string:
			//
			// backspace, tab, newline, form feed or carriage return.
			case '\b', '\t', '\n', '\f', '\r':
				pos := len(content) - len(rem)
				contextStart := pos - 10
				if contextStart < 0 {
					contextStart = 0
				}
				contextEnd := pos + 10
				if contextEnd > len(content) {
					contextEnd = len(content)
				}
				err = errorf.E(
					"invalid character '%s' in quoted string (position %d, context: %q)",
					NostrEscape(nil, rem[:1]),
					pos,
					string(content[contextStart:contextEnd]),
				)
				return
			}
			contentLen++
			rem = rem[1:]
		}
	}
	return
}

func MarshalHexArray(dst []byte, ha [][]byte) (b []byte) {
	b = dst
	// Pre-allocate buffer if nil to reduce reallocations
	// Estimate: [ + (hex encoded item + quotes + comma) * n + ]
	// Each hex item is 2*size + 2 quotes = 2*size + 2, plus comma for all but last
	if b == nil && len(ha) > 0 {
		estimatedSize := 2 // brackets
		if len(ha) > 0 {
			// Estimate based on first item size
			itemSize := len(ha[0]) * 2 // hex encoding doubles size
			estimatedSize += len(ha) * (itemSize + 2 + 1) // item + quotes + comma
		}
		b = make([]byte, 0, estimatedSize)
	}
	b = append(b, '[')
	for i := range ha {
		b = AppendQuote(b, ha[i], hex.EncAppend)
		if i != len(ha)-1 {
			b = append(b, ',')
		}
	}
	b = append(b, ']')
	return
}

// UnmarshalHexArray unpacks a JSON array containing strings with hexadecimal, and checks all
// values have the specified byte size.
func UnmarshalHexArray(b []byte, size int) (t [][]byte, rem []byte, err error) {
	rem = b
	var openBracket bool
	// Pre-allocate slice with estimated capacity to reduce reallocations
	// Estimate based on typical array sizes (can grow if needed)
	t = make([][]byte, 0, 16)
	for ; len(rem) > 0; rem = rem[1:] {
		if rem[0] == '[' {
			openBracket = true
		} else if openBracket {
			if rem[0] == ',' {
				continue
			} else if rem[0] == ']' {
				rem = rem[1:]
				return
			} else if rem[0] == '"' {
				var h []byte
				if h, rem, err = UnmarshalHex(rem); chk.E(err) {
					return
				}
				if len(h) != size {
					err = errorf.E(
						"invalid hex array size, got %d expect %d",
						2*len(h), 2*size,
					)
					return
				}
				t = append(t, h)
				if rem[0] == ']' {
					rem = rem[1:]
					// done
					return
				}
			}
		}
	}
	return
}

// UnmarshalStringArray unpacks a JSON array containing strings.
func UnmarshalStringArray(b []byte) (t [][]byte, rem []byte, err error) {
	rem = b
	var openBracket bool
	// Pre-allocate slice with estimated capacity to reduce reallocations
	// Estimate based on typical array sizes (can grow if needed)
	t = make([][]byte, 0, 16)
	for ; len(rem) > 0; rem = rem[1:] {
		if rem[0] == '[' {
			openBracket = true
		} else if openBracket {
			if rem[0] == ',' {
				continue
			} else if rem[0] == ']' {
				rem = rem[1:]
				return
			} else if rem[0] == '"' {
				var h []byte
				if h, rem, err = UnmarshalQuoted(rem); chk.E(err) {
					return
				}
				t = append(t, h)
				if rem[0] == ']' {
					rem = rem[1:]
					// done
					return
				}
			}
		}
	}
	return
}

func True() []byte  { return []byte("true") }
func False() []byte { return []byte("false") }

func MarshalBool(src []byte, truth bool) []byte {
	if truth {
		return append(src, True()...)
	}
	return append(src, False()...)
}

func UnmarshalBool(src []byte) (rem []byte, truth bool, err error) {
	rem = src
	t, f := True(), False()
	for i := range rem {
		if rem[i] == t[0] {
			if len(rem) < i+len(t) {
				err = io.EOF
				return
			}
			if utils.FastEqual(t, rem[i:i+len(t)]) {
				truth = true
				rem = rem[i+len(t):]
				return
			}
		}
		if rem[i] == f[0] {
			if len(rem) < i+len(f) {
				err = io.EOF
				return
			}
			if utils.FastEqual(f, rem[i:i+len(f)]) {
				rem = rem[i+len(f):]
				return
			}
		}
	}
	// if a truth value is not found in the string it will run to the end
	err = io.EOF
	return
}

func Comma(b []byte) (rem []byte, err error) {
	rem = b
	for i := range rem {
		if rem[i] == ',' {
			rem = rem[i:]
			return
		}
	}
	err = io.EOF
	return
}
