// Copyright 2026 International Digital Economy Academy
//
// 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.

///|
fn ParseContext::read_char(ctx : ParseContext) -> Char? {
  if ctx.offset < ctx.end_offset {
    let c1 = ctx.input.unsafe_get(ctx.offset).to_int()
    ctx.offset += 1
    if c1 >= 0xD800 && c1 <= 0xDBFF {
      if ctx.offset < ctx.end_offset {
        let c2 = ctx.input.unsafe_get(ctx.offset).to_int()
        if c2 >= 0xDC00 && c2 <= 0xDFFF {
          ctx.offset += 1
          let c3 = (c1 << 10) + c2 - 0x35fdc00
          return Some(c3.unsafe_to_char())
        }
      }
    }
    Some(c1.unsafe_to_char())
  } else {
    None
  }
}

///|
/// low surrogate
const SURROGATE_LOW_CHAR = 0xD800

///|
/// high surrogate
const SURROGATE_HIGH_CHAR = 0xDFFF

///|
/// `ctx.expect_char(c)` check the current context is c,
/// if it is, consume the character and return `()`,
/// otherwise raise an error, when it is an error, the position is unspecified.
fn ParseContext::expect_char(
  ctx : ParseContext,
  c : Char,
) -> Unit raise ParseError {
  guard ctx.offset < ctx.end_offset else { raise InvalidEof }
  let c1 = ctx.input.unsafe_get(ctx.offset).to_int()
  ctx.offset += 1
  let c0 = c.to_int()
  if c0 < 0xFFFF {
    // c0 < SURROGATE_LOW_CHAR || c0 is (0xE000..=0XFFFF)  
    // c0 is a valid char so only need check if c0<0xFFFF is BMP code point
    if c0 != c1 {
      ctx.invalid_char(shift=-1)
    }
  } else {
    // c0 is not bmp code point
    // c1 has to be surrogate pair otherwise it is invalid
    guard c1 is (SURROGATE_LOW_CHAR..=SURROGATE_HIGH_CHAR) &&
      ctx.offset < ctx.end_offset else {
      ctx.invalid_char(shift=-1)
    }
    let c2 = ctx.input.unsafe_get(ctx.offset).to_int()
    let c3 = (c1 << 10) + c2 - 0x35fdc00
    if c3 != c0 {
      ctx.invalid_char(shift=-1)
    } else {
      ctx.offset += 1 // consume and move forward
    }
  }
}

///|
/// `ctx.expect_ascii_char(c)` check the current context is c,
/// 
fn ParseContext::expect_ascii_char(
  ctx : ParseContext,
  c : Byte,
) -> Unit raise ParseError {
  guard ctx.offset < ctx.end_offset else { raise InvalidEof }
  let c1 = ctx.input.unsafe_get(ctx.offset).to_int()
  ctx.offset += 1
  if c.to_int() != c1 {
    ctx.invalid_char(shift=-1)
  }
}

///|
test "expect_char" {
  let ctx = ParseContext::make("abc")
  ctx.expect_char('a')
  ctx.expect_char('b')
  ctx.expect_char('c')
  try ctx.expect_char('d') catch {
    err => json_inspect(err, content="InvalidEof")
  } noraise {
    _ => fail("expected InvalidEof")
  }
}

///|
test "expect_char with surrogate pair" {
  // "\uD83D\uDE00" // todo: shall we allow this?
  let ctx = ParseContext::make("a\u{1F600}bc\u{1F600}c")
  ctx.expect_char('a')
  ctx.expect_char((0x1F600).unsafe_to_char())
  ctx.expect_char('b')
  ctx.expect_char('c')
  ctx.expect_char((0x1F600).unsafe_to_char())
  ctx.expect_char('c')
  try ctx.expect_char('d') catch {
    err => json_inspect(err, content="InvalidEof")
  } noraise {
    _ => fail("expected InvalidEof")
  }
}

