///|
/// Incremental ECMA-48 parser. It accepts arbitrary chunk boundaries, including
/// a split between ESC and `\\` in an ST terminator.
pub(all) struct Parser {
  mut mode : ParserMode
  mut text : Array[Char]
  mut sequence : Array[Char]
  mut payload : Array[Char]
  mut sequence_start : Int
  mut offset : Int
}

///|
pub fn Parser::new() -> Parser {
  {
    mode: Ground,
    text: [],
    sequence: [],
    payload: [],
    sequence_start: 0,
    offset: 0,
  }
}

///|
pub fn Parser::current_mode(self : Parser) -> ParserMode {
  self.mode
}

///|
pub fn Parser::consumed(self : Parser) -> Int {
  self.offset
}

///|
fn is_c0(c : Char) -> Bool {
  let n = c.to_int()
  n < 0x20 || n == 0x7f
}

///|
fn is_c1(c : Char) -> Bool {
  let n = c.to_int()
  n >= 0x80 && n <= 0x9f
}

///|
fn is_parameter_byte(c : Char) -> Bool {
  let n = c.to_int()
  n >= 0x30 && n <= 0x3f
}

///|
fn is_intermediate_byte(c : Char) -> Bool {
  let n = c.to_int()
  n >= 0x20 && n <= 0x2f
}

///|
fn is_final_byte(c : Char) -> Bool {
  let n = c.to_int()
  n >= 0x40 && n <= 0x7e
}

///|
fn chars_to_string(chars : Array[Char]) -> String {
  String::from_array(chars)
}

///|
fn Parser::flush_text(self : Parser, out : Array[Event]) -> Unit {
  if self.text.length() > 0 {
    let text = chars_to_string(self.text)
    out.push({
      kind: Text,
      raw: text,
      payload: text,
      final_byte: None,
      offset: self.offset - self.text.length(),
      complete: true,
    })
    self.text = []
  }
}

///|
fn Parser::begin_sequence(self : Parser, mode : ParserMode, c : Char) -> Unit {
  self.mode = mode
  self.sequence = [c]
  self.payload = []
  self.sequence_start = self.offset
}

///|
fn Parser::emit_sequence(
  self : Parser,
  out : Array[Event],
  kind : EventKind,
  final_byte : Char?,
  complete : Bool,
) -> Unit {
  out.push({
    kind,
    raw: chars_to_string(self.sequence),
    payload: chars_to_string(self.payload),
    final_byte,
    offset: self.sequence_start,
    complete,
  })
  self.mode = Ground
  self.sequence = []
  self.payload = []
}

///|
fn Parser::emit_single(self : Parser, out : Array[Event], c : Char) -> Unit {
  out.push({
    kind: if is_c1(c) {
      C1
    } else {
      C0
    },
    raw: String::from_array([c]),
    payload: "",
    final_byte: Some(c),
    offset: self.offset,
    complete: true,
  })
}

///|
fn Parser::consume_ground(self : Parser, out : Array[Event], c : Char) -> Unit {
  if c == '\u{1b}' {
    self.flush_text(out)
    self.begin_sequence(EscapeStart, c)
  } else if c == '\u{9b}' {
    self.flush_text(out)
    self.begin_sequence(CsiEntry, c)
  } else if c == '\u{9d}' {
    self.flush_text(out)
    self.begin_sequence(OscString, c)
  } else if c == '\u{90}' {
    self.flush_text(out)
    self.begin_sequence(DcsString, c)
  } else if c == '\u{98}' {
    self.flush_text(out)
    self.begin_sequence(SosString, c)
  } else if c == '\u{9e}' {
    self.flush_text(out)
    self.begin_sequence(PmString, c)
  } else if c == '\u{9f}' {
    self.flush_text(out)
    self.begin_sequence(ApcString, c)
  } else if is_c0(c) || is_c1(c) {
    if c == '\n' || c == '\t' {
      self.text.push(c)
    } else {
      self.flush_text(out)
      self.emit_single(out, c)
    }
  } else {
    self.text.push(c)
  }
}

///|
fn Parser::consume_escape(self : Parser, out : Array[Event], c : Char) -> Unit {
  self.sequence.push(c)
  if c == '[' {
    self.mode = CsiEntry
  } else if c == ']' {
    self.mode = OscString
  } else if c == 'P' {
    self.mode = DcsString
  } else if c == 'X' {
    self.mode = SosString
  } else if c == '^' {
    self.mode = PmString
  } else if c == '_' {
    self.mode = ApcString
  } else if is_intermediate_byte(c) {
    self.payload.push(c)
    self.mode = EscapeIntermediate
  } else if is_final_byte(c) {
    self.emit_sequence(out, Escape, Some(c), true)
  } else {
    self.payload.push(c)
    self.emit_sequence(out, Invalid, None, false)
  }
}

///|
fn Parser::consume_escape_intermediate(
  self : Parser,
  out : Array[Event],
  c : Char,
) -> Unit {
  self.sequence.push(c)
  if is_intermediate_byte(c) {
    self.payload.push(c)
  } else if is_final_byte(c) {
    self.emit_sequence(out, Escape, Some(c), true)
  } else {
    self.payload.push(c)
    self.emit_sequence(out, Invalid, None, false)
  }
}

