///|
/// Skip newline tokens
fn Parser::skip_newlines(self : Parser) -> Unit {
  let next_view = for view = self.view() {
    match view {
      [Newline(..), .. rest] => continue rest
      rest => break rest
    }
  }
  self.update_view(next_view)
}

///|
/// Try to consume a single bare key from the current position.
/// Handles identifiers, strings, integers, booleans (true/false),
/// and special float keywords (inf/nan) in key position.
///
/// The raw source text of numeric tokens is used as the key so that
/// underscores and other digit formatting are preserved (`1_0` stays
/// "1_0"). A leading '+' in the raw text means the token can never be a
/// bare key ('+' is not a bare-key character), so it is rejected here and
/// surfaces as an "Expected key" error.
fn Parser::try_parse_single_key(self : Parser) -> String? {
  match self.view() {
    [Identifier(name, ..), .. rest] =>
      if name.has_prefix("+") {
        None
      } else {
        self.update_view(rest)
        Some(name)
      }
    [StringToken(_, multiline=true, ..), ..] => None // multiline strings not allowed as keys
    [StringToken(name, ..), .. rest] => {
      self.update_view(rest)
      Some(name)
    }
    [IntegerToken(_, raw~, ..), .. rest] =>
      if raw.has_prefix("+") {
        None
      } else {
        self.update_view(rest)
        Some(raw)
      }
    [BooleanToken(b, ..), .. rest] => {
      self.update_view(rest)
      Some(if b { "true" } else { "false" })
    }
    [FloatToken(_, raw~, ..), .. rest] =>
      // Use the raw source text so that tokens like -10e-1 (which the
      // lexer parses as a float) can still serve as bare keys.
      if raw.has_prefix("+") {
        None
      } else {
        self.update_view(rest)
        Some(raw)
      }
    [DateTimeToken(LocalDate(s), ..), .. rest] => {
      // Only LocalDate (YYYY-MM-DD) is valid as a bare key — it only
      // contains digits and dashes. Other datetime variants contain
      // colons, T, Z, or offsets which are not bare key characters.
      self.update_view(rest)
      Some(s)
    }
    _ => None
  }
}

///|
/// Parse a primary value (string, number, boolean)
fn Parser::parse_value(
  self : Self,
  skip_newlines? : Bool = false,
) -> TomlValue raise {
  match self.view(skip_newlines~) {
    [StringToken(s, ..), .. rest] => {
      self.update_view(rest)
      TomlString(s)
    }
    [IntegerToken(i, raw~, ..), .. rest] => {
      // Numeric rules (underscore placement, Int64 range) apply only when
      // the token is used as a value; in key position the raw text is used
      // verbatim and never validated.
      @tokenize.validate_integer_literal(raw) catch {
        e => self.error(e.to_string())
      }
      self.update_view(rest)
      TomlInteger(i)
    }
    [FloatToken(f, raw~, ..), .. rest] => {
      // As above: dot/underscore rules apply to values only.
      @tokenize.validate_float_literal(raw) catch {
        e => self.error(e.to_string())
      }
      // Reject overflow: non-finite values are only valid with the
      // canonical spellings inf/nan/+inf/-inf/+nan/-nan.
      if (f.is_inf() || f.is_nan()) &&
        raw != "inf" &&
        raw != "-inf" &&
        raw != "+inf" &&
        raw != "nan" &&
        raw != "-nan" &&
        raw != "+nan" {
        self.error("Invalid float: \{raw}")
      }
      self.update_view(rest)
      TomlFloat(f)
    }
    [BooleanToken(b, ..), .. rest] => {
      self.update_view(rest)
      TomlBoolean(b)
    }
    [DateTimeToken(dt, ..), .. rest] => {
      self.update_view(rest)
      TomlDateTime(dt)
    }
    // TOML Array
    [LeftBracket, .. rest] => {
      self.update_view(rest)
      self.parse_array()
    }
    // TOML Inline Table
    [LeftBrace, .. rest] => {
      self.update_view(rest)
      self.parse_inline_table()
    }
    [Identifier("inf", ..), .. rest] => {
      self.update_view(rest)
      TomlFloat(1.0 / 0.0) // positive infinity
    }
    [Identifier("nan", ..), .. rest] => {
      self.update_view(rest)
      TomlFloat(0.0 / 0.0) // NaN
    }
    _ => self.error("Expected value")
  }
}

