// json.v — JSON encoding and decoding for the VuurRaaf VM.
//
// The decoder is self-contained (no json2) because json2.decode[Any] can loop
// forever on truncated input; this parser is strictly bounded — every loop
// consumes input, so malformed documents always fail with a position.
//
// Values map 1:1 to the language: objects -> structs (keys in input order,
// last duplicate wins), arrays -> arrays, strings -> strings, numbers -> int
// when integral (42, 42.0) else float, true/false -> 1/0, null -> `none`.
module vm

import math

// JsonParser walks a JSON document one byte at a time.
struct JsonParser {
mut:
	src string
	pos int
}

// json_parse decodes a whole JSON document into a VM value.
fn (mut v Vm) json_parse(s string) !i64 {
	mut p := JsonParser{ src: s }
	p.skip_ws()
	if p.pos >= p.src.len {
		return p.error('empty input')
	}
	val := v.json_parse_value(mut p)!
	p.skip_ws()
	if p.pos < p.src.len {
		return p.error('trailing data after the value')
	}
	return val
}

fn (mut p JsonParser) peek() u8 {
	if p.pos >= p.src.len {
		return 0
	}
	return p.src[p.pos]
}

fn (mut p JsonParser) skip_ws() {
	for p.pos < p.src.len {
		c := p.src[p.pos]
		if c == ` ` || c == `\t` || c == `\n` || c == `\r` {
			p.pos++
		} else {
			break
		}
	}
}

// error builds a parse error with the current line and column.
fn (mut p JsonParser) error(msg string) IError {
	mut line := 1
	mut col := 1
	for i in 0..p.pos {
		if p.src[i] == `\n` {
			line++
			col = 1
		} else {
			col++
		}
	}
	return error('${msg} at line ${line}, col ${col}')
}

fn (mut p JsonParser) expect_word(w string) ! {
	if p.pos + w.len > p.src.len || p.src[p.pos..p.pos + w.len] != w {
		return p.error('invalid token')
	}
	p.pos += w.len
}

fn (mut v Vm) json_parse_value(mut p JsonParser) !i64 {
	p.skip_ws()
	match p.peek() {
		`{` {
			return v.json_parse_object(mut p)!
		}
		`[` {
			return v.json_parse_array(mut p)!
		}
		`"` {
			return v.alloc_str(p.parse_string()!)
		}
		`t` {
			p.expect_word('true')!
			return v.enc_int(1)
		}
		`f` {
			p.expect_word('false')!
			return v.enc_int(0)
		}
		`n` {
			p.expect_word('null')!
			return none_val
		}
		else {
			c := p.peek()
			if (c >= `0` && c <= `9`) || c == `-` {
				return v.json_parse_number(mut p)!
			}
			return p.error('unexpected character "${c.ascii_str()}"')
		}
	}
}

fn (mut v Vm) json_parse_object(mut p JsonParser) !i64 {
	p.pos++ // consume '{'
	mut fields := []Field{}
	p.skip_ws()
	if p.peek() == `}` {
		p.pos++
		v.structs << StructVal{ fields: fields, by_name: v.index_fields(fields) }
		return v.mkstruct_handle(v.structs.len - 1)
	}
	for {
		p.skip_ws()
		if p.peek() != `"` {
			return p.error('expected a string key in object')
		}
		key := p.parse_string()!
		p.skip_ws()
		if p.peek() != `:` {
			return p.error('expected ":" after object key')
		}
		p.pos++
		val := v.json_parse_value(mut p)!
		// last duplicate key wins (like most JSON parsers)
		mut replaced := false
		for i in 0..fields.len {
			if fields[i].name == key {
				fields[i].val = val
				replaced = true
				break
			}
		}
		if !replaced {
			fields << Field{ name: key, val: val }
		}
		p.skip_ws()
		c := p.peek()
		if c == `,` {
			p.pos++
			continue
		}
		if c == `}` {
			p.pos++
			break
		}
		return p.error('expected "," or "}" in object')
	}
	v.structs << StructVal{ fields: fields, by_name: v.index_fields(fields) }
	return v.mkstruct_handle(v.structs.len - 1)
}

fn (mut v Vm) json_parse_array(mut p JsonParser) !i64 {
	p.pos++ // consume '['
	mut arr := []i64{}
	p.skip_ws()
	if p.peek() == `]` {
		p.pos++
		v.arrays << arr
		return v.mkarr(v.arrays.len - 1)
	}
	for {
		arr << v.json_parse_value(mut p)!
		p.skip_ws()
		c := p.peek()
		if c == `,` {
			p.pos++
			continue
		}
		if c == `]` {
			p.pos++
			break
		}
		return p.error('expected "," or "]" in array')
	}
	v.arrays << arr
	return v.mkarr(v.arrays.len - 1)
}

