///|
priv struct Parser {
  chars : Array[Char]
  options : ParseOptions
  mut pos : Int
  mut line : Int
  mut column : Int
  mut entry_count : Int
  allow_directives : Bool
  capture : Bool
  path : Array[Int]
  spans : Array[SourceSpan]
  tracing : Bool
  trace_stack : Array[Array[TraceNode]]
  mut last_trace : Array[TraceNode]
}

///|
/// Parse the documented KV1 text subset; unsupported file-loading directives
/// and platform conditionals are rejected rather than silently misinterpreted.
pub fn parse(
  input : String,
  options? : ParseOptions = ParseOptions::default(),
) -> Document raise ParseError {
  parse_internal(input, options, false).0
}

///|
fn parse_internal(
  input : String,
  options : ParseOptions,
  allow_directives : Bool,
) -> (Document, Int) raise ParseError {
  let (doc, count, _) = parse_with_spans(
    input, options, allow_directives, false,
  )
  (doc, count)
}

///|
fn parse_with_spans(
  input : String,
  options : ParseOptions,
  allow_directives : Bool,
  capture : Bool,
  trace? : Array[TraceNode]? = None,
) -> (Document, Int, Array[SourceSpan]) raise ParseError {
  if options.max_depth < 0 ||
    options.max_depth > 256 ||
    options.max_entries < 0 ||
    options.max_input_chars < 0 ||
    options.max_token_chars < 0 {
    raise ParseError({
      code: "invalid_options",
      message: "Limits must be nonnegative; max_depth must be at most 256",
      offset: 0,
      line: 1,
      column: 1,
    })
  }
  if input.char_length() > options.max_input_chars {
    raise ParseError({
      code: "input_limit",
      message: "Input exceeds max_input_chars",
      offset: 0,
      line: 1,
      column: 1,
    })
  }
  let p = Parser::{
    chars: input.to_array(),
    options,
    pos: 0,
    line: 1,
    column: 1,
    entry_count: 0,
    allow_directives,
    capture,
    path: [],
    spans: [],
    tracing: trace is Some(_),
    trace_stack: [],
    last_trace: [],
  }
  if p.chars.get(0) is Some(c) && c.to_int() == 0xFEFF {
    p.advance()
  }
  let doc = Document::{ entries: p.read_entries(0, false), }
  if trace is Some(output) {
    for node in p.last_trace {
      output.push(node)
    }
  }
  (doc, p.entry_count, p.spans)
}

///|
fn Parser::fail(
  self : Parser,
  code : String,
  message : String,
) -> Unit raise ParseError {
  raise ParseError({
    code,
    message,
    offset: self.pos,
    line: self.line,
    column: self.column,
  })
}

///|
fn Parser::peek(self : Parser) -> Char? {
  self.chars.get(self.pos)
}

///|
fn Parser::advance(self : Parser) -> Unit {
  guard self.pos < self.chars.length() else { return }
  let c = self.chars[self.pos]
  self.pos = self.pos + 1
  if c == '\r' {
    self.line = self.line + 1
    self.column = 1
  } else if c == '\n' {
    if self.pos < 2 || self.chars[self.pos - 2] != '\r' {
      self.line = self.line + 1
    }
    self.column = 1
  } else {
    self.column = self.column + 1
  }
}

///|
fn is_space(c : Char) -> Bool {
  c == ' ' ||
  c == '\t' ||
  c == '\r' ||
  c == '\n' ||
  c.to_int() == 0x0B ||
  c.to_int() == 0x0C
}

///|
fn Parser::skip_trivia(self : Parser) -> Unit raise ParseError {
  while self.pos < self.chars.length() {
    let c = self.chars[self.pos]
    if c.to_int() == 0 {
      self.fail("nul_character", "NUL characters are not valid KV1 text")
    } else if is_space(c) {
      self.advance()
    } else if c == '/' && self.chars.get(self.pos + 1) == Some('/') {
      while self.peek() is Some(ch) && ch != '\r' && ch != '\n' {
        self.advance()
      }
    } else {
      break
    }
  }
}

///|
fn Parser::read_string(self : Parser) -> String raise ParseError {
  self.skip_trivia()
  guard self.peek() is Some(first) else {
    self.fail("expected_string", "Expected a key or string value")
    return ""
  }
  if first == '{' || first == '}' {
    self.fail("expected_string", "Expected a string, found an object delimiter")
  }
  let quoted = first == '"'
  if quoted {
    self.advance()
  }
  let builder = StringBuilder()
  let mut count = 0
  while self.peek() is Some(c) {
    if quoted && c == '"' {
      self.advance()
      return builder.to_string()
    }
    if !quoted && (is_space(c) || c == '{' || c == '}' || c == '=') {
      return builder.to_string()
    }
    if c.to_int() == 0 {
      self.fail("nul_character", "NUL characters are not valid KV1 text")
    }
    if !quoted && c == '"' {
      self.fail(
        "unexpected_quote", "A quoted string must begin at a token boundary",
      )
    }
    if count >= self.options.max_token_chars {
      self.fail("token_limit", "String exceeds max_token_chars")
    }
    if quoted && c == '\\' && self.options.escape_sequences {
      self.advance()
      guard self.peek() is Some(escaped) else {
        self.fail("unterminated_string", "Backslash at end of quoted string")
        return ""
      }
      match escaped {
        'n' => builder.write_char('\n')
        'r' => builder.write_char('\r')
        't' => builder.write_char('\t')
        '\\' => builder.write_char('\\')
        '"' => builder.write_char('"')
        _ =>
          self.fail(
            "unknown_escape", "Unknown escape; use literal mode for unescaped paths",
          )
      }
      self.advance()
    } else {
      builder.write_char(c)
      self.advance()
    }
    count = count + 1
  }
  if quoted {
    self.fail(
      "unterminated_string", "Quoted string is missing its closing quote",
    )
  }
  builder.to_string()
}