///|
/// Parse an array [1, 2, 3]
fn Parser::parse_array(self : Self) -> TomlValue raise {
  let values = []
  while true {
    if self.view(skip_newlines=true) is [RightBracket, .. rest] {
      self.update_view(rest)
      return TomlArray(values)
    }
    // self.skip_newlines() // here Newline is allowed
    values.push(self.parse_value(skip_newlines=true))
    match self.view(skip_newlines=true) {
      [Comma, .. rest] => self.update_view(rest)
      [RightBracket, .. rest] => {
        self.update_view(rest)
        break
      }
      _ => self.error("Expected ',' or ']' in array")
    }
  }
  TomlArray(values)
}

///|
/// Parse an inline table {key = value, key2 = value2}
/// No trailing comma allowed unlike Array
fn Parser::parse_inline_table(self : Parser) -> TomlValue raise {
  let table = Map([])
  self.skip_newlines() // TOML 1.1: allow newlines in inline tables
  if self.view() is [RightBrace, .. rest] {
    self.update_view(rest)
    table[inline_table_marker] = TomlBoolean(true)
    return TomlTable(table)
  }
  while true {
    self.skip_newlines()
    // Parse dotted key
    let key_path = self.parse_dotted_key()

    // Expect =
    match self.view() {
      [Equals, .. rest] => self.update_view(rest)
      _ => self.error("Expected '='")
    }

    // Parse value
    let value = self.parse_value()
    set_dotted_key_value(table, key_path, value) catch {
      error => self.error("\{error}")
    }
    self.skip_newlines()
    match self.view() {
      [Comma, .. rest] => {
        self.update_view(rest)
        self.skip_newlines()
        // Trailing comma: check for closing brace after comma
        if self.view() is [RightBrace, .. rest2] {
          self.update_view(rest2)
          break
        }
      }
      [RightBrace, .. rest] => {
        self.update_view(rest)
        break
      }
      _ => self.error("Expected ',' or '}' in inline table")
    }
  }
  table[inline_table_marker] = TomlBoolean(true)
  TomlTable(table)
}

///|
/// Parse a table path: section.subsection.key
fn Parser::parse_table_path(self : Parser) -> Array[String] raise {
  self.parse_dotted_key()
}

///|
fn[A] Parser::error(self : Parser, msg : String) -> A raise {
  match self.view() {
    [token, ..] => fail("\{msg} at \{@debug.to_string(token.loc())}")
    [] => fail("\{msg} at the end of input")
  }
}

///|
/// Parse a dotted key path: key.subkey.subsubkey
fn Parser::parse_dotted_key(self : Parser) -> Array[String] raise {
  let path = Array::new()

  // Parse first key - handle the case where the tokenizer saw a float like "1.2"
  match self.view() {
    [FloatToken(_, raw~, ..), .. rest] => {
      // Use the raw source text so dotted numeric keys like "1.2" split
      // correctly and tokens like -10e-1 are preserved verbatim.
      // A leading '+' is never valid in a bare key.
      if raw.has_prefix("+") {
        self.error("Expected key")
      }
      self.update_view(rest)
      match raw.split_once(".") {
        Some((left, right)) => {
          path.push(left.to_owned())
          path.push(right.to_owned())
        }
        None => path.push(raw)
      }
    }
    _ =>
      match self.try_parse_single_key() {
        Some(k) => path.push(k)
        None => self.error("Expected key")
      }
  }

  // Parse additional keys separated by dots
  let next_view = for view = self.view() {
    match view {
      [Dot(..), .. rest] => {
        self.update_view(rest)
        // Check for float token (e.g. 1.2 → keys "1" and "2")
        match self.view() {
          [FloatToken(_, raw~, ..), .. rest2] => {
            // A leading '+' is never valid in a bare key.
            if raw.has_prefix("+") {
              self.error("Expected key after dot")
            }
            self.update_view(rest2)
            let float_str = raw
            match float_str.split_once(".") {
              Some((left, right)) => {
                path.push(left.to_owned())
                path.push(right.to_owned())
              }
              None => path.push(float_str)
            }
          }
          _ =>
            match self.try_parse_single_key() {
              Some(k) => path.push(k)
              None => self.error("Expected key after dot")
            }
        }
        continue self.view()
      }
      rest => break rest
    }
  }
  self.update_view(next_view)
  path
}

///|
/// Parse a key-value pair with dotted key support: key.subkey = value
fn Parser::parse_key_value(self : Parser) -> (Array[String], TomlValue) raise {
  // Parse dotted key
  let key_path = self.parse_dotted_key()

  // Expect =
  match self.view() {
    [Equals, .. rest] => self.update_view(rest)
    _ => self.error("Expected '='")
  }

  // Parse value
  let value = self.parse_value()
  (key_path, value)
}