fn (mut v Vm) json_parse_number(mut p JsonParser) !i64 {
	start := p.pos
	if p.peek() == `-` {
		p.pos++
	}
	mut digits := 0
	for p.pos < p.src.len && p.src[p.pos] >= `0` && p.src[p.pos] <= `9` {
		p.pos++
		digits++
	}
	if digits == 0 {
		return p.error('invalid number')
	}
	mut is_float := false
	if p.pos < p.src.len && p.src[p.pos] == `.` {
		is_float = true
		p.pos++
		mut fd := 0
		for p.pos < p.src.len && p.src[p.pos] >= `0` && p.src[p.pos] <= `9` {
			p.pos++
			fd++
		}
		if fd == 0 {
			return p.error('invalid number (missing digits after ".")')
		}
	}
	if p.pos < p.src.len && (p.src[p.pos] == `e` || p.src[p.pos] == `E`) {
		is_float = true
		p.pos++
		if p.pos < p.src.len && (p.src[p.pos] == `+` || p.src[p.pos] == `-`) {
			p.pos++
		}
		mut ed := 0
		for p.pos < p.src.len && p.src[p.pos] >= `0` && p.src[p.pos] <= `9` {
			p.pos++
			ed++
		}
		if ed == 0 {
			return p.error('invalid number (missing exponent digits)')
		}
	}
	raw := p.src[start..p.pos]
	if is_float {
		f := raw.f64()
		// integral floats become ints so `42` and `42.0` round-trip cleanly
		if f == math.floor(f) && math.abs(f) < 1e18 {
			return v.enc_int(i64(f))
		}
		return v.push_float(f)
	}
	return v.enc_int(raw.i64())
}

fn (mut p JsonParser) parse_string() !string {
	p.pos++ // opening quote
	mut out := ''
	for {
		if p.pos >= p.src.len {
			return p.error('unterminated string')
		}
		c := p.src[p.pos]
		if c == `"` {
			p.pos++
			return out
		}
		if c == `\\` {
			p.pos++
			if p.pos >= p.src.len {
				return p.error('unterminated escape sequence')
			}
			e := p.src[p.pos]
			p.pos++
			match e {
				`"` {
					out += '"'
				}
				`\\` {
					out += '\\'
				}
				`/` {
					out += '/'
				}
				`b` {
					out += '\b'
				}
				`f` {
					out += '\f'
				}
				`n` {
					out += '\n'
				}
				`r` {
					out += '\r'
				}
				`t` {
					out += '\t'
				}
				`u` {
					out += p.parse_unicode_escape()!
				}
				else {
					return p.error('invalid escape "\\${e.ascii_str()}"')
				}
			}
			continue
		}
		// pass a UTF-8 code point through as raw bytes
		width := utf8_width(c)
		if width == 0 {
			return p.error('invalid UTF-8 byte')
		}
		out += p.src[p.pos..p.pos + width]
		p.pos += width
	}
	return p.error('unterminated string')
}

// parse_unicode_escape handles \uXXXX, combining surrogate pairs so emoji and
// astral characters decode correctly.
fn (mut p JsonParser) parse_unicode_escape() !string {
	if p.pos + 4 > p.src.len {
		return p.error('invalid \\u escape')
	}
	hi := hex4(p.src[p.pos..p.pos + 4]) or { return p.error('invalid \\u escape') }
	p.pos += 4
	mut code := hi
	if hi >= 0xD800 && hi <= 0xDBFF {
		// high surrogate: combine with an immediately following low surrogate
		if p.pos + 6 <= p.src.len && p.src[p.pos] == `\\` && p.src[p.pos + 1] == `u` {
			lo := hex4(p.src[p.pos + 2..p.pos + 6]) or { return p.error('invalid \\u escape') }
			if lo >= 0xDC00 && lo <= 0xDFFF {
				code = 0x10000 + (hi - 0xD800) * 0x400 + (lo - 0xDC00)
				p.pos += 6
			}
		}
	} else if hi >= 0xDC00 && hi <= 0xDFFF {
		return p.error('unpaired low surrogate')
	}
	return utf8_encode(code)
}

fn hex4(s string) !u32 {
	mut n := u32(0)
	for i in 0..4 {
		c := s[i]
		d := match c {
			`0`...`9` { u32(c - `0`) }
			`a`...`f` { u32(c - `a` + 10) }
			`A`...`F` { u32(c - `A` + 10) }
			else { return error('bad hex digit "${c.ascii_str()}"') }
		}
		n = n * 16 + d
	}
	return n
}

fn utf8_width(b u8) int {
	if b < 0x80 {
		return 1
	}
	if b >= 0xC0 && b <= 0xDF {
		return 2
	}
	if b >= 0xE0 && b <= 0xEF {
		return 3
	}
	if b >= 0xF0 && b <= 0xF7 {
		return 4
	}
	return 0
}

