///|
/// Raw SAX-style events. Conditions are validated and retained, never evaluated.
/// Returning false for BeginObject suppresses descendants but still validates
/// them and emits the matching EndObject. All other return values are ignored.
pub(all) enum StreamEvent {
  Pair(String, String)
  BeginObject(String)
  EndObject
  Condition(String, String)
} derive(Eq, Debug)

///|
pub extend StreamEvent with Eq::{equal, not_equal}

///|
pub extend StreamEvent with @debug.Debug::{to_repr}

///|
struct StreamParser {
  options : ParseOptions
  visitor : (StreamEvent) -> Bool raise
  mut mode : Int
  mut phase : Int
  mut key : String
  parents : Array[String]
  mut skip_depth : Int
  mut token : StringBuilder
  mut token_chars : Int
  mut count : Int
  mut offset : Int
  mut line : Int
  mut column : Int
  mut previous_cr : Bool
  mut assigned : Bool
  mut closed : Bool
  mut input_kind : Int
  mut pending : Bytes
}

///|
pub fn new_stream_parser(
  visitor : (StreamEvent) -> Bool raise,
  options? : ParseOptions = ParseOptions::default(),
) -> StreamParser raise ParseError {
  parse("", options~) |> ignore
  if options.conditions is Some(_) {
    raise ParseError({
      code: "invalid_options",
      message: "Stream events retain raw conditions; evaluation requires a document",
      offset: 0,
      line: 1,
      column: 1,
    })
  }
  {
    options,
    visitor,
    mode: 0,
    phase: 0,
    key: "",
    parents: [],
    skip_depth: 0,
    token: StringBuilder(),
    token_chars: 0,
    count: 0,
    offset: 0,
    line: 1,
    column: 1,
    previous_cr: false,
    assigned: false,
    closed: false,
    input_kind: 0,
    pending: b"",
  }
}

///|
fn StreamParser::error(
  self : StreamParser,
  code : String,
  message : String,
) -> Unit raise ParseError {
  self.closed = true
  raise ParseError({
    code,
    message,
    offset: self.offset,
    line: self.line,
    column: self.column,
  })
}

///|
pub fn StreamParser::entries_seen(self : StreamParser) -> Int {
  self.count
}

///|
pub fn StreamParser::characters_seen(self : StreamParser) -> Int {
  self.offset
}

///|
pub fn StreamParser::feed(self : StreamParser, chunk : String) -> Unit raise {
  if self.input_kind == 2 {
    self.error("input_mode", "Do not mix text and byte chunks")
  }
  self.input_kind = 1
  self.ingest(chunk)
}

///|
/// Strict incremental UTF-8, retaining at most three incomplete bytes between calls.
pub fn StreamParser::feed_bytes(
  self : StreamParser,
  chunk : Bytes,
) -> Unit raise {
  if self.closed {
    self.error("stream_closed", "Stream is closed")
  }
  if self.input_kind == 1 {
    self.error("input_mode", "Do not mix text and byte chunks")
  }
  self.input_kind = 2
  let bytes = self.pending + chunk
  let mut end = bytes.length()
  if end > 0 {
    let mut start = end - 1
    while start > 0 &&
          (bytes[start].to_int() & 0xc0) == 0x80 &&
          end - start <= 3 {
      start -= 1
    }
    let lead = bytes[start].to_int()
    let width = if lead >= 0xc2 && lead <= 0xdf {
      2
    } else if lead >= 0xe0 && lead <= 0xef {
      3
    } else if lead >= 0xf0 && lead <= 0xf4 {
      4
    } else {
      1
    }
    if end - start < width {
      end = start
    }
  }
  let text = @utf8.decode(bytes[:end], ignore_bom=false) catch {
    _ => {
      self.error("invalid_utf8", "Invalid UTF-8 chunk")
      return
    }
  }
  self.pending = bytes[end:].to_owned()
  self.ingest(text)
}

///|
fn StreamParser::ingest(self : StreamParser, chunk : String) -> Unit raise {
  if self.closed {
    self.error("stream_closed", "Stream is closed")
  }
  errdefer {
    self.closed = true
  }
  for c in chunk {
    if self.offset >= self.options.max_input_chars {
      self.error("input_limit", "Input exceeds max_input_chars")
    }
    if c.to_int() == 0 {
      self.error("nul_character", "NUL is not valid KV1 text")
    }
    if !(self.offset == 0 && c.to_int() == 0xfeff) {
      self.consume(c)
    }
    self.offset += 1
    if c == '\r' {
      self.line += 1
      self.column = 1
    } else if c == '\n' {
      if !self.previous_cr {
        self.line += 1
      }
      self.column = 1
    } else {
      self.column += 1
    }
    self.previous_cr = c == '\r'
  }
}