///|
/// Parse a TOML document with support for tables
/// Marker key used to track explicitly defined tables.
/// This is removed before returning the final result.
let table_defined_marker : String = "\u0000__defined__"

///|
/// Marker key for inline tables (immutable after definition).
let inline_table_marker : String = "\u0000__inline__"

///|
/// Marker for tables defined via [table] headers (distinct from dotted-key defined).
let header_defined_marker : String = "\u0000__header__"

///|
/// Parse a TOML document and return its root table as a `TomlValue`.
///
/// On success the result is always a `TomlTable` whose contents reflect the
/// document's top-level keys, `[section]` headers, and `[[array]]` of
/// tables. Standard TOML 1.0 plus 1.1 features (optional seconds, `\xHH`
/// escapes, inline-table newlines) are accepted.
///
/// On any lexical or syntactic error, `parse` raises with a message
/// containing the source location. Wrap the call in `try?` to receive a
/// `Result[TomlValue, Error]` instead.
pub fn parse(input : String) -> TomlValue raise {
  let tokens = @tokenize.tokenize(input)
  let parser = Parser::Parser(tokens)
  let main_table = Map([])
  for current_table = main_table {
    parser.skip_newlines()
    match parser.view() {
      [EOF, ..] => break
      [LeftBracket(loc=loc1), LeftBracket(loc=loc2), .. rest] => {
        if !loc1.adjacent(loc2) {
          parser.error("Invalid table header: space between '[' and '['")
        }
        parser.update_view(rest)
        let table_path = parser.parse_table_path()
        match parser.view() {
          [RightBracket(loc=rl1), RightBracket(loc=rl2), .. rest] => {
            if !rl1.adjacent(rl2) {
              parser.error("Expected ']]' (no space between brackets)")
            }
            parser.update_view(rest)
          }
          _ => parser.error("Expected ']]'")
        }
        // Validate newline/EOF/comment after ]]
        match parser.view() {
          [EOF, ..] | [Newline, ..] => ()
          _ => parser.error("Expected newline or end of file after ']]'")
        }
        // Create or append to the array of tables structure
        let current_table = create_array_of_tables(main_table, table_path) catch {
          error => parser.error("\{error}")
        }
        continue current_table
      }
      [LeftBracket, .. rest] => {
        parser.update_view(rest)
        let table_path = parser.parse_table_path()
        match parser.view() {
          [RightBracket, .. rest3] => parser.update_view(rest3)
          _ => parser.error("Expected ']'")
        }
        // Validate newline/EOF/comment after ]
        match parser.view() {
          [EOF, ..] | [Newline, ..] => ()
          _ => parser.error("Expected newline or end of file after ']'")
        }

        // Create or get the nested table structure
        let current_table = create_nested_table(main_table, table_path) catch {
          error => parser.error("\{error}")
        }
        // Check if this table was already explicitly defined
        if current_table.contains(table_defined_marker) {
          let path_str = table_path.join(".")
          parser.error("Duplicate table definition: [\{path_str}]")
        }
        current_table[table_defined_marker] = TomlBoolean(true)
        current_table[header_defined_marker] = TomlBoolean(true)
        continue current_table
      }
      _ => {
        // Parse key-value pair
        let (key_path, value) = parser.parse_key_value()
        // Tables created by dotted keys count as defined: they cannot be
        // reopened with [table] headers later (TOML 1.0 "tables cannot be
        // defined more than once"), and dotted keys in one section cannot
        // extend a table that a [table] header defined in another section.
        set_dotted_key_value(current_table, key_path, value, mark_implicit=true) catch {
          error => parser.error("\{error}")
        }
        // Validate that key-value pair is properly terminated
        match parser.view() {
          [EOF, ..] => () // EOF is valid
          [Newline, ..] => () // Newline is valid (comments are already skipped by tokenizer)
          _ =>
            parser.error("Expected newline or end of file after key-value pair")
        }
      }
    }
  }
  cleanup_markers(main_table)
  TomlTable(main_table)
}

///|
/// Remove internal marker keys from all tables recursively.
fn cleanup_markers(table : Map[String, TomlValue]) -> Unit {
  table.remove(table_defined_marker)
  table.remove(inline_table_marker)
  table.remove(header_defined_marker)
  for _, value in table {
    cleanup_value_markers(value)
  }
}

///|
fn cleanup_value_markers(value : TomlValue) -> Unit {
  match value {
    TomlTable(sub) => cleanup_markers(sub)
    TomlArray(arr) =>
      for item in arr {
        cleanup_value_markers(item)
      }
    _ => ()
  }
}