///|
// Hot path: this runs at the start of every `lex_value` and
// `lex_after_*` step. The previous `lexmatch` path constructed a
// fresh `StringView` of the remaining input on every call, which
// shows up as a top allocation source in JSON parsing workloads.
// JSON whitespace is ASCII-only, so we can scan UTF-16 code units
// directly on the existing input view with the same semantics.
//
// `offset` is held in a local for the duration of the scan so each
// consumed whitespace character is one `local.set` instead of a
// `struct.set` against `ctx`.
fn ParseContext::lex_skip_whitespace(ctx : ParseContext) -> Unit {
  let end = ctx.end_offset
  ctx.offset = for offset = ctx.offset {
    if offset >= end {
      break offset
    }
    match ctx.input.unsafe_get(offset) {
      ' ' | '\t' | '\r' | '\n' => continue offset + 1
      _ => break offset
    }
  }
}

///|
test "lex_skip_whitespace" {
  // Empty input: nothing to skip.
  let ctx = ParseContext::make("")
  ctx.lex_skip_whitespace()
  json_inspect(ctx.offset, content=0)
  // Only whitespace: offset advances to end.
  let ctx = ParseContext::make("   \t\r\n")
  ctx.lex_skip_whitespace()
  json_inspect(ctx.offset, content=6)
  // Mixed whitespace before a token: stops at the first non-ws byte.
  let ctx = ParseContext::make(" \t\r\n{")
  ctx.lex_skip_whitespace()
  json_inspect(ctx.offset, content=4)
  // No leading whitespace: offset unchanged.
  let ctx = ParseContext::make("{}")
  ctx.lex_skip_whitespace()
  json_inspect(ctx.offset, content=0)
  // Non-ASCII / Unicode whitespace (e.g. U+00A0 NBSP) must NOT be
  // treated as JSON whitespace — RFC 8259 limits the set to the four
  // ASCII chars handled above.
  let ctx = ParseContext::make("\u{00A0}x")
  ctx.lex_skip_whitespace()
  json_inspect(ctx.offset, content=0)
}

///|
fn ParseContext::lex_after_array_value(
  ctx : ParseContext,
) -> Token raise ParseError {
  ctx.lex_skip_whitespace()
  match ctx.read_char() {
    Some(']') => RBracket
    Some(',') => Comma
    Some(_) => ctx.invalid_char(shift=-1)
    None => raise InvalidEof
  }
}

///|
fn ParseContext::lex_after_property_name(
  ctx : ParseContext,
) -> Unit raise ParseError {
  ctx.lex_skip_whitespace()
  match ctx.read_char() {
    Some(':') => ()
    Some(_) => ctx.invalid_char(shift=-1)
    None => raise InvalidEof
  }
}

///|
fn ParseContext::lex_after_object_value(
  ctx : ParseContext,
) -> Token raise ParseError {
  ctx.lex_skip_whitespace()
  match ctx.read_char() {
    Some('}') => RBrace
    Some(',') => Comma
    Some(_) => ctx.invalid_char(shift=-1)
    None => raise InvalidEof
  }
}

///|
/// In the context of `{`, try to lex token `}` or a property name,
/// otherwise raise an error.
fn ParseContext::lex_property_name(
  ctx : ParseContext,
) -> Token raise ParseError {
  ctx.lex_skip_whitespace()
  match ctx.read_char() {
    Some('}') => RBrace
    Some('"') => {
      let s = ctx.lex_string()
      String(s)
    }
    Some(_) => ctx.invalid_char(shift=-1)
    None => raise InvalidEof
  }
}

///|
/// In the context of `{ ...,` try to lex a property name,
/// otherwise raise an error.
/// since it is in comma context, `}` is not allowed.
fn ParseContext::lex_property_name2(
  ctx : ParseContext,
) -> Token raise ParseError {
  ctx.lex_skip_whitespace()
  match ctx.read_char() {
    Some('"') => {
      let s = ctx.lex_string()
      String(s)
    }
    Some(_) => ctx.invalid_char(shift=-1)
    None => raise InvalidEof
  }
}