// utf8_encode renders a Unicode code point as UTF-8 bytes.
fn utf8_encode(code u32) string {
	if code < 0x80 {
		return u8(code).ascii_str()
	}
	mut bytes := []u8{}
	if code < 0x800 {
		bytes << u8(0xC0 | (code >> 6))
		bytes << u8(0x80 | (code & 0x3F))
	} else if code < 0x10000 {
		bytes << u8(0xE0 | (code >> 12))
		bytes << u8(0x80 | ((code >> 6) & 0x3F))
		bytes << u8(0x80 | (code & 0x3F))
	} else {
		bytes << u8(0xF0 | (code >> 18))
		bytes << u8(0x80 | ((code >> 12) & 0x3F))
		bytes << u8(0x80 | ((code >> 6) & 0x3F))
		bytes << u8(0x80 | (code & 0x3F))
	}
	return bytes.bytestr()
}

// ---------------------------------------------------------------------------
// encoding

// json_encode_value renders a VuurRaaf value as a JSON string. Ints (and
// bools, which are 0/1) become numbers, floats use fmt_float so integral
// floats stay clean, structs become objects, arrays become arrays, and the
// `none` value becomes null. Functions cannot be encoded.
fn (mut v Vm) json_encode_value(x i64, depth int) !string {
	if depth > 64 {
		return error('value is nested too deeply (possible cycle)')
	}
	if v.is_none(x) {
		return 'null'
	}
	if v.is_str(x) && v.valid_handle(x) {
		return json_quote(v.strings[v.hand(x)])
	}
	if v.is_arr(x) && v.valid_arr_handle(x) {
		mut parts := []string{}
		for el in v.arrays[v.hand(x)] {
			parts << v.json_encode_value(el, depth + 1)!
		}
		return '[' + parts.join(',') + ']'
	}
	if v.is_struct(x) && v.valid_struct_handle(x) {
		mut parts := []string{}
		for f in v.structs[v.hand(x)].fields {
			parts << json_quote(f.name) + ':' + v.json_encode_value(f.val, depth + 1)!
		}
		return '{' + parts.join(',') + '}'
	}
	if v.is_float(x) && v.valid_float_handle(x) {
		f := v.fval(x)
		if math.is_nan(f) || math.is_inf(f, 1) || math.is_inf(f, -1) {
			return error('cannot encode NaN or Infinity as JSON')
		}
		return fmt_float(f)
	}
	if v.is_closure(x) && v.valid_closure_handle(x) {
		return error('cannot encode a function value as JSON')
	}
	return v.dec_int(x).str()
}

// json_pretty_value renders a value as indented, multi-line JSON (the
// `json_pretty` builtin used by the json stdlib module).
fn (mut v Vm) json_pretty_value(x i64, depth int) !string {
	ind := '  '.repeat(depth)
	ind1 := '  '.repeat(depth + 1)
	if v.is_none(x) {
		return 'null'
	}
	if v.is_str(x) && v.valid_handle(x) {
		return json_quote(v.strings[v.hand(x)])
	}
	if v.is_arr(x) && v.valid_arr_handle(x) {
		a := v.arrays[v.hand(x)]
		if a.len == 0 {
			return '[]'
		}
		mut parts := []string{}
		for el in a {
			parts << ind1 + v.json_pretty_value(el, depth + 1)!
		}
		return '[\n' + parts.join(',\n') + '\n' + ind + ']'
	}
	if v.is_struct(x) && v.valid_struct_handle(x) {
		s := v.structs[v.hand(x)]
		if s.fields.len == 0 {
			return '{}'
		}
		mut parts := []string{}
		for f in s.fields {
			parts << ind1 + json_quote(f.name) + ': ' + v.json_pretty_value(f.val, depth + 1)!
		}
		return '{\n' + parts.join(',\n') + '\n' + ind + '}'
	}
	if v.is_float(x) && v.valid_float_handle(x) {
		f := v.fval(x)
		if math.is_nan(f) || math.is_inf(f, 1) || math.is_inf(f, -1) {
			return error('cannot encode NaN or Infinity as JSON')
		}
		return fmt_float(f)
	}
	if v.is_closure(x) && v.valid_closure_handle(x) {
		return error('cannot encode a function value as JSON')
	}
	return v.dec_int(x).str()
}

// json_quote escapes a string into a JSON string literal. UTF-8 bytes pass
// through untouched; control characters become \\u00XX escapes.
fn json_quote(s string) string {
	hex := '0123456789ABCDEF'
	mut out := '"'
	for b in s.bytes() {
		match b {
			`"` { out += '\\"' }
			`\\` { out += '\\\\' }
			`\n` { out += '\\n' }
			`\r` { out += '\\r' }
			`\t` { out += '\\t' }
			`\b` { out += '\\b' }
			`\f` { out += '\\f' }
			else {
				if b < 0x20 {
					out += '\\u00'
					out += hex[int(b >> 4)].ascii_str()
					out += hex[int(b & 0xF)].ascii_str()
				} else {
					out += b.ascii_str()
				}
			}
		}
	}
	return out + '"'
}