///|
fn StreamParser::append(self : StreamParser, c : Char) -> Unit raise ParseError {
  let limit = if self.mode == 6 { 4096 } else { self.options.max_token_chars }
  if self.token_chars >= limit {
    self.error("token_limit", "Stream token exceeds limit")
  }
  self.token.write_char(c)
  self.token_chars += 1
}

///|
fn StreamParser::emit(self : StreamParser, event : StreamEvent) -> Unit raise {
  if self.skip_depth == 0 {
    (self.visitor)(event) |> ignore
  }
}

///|
fn StreamParser::accept(self : StreamParser) -> Unit raise {
  let text = self.token.to_string()
  self.token = StringBuilder()
  self.token_chars = 0
  self.mode = 0
  if self.phase == 1 {
    self.emit(Pair(self.key, text))
    self.phase = 2
  } else {
    if self.count >= self.options.max_entries {
      self.error("entry_limit", "Input exceeds max_entries")
    }
    self.count += 1
    self.key = text
    self.phase = 1
    self.assigned = false
  }
}

///|
fn StreamParser::consume(self : StreamParser, c : Char) -> Unit raise {
  // At most one reprocessing step is needed for a delimiter or pending slash.
  let mut again = true
  while again {
    again = false
    match self.mode {
      4 => if c == '\n' || c == '\r' { self.mode = 0 }
      3 => {
        match c {
          'n' => self.append('\n')
          'r' => self.append('\r')
          't' => self.append('\t')
          '"' => self.append('"')
          '\\' => self.append('\\')
          _ => self.error("unknown_escape", "Unknown escaped character")
        }
        self.mode = 2
      }
      2 =>
        if c == '"' {
          self.accept()
        } else if c == '\\' && self.options.escape_sequences {
          self.mode = 3
        } else {
          self.append(c)
        }
      1 =>
        if is_space(c) || c == '{' || c == '}' || c == '=' {
          self.accept()
          again = true
        } else if c == '"' {
          self.error("unexpected_quote", "Quote must start at a token boundary")
        } else {
          self.append(c)
        }
      5 =>
        if c == '/' {
          self.mode = 4
        } else {
          self.mode = 1
          self.append('/')
          again = true
        }
      6 =>
        if c == ']' {
          let expression = self.token.to_string()
          let _ = evaluate_condition(expression, []) catch {
            InvalidCondition(message) => {
              self.error("invalid_condition", message)
              false
            }
          }
          self.emit(Condition(self.key, expression))
          self.token = StringBuilder()
          self.token_chars = 0
          self.mode = 0
        } else {
          self.append(c)
        }
      _ => {
        if is_space(c) {
          continue
        }
        match c {
          '/' => self.mode = 5
          '"' => self.mode = 2
          '=' => {
            if self.phase != 1 || self.assigned {
              self.error("expected_string", "Unexpected equals sign")
            }
            self.assigned = true
          }
          '[' => {
            if self.phase == 0 {
              self.error("invalid_condition", "Condition has no preceding key")
            }
            self.mode = 6
          }
          '{' => {
            if self.phase != 1 {
              self.error("expected_string", "Object has no key")
            }
            if self.parents.length() >= self.options.max_depth {
              self.error("depth_limit", "Object exceeds max_depth")
            }
            if self.skip_depth == 0 && !(self.visitor)(BeginObject(self.key)) {
              self.skip_depth = self.parents.length() + 1
            }
            self.parents.push(self.key)
            self.phase = 0
            self.key = ""
            self.assigned = false
          }
          '}' => {
            if self.phase == 1 {
              self.error("missing_value", "Key has no value")
            }
            if self.parents.is_empty() {
              self.error("unexpected_close", "Unexpected closing brace")
            }
            if self.skip_depth == self.parents.length() {
              self.skip_depth = 0
            }
            self.emit(EndObject)
            self.key = self.parents.pop().unwrap()
            self.phase = 2
          }
          _ => {
            self.mode = 1
            self.append(c)
          }
        }
      }
    }
  }
}

///|
pub fn StreamParser::finish(self : StreamParser) -> Unit raise {
  errdefer {
    self.closed = true
  }
  if self.closed {
    self.error("stream_closed", "Stream is closed")
  }
  if !self.pending.is_empty() {
    self.error("invalid_utf8", "Incomplete UTF-8 sequence")
  }
  if self.mode == 1 {
    self.accept()
  }
  if self.mode == 5 {
    self.mode = 1
    self.append('/')
    self.accept()
  }
  if self.mode == 2 || self.mode == 3 {
    self.error("unterminated_string", "Unterminated quoted string")
  }
  if self.mode == 6 {
    self.error("invalid_condition", "Unclosed conditional")
  }
  if self.phase == 1 {
    self.error("missing_value", "Key has no value")
  }
  if !self.parents.is_empty() {
    self.error("unclosed_object", "Object has no closing brace")
  }
  self.closed = true
}