///|
fn Parser::read_entries(
  self : Parser,
  depth : Int,
  nested : Bool,
) -> Array[Entry] raise ParseError {
  let entries : Array[Entry] = []
  let traces : Array[TraceNode] = []
  if self.tracing {
    self.trace_stack.push(traces)
  }
  while true {
    self.skip_trivia()
    match self.peek() {
      None => {
        if nested {
          self.fail("unclosed_object", "Object is missing its closing brace")
        }
        break
      }
      Some('}') => {
        if !nested {
          self.fail("unexpected_close", "Closing brace has no matching object")
        }
        self.advance()
        break
      }
      Some('[') =>
        self.fail(
          "unsupported_conditional", "Platform conditionals are not supported",
        )
      _ => ()
    }
    if self.entry_count >= self.options.max_entries {
      self.fail("entry_limit", "Document exceeds max_entries")
    }
    let key_start = self.pos
    let key_line = self.line
    let key_column = self.column
    let key = self.read_string()
    let key_end = self.pos
    if key.is_empty() && self.peek() == Some('=') {
      self.fail("expected_string", "Unexpected equals sign")
    }
    if depth == 0 &&
      !self.allow_directives &&
      (
        key.equal_ignore_ascii_case("#include") ||
        key.equal_ignore_ascii_case("#base")
      ) {
      self.fail(
        "unsupported_directive", "Include/base directives require an explicit file resolver",
      )
    }
    self.entry_count = self.entry_count + 1
    self.skip_trivia()
    let mut keep = true
    while self.peek() == Some('[') {
      keep = self.read_condition()
      self.skip_trivia()
    }
    let mut override_previous = false
    if self.peek() == Some('=') {
      self.advance()
      self.skip_trivia()
      while self.peek() == Some('[') {
        keep = self.read_condition()
        override_previous = true
        self.skip_trivia()
      }
    }
    let value_start = self.pos
    if self.capture {
      self.path.push(entries.length())
    }
    let value = match self.peek() {
      Some('{') => {
        if depth >= self.options.max_depth {
          self.fail("depth_limit", "Document exceeds max_depth")
        }
        self.advance()
        Value::Object(self.read_entries(depth + 1, true))
      }
      None | Some('}') => {
        self.fail("missing_value", "Key has no value")
        Value::Text("")
      }
      _ => Value::Text(self.read_string())
    }
    let value_end = self.pos
    let children = if value is Object(_) && self.tracing {
      self.last_trace
    } else {
      []
    }
    let mut entry_end = value_end
    self.skip_trivia()
    while self.peek() == Some('[') {
      keep = self.read_condition()
      entry_end = self.pos
      self.skip_trivia()
    }
    if self.capture {
      self.spans.push({
        path: self.path.map(i => i),
        key_start,
        key_end,
        value_start,
        value_end,
        entry_end,
      })
      self.path.pop() |> ignore
    }
    if keep {
      if override_previous && !self.capture {
        let mut index = 0
        while index < entries.length() {
          if entries[index].key.equal_ignore_ascii_case(key) {
            entries.remove(index) |> ignore
            if self.tracing {
              traces.remove(index) |> ignore
            }
          } else {
            index += 1
          }
        }
      }
      entries.push({ key, value, })
      if self.tracing {
        traces.push({
          offset: key_start,
          line: key_line,
          column: key_column,
          children,
        })
      }
    }
  }
  if self.tracing {
    self.last_trace = self.trace_stack.pop().unwrap()
  }
  entries
}

///|
fn Parser::read_condition(self : Parser) -> Bool raise ParseError {
  let symbols = match self.options.conditions {
    Some(symbols) => symbols
    None if self.capture => []
    None => {
      self.fail(
        "unsupported_conditional", "Pass an explicit conditions array to evaluate platform conditions",
      )
      return false
    }
  }
  self.advance()
  let text = StringBuilder()
  let mut count = 0
  while self.peek() is Some(c) && c != ']' {
    if count >= 4096 {
      self.fail("condition_limit", "Conditional exceeds 4096 characters")
    }
    text.write_char(c)
    count += 1
    self.advance()
  }
  if self.peek() != Some(']') {
    self.fail("invalid_condition", "Missing closing bracket")
  }
  self.advance()
  let result = evaluate_condition(text.to_string(), symbols) catch {
    InvalidCondition(message) => {
      self.fail("invalid_condition", message)
      false
    }
  }
  if self.capture {
    true
  } else {
    result
  }
}