///|
fn Parser::consume_csi(self : Parser, out : Array[Event], c : Char) -> Unit {
  self.sequence.push(c)
  match self.mode {
    CsiEntry | CsiParam =>
      if is_parameter_byte(c) {
        self.payload.push(c)
        self.mode = CsiParam
      } else if is_intermediate_byte(c) {
        self.payload.push(c)
        self.mode = CsiIntermediate
      } else if is_final_byte(c) {
        self.emit_sequence(out, Csi, Some(c), true)
      } else if is_c0(c) {
        // Embedded format effectors do not terminate a CSI. Retaining them in
        // `raw` makes the event lossless while omitting them from parameters.
        ()
      } else {
        self.payload.push(c)
        self.emit_sequence(out, Invalid, None, false)
      }
    CsiIntermediate =>
      if is_intermediate_byte(c) {
        self.payload.push(c)
      } else if is_final_byte(c) {
        self.emit_sequence(out, Csi, Some(c), true)
      } else {
        self.payload.push(c)
        self.emit_sequence(out, Invalid, None, false)
      }
    _ => ()
  }
}

///|
fn Parser::consume_osc(self : Parser, out : Array[Event], c : Char) -> Unit {
  self.sequence.push(c)
  if c == '\u{7}' {
    self.emit_sequence(out, Osc, None, true)
  } else if c == '\u{9c}' {
    self.emit_sequence(out, Osc, None, true)
  } else if c == '\u{1b}' {
    self.mode = OscEscape
  } else {
    self.payload.push(c)
  }
}

///|
fn Parser::consume_osc_escape(
  self : Parser,
  out : Array[Event],
  c : Char,
) -> Unit {
  self.sequence.push(c)
  if c == '\\' {
    self.emit_sequence(out, Osc, None, true)
  } else {
    self.payload.push('\u{1b}')
    self.payload.push(c)
    self.mode = OscString
  }
}

///|
fn string_kind(mode : ParserMode) -> EventKind {
  match mode {
    DcsString | DcsEscape => Dcs
    SosString | SosEscape => Sos
    PmString | PmEscape => Pm
    ApcString | ApcEscape => Apc
    _ => Invalid
  }
}

///|
fn string_escape_mode(mode : ParserMode) -> ParserMode {
  match mode {
    DcsString => DcsEscape
    SosString => SosEscape
    PmString => PmEscape
    ApcString => ApcEscape
    _ => mode
  }
}

///|
fn string_body_mode(mode : ParserMode) -> ParserMode {
  match mode {
    DcsEscape => DcsString
    SosEscape => SosString
    PmEscape => PmString
    ApcEscape => ApcString
    _ => mode
  }
}

///|
fn Parser::consume_control_string(
  self : Parser,
  out : Array[Event],
  c : Char,
) -> Unit {
  let old_mode = self.mode
  self.sequence.push(c)
  match old_mode {
    DcsEscape | SosEscape | PmEscape | ApcEscape =>
      if c == '\\' {
        self.emit_sequence(out, string_kind(old_mode), None, true)
      } else {
        self.payload.push('\u{1b}')
        self.payload.push(c)
        self.mode = string_body_mode(old_mode)
      }
    _ =>
      if c == '\u{9c}' {
        self.emit_sequence(out, string_kind(old_mode), None, true)
      } else if c == '\u{1b}' {
        self.mode = string_escape_mode(old_mode)
      } else {
        self.payload.push(c)
      }
  }
}

///|
/// Feed a chunk and return all events completed by that chunk. Text is held
/// until a control boundary or `finish`, so adjacent chunks coalesce naturally.
pub fn Parser::feed(self : Parser, chunk : String) -> Array[Event] {
  let out : Array[Event] = []
  for c in chunk {
    match self.mode {
      Ground => self.consume_ground(out, c)
      EscapeStart => self.consume_escape(out, c)
      EscapeIntermediate => self.consume_escape_intermediate(out, c)
      CsiEntry | CsiParam | CsiIntermediate => self.consume_csi(out, c)
      OscString => self.consume_osc(out, c)
      OscEscape => self.consume_osc_escape(out, c)
      DcsString
      | DcsEscape
      | SosString
      | SosEscape
      | PmString
      | PmEscape
      | ApcString
      | ApcEscape => self.consume_control_string(out, c)
    }
    self.offset += 1
  }
  out
}

///|
/// Flush remaining text and turn an unfinished control string into a Truncated
/// event. Calling `finish` resets the parser to Ground and is idempotent.
pub fn Parser::finish(self : Parser) -> Array[Event] {
  let out : Array[Event] = []
  if self.mode == Ground {
    self.flush_text(out)
  } else {
    self.emit_sequence(out, Truncated, None, false)
  }
  out
}

///|
/// Convenience batch parser.
pub fn parse(input : String) -> Array[Event] {
  let parser = Parser::new()
  let events = parser.feed(input)
  for event in parser.finish() {
    events.push(event)
  }
  events
}