// Go 1.24.0 encoding/json scanner transition/error rules.
// Copyright The Go Authors; BSD-3-Clause, see licenses/GO-BSD-3-Clause.txt.

///|
priv enum JsonDiagnosticState {
  Value
  ValueOrEmpty
  Key
  KeyOrEmpty
  EndValue
  EndTop
  Quoted
  Escape
  Hex(Int)
  Negative
  Zero
  Integer
  Dot
  Fraction
  Exponent
  ExponentSign
  ExponentDigits
  Literal(Bytes, Int, String)
}

///|
priv struct JsonDiagnosticScanner {
  mut state : JsonDiagnosticState
  // Object key = 0, object value = 1, array value = 2.
  stack : Array[Int]
}

///|
fn json_space(c : Byte) -> Bool {
  c == 32 || c == 9 || c == 10 || c == 13
}

///|
fn json_error_character(c : Byte, context : String) -> String {
  let quote = if c == 39 {
    "'\\''"
  } else if c == 34 {
    "'\"'"
  } else {
    let quoted = lint_quote(c.to_int().to_char().unwrap().to_string())
    "'" + quoted[1:quoted.length() - 1].to_owned() + "'"
  }
  "invalid character " + quote + " " + context
}

///|
fn JsonDiagnosticScanner::close(self : JsonDiagnosticScanner) -> Unit {
  ignore(self.stack.pop())
  self.state = if self.stack.is_empty() { EndTop } else { EndValue }
}

///|
fn JsonDiagnosticScanner::step(
  self : JsonDiagnosticScanner,
  c : Byte,
) -> String? {
  // A numeric terminator is reprocessed by EndValue, and then by EndTop.
  // These transitions are constant depth and never recurse with source length.
  while true {
    match self.state {
      ValueOrEmpty => {
        if json_space(c) {
          return None
        }
        self.state = if c == 93 { EndValue } else { Value }
        continue
      }
      KeyOrEmpty => {
        if json_space(c) {
          return None
        }
        if c == 125 {
          self.stack[self.stack.length() - 1] = 1
          self.state = EndValue
        } else {
          self.state = Key
        }
        continue
      }
      Value => {
        if json_space(c) {
          return None
        }
        self.state = match c {
          123 => {
            self.stack.push(0)
            KeyOrEmpty
          }
          91 => {
            self.stack.push(2)
            ValueOrEmpty
          }
          34 => Quoted
          45 => Negative
          48 => Zero
          116 => Literal(b"true", 1, "true")
          102 => Literal(b"false", 1, "false")
          110 => Literal(b"null", 1, "null")
          _ if c >= 49 && c <= 57 => Integer
          _ =>
            return Some(
              json_error_character(c, "looking for beginning of value"),
            )
        }
        return None
      }
      Key => {
        if json_space(c) {
          return None
        }
        if c != 34 {
          return Some(
            json_error_character(
              c, "looking for beginning of object key string",
            ),
          )
        }
        self.state = Quoted
        return None
      }
      EndValue => {
        if self.stack.is_empty() {
          self.state = EndTop
          continue
        }
        if json_space(c) {
          return None
        }
        let last = self.stack.length() - 1
        match self.stack[last] {
          0 => {
            if c != 58 {
              return Some(json_error_character(c, "after object key"))
            }
            self.stack[last] = 1
            self.state = Value
          }
          1 =>
            if c == 44 {
              self.stack[last] = 0
              self.state = Key
            } else if c == 125 {
              self.close()
            } else {
              return Some(
                json_error_character(c, "after object key:value pair"),
              )
            }
          _ =>
            if c == 44 {
              self.state = Value
            } else if c == 93 {
              self.close()
            } else {
              return Some(json_error_character(c, "after array element"))
            }
        }
        return None
      }
      EndTop =>
        return if json_space(c) {
          None
        } else {
          Some(json_error_character(c, "after top-level value"))
        }
      Quoted => {
        if c == 34 {
          self.state = EndValue
        } else if c == 92 {
          self.state = Escape
        } else if c < 32 {
          return Some(json_error_character(c, "in string literal"))
        }
        return None
      }
      Escape => {
        self.state = match c {
          98 | 102 | 110 | 114 | 116 | 92 | 47 | 34 => Quoted
          117 => Hex(4)
          _ => return Some(json_error_character(c, "in string escape code"))
        }
        return None
      }
      Hex(left) => {
        if !((c >= 48 && c <= 57) ||
          (c >= 97 && c <= 102) ||
          (c >= 65 && c <= 70)) {
          return Some(
            json_error_character(c, "in \\u hexadecimal character escape"),
          )
        }
        self.state = if left == 1 { Quoted } else { Hex(left - 1) }
        return None
      }
      Negative => {
        self.state = if c == 48 {
          Zero
        } else if c >= 49 && c <= 57 {
          Integer
        } else {
          return Some(json_error_character(c, "in numeric literal"))
        }
        return None
      }
      Integer => {
        if c >= 48 && c <= 57 {
          return None
        }
        self.state = Zero
        continue
      }
      Zero => {
        if c == 46 {
          self.state = Dot
          return None
        }
        if c == 101 || c == 69 {
          self.state = Exponent
          return None
        }
        self.state = EndValue
        continue
      }
      Dot => {
        if c < 48 || c > 57 {
          return Some(
            json_error_character(c, "after decimal point in numeric literal"),
          )
        }
        self.state = Fraction
        return None
      }
      Fraction => {
        if c >= 48 && c <= 57 {
          return None
        }
        if c == 101 || c == 69 {
          self.state = Exponent
          return None
        }
        self.state = EndValue
        continue
      }
      Exponent => {
        self.state = ExponentSign
        if c == 43 || c == 45 {
          return None
        }
        continue
      }
      ExponentSign => {
        if c < 48 || c > 57 {
          return Some(json_error_character(c, "in exponent of numeric literal"))
        }
        self.state = ExponentDigits
        return None
      }
      ExponentDigits => {
        if c >= 48 && c <= 57 {
          return None
        }
        self.state = EndValue
        continue
      }
      Literal(word, index, name) => {
        if c != word[index] {
          return Some(
            json_error_character(
              c,
              "in literal " +
              name +
              " (expecting '" +
              word[index].to_int().to_char().unwrap().to_string() +
              "')",
            ),
          )
        }
        self.state = if index == word.length() - 1 {
          EndValue
        } else {
          Literal(word, index + 1, name)
        }
        return None
      }
    }
  } nobreak {
    abort("unreachable JSON diagnostic scanner")
  }
}

///|
fn Vm::json_syntax_error(
  self : Vm,
  source : String,
  loc : Location,
) -> String raise {
  let scanner : JsonDiagnosticScanner = { state: Value, stack: [], }
  for c in @utf8.encode(source) {
    self.tick(loc)
    if scanner.step(c) is Some(error) {
      return error
    }
  }
  if scanner.state is EndTop {
    return "invalid JSON"
  }
  if scanner.step(32) is Some(error) {
    return error
  }
  if scanner.state is EndTop {
    "invalid JSON"
  } else {
    "unexpected end of JSON input"
  }
}