// Copyright 2026 moonbit-toml contributors
//
// SPDX-License-Identifier: MIT

///|
/// Parses the four TOML string kinds starting at an opening quote.
/// `"""` / `'''` select the multi-line variants.
fn Scanner::parse_string_value(self : Scanner) -> String raise ParseError {
  let c = match self.peek() {
    Some(c) => c
    None => raise self.error_here(UnexpectedEof(self.here()))
  }
  if c == '"' {
    let multiline = self.peek_at(1) == Some('"') && self.peek_at(2) == Some('"')
    self.parse_basic_string(multiline)
  } else if c == '\'' {
    let multiline = self.peek_at(1) == Some('\'') &&
      self.peek_at(2) == Some('\'')
    self.parse_literal_string(multiline)
  } else {
    raise self.error_here(InvalidValue(self.here(), c))
  }
}

///|
/// Parses a basic string (`"..."` or `"""..."""` with escapes).
/// The scanning position must be at the opening quote.
fn Scanner::parse_basic_string(
  self : Scanner,
  multiline : Bool,
) -> String raise ParseError {
  ignore(self.bump()) // opening quote
  let sb = StringBuilder::StringBuilder()
  if multiline {
    ignore(self.bump()) // second quote
    ignore(self.bump()) // third quote
    // A newline immediately after the opening delimiter is trimmed.
    if self.peek() == Some('\r') {
      if self.bump_str("\r\n") {
        self.line += 1
        self.col = 1
      }
    } else if self.peek() == Some('\n') {
      ignore(self.bump())
      self.line += 1
      self.col = 1
    }
    while true {
      let c = match self.peek() {
        Some(c) => c
        None => raise self.error_here(UnterminatedString(self.here()))
      }
      if c == '"' {
        // Count the run of quotes: a run of 3..5 closes the string and the
        // surplus quotes (up to two) belong to the content. Longer runs of
        // quotes with no content in between cannot appear in a valid body.
        let mut run = 0
        while self.peek() == Some('"') {
          ignore(self.bump())
          run += 1
        }
        if run >= 3 {
          if run > 5 {
            raise self.error_here(
              InvalidSyntax(self.here(), "too many consecutive quotes"),
            )
          }
          for _i in 0..<(run - 3) {
            sb.write_char('"')
          }
          return sb.to_string()
        }
        for _i in 0.. c
        None => raise self.error_here(UnterminatedString(self.here()))
      }
      if c == '"' {
        ignore(self.bump())
        return sb.to_string()
      } else if c == '\\' {
        self.parse_escape(sb, multiline=false)
      } else if c == '\n' || c == '\r' {
        raise self.error_here(
          InvalidSyntax(
            self.here(),
            "newline is not allowed in a single-line string",
          ),
        )
      } else if is_forbidden_control(c) {
        raise self.error_here(UnexpectedChar(self.here(), c))
      } else {
        ignore(self.bump())
        sb.write_char(c)
      }
    }
    abort("unreachable")
  }
}

///|
/// Parses a literal string (`'...'` or `'''...'''`, no escapes).
fn Scanner::parse_literal_string(
  self : Scanner,
  multiline : Bool,
) -> String raise ParseError {
  ignore(self.bump()) // opening quote
  let sb = StringBuilder::StringBuilder()
  if multiline {
    ignore(self.bump())
    ignore(self.bump())
    if self.peek() == Some('\r') {
      if self.bump_str("\r\n") {
        self.line += 1
        self.col = 1
      }
    } else if self.peek() == Some('\n') {
      ignore(self.bump())
      self.line += 1
      self.col = 1
    }
    while true {
      let c = match self.peek() {
        Some(c) => c
        None => raise self.error_here(UnterminatedString(self.here()))
      }
      if c == '\'' {
        let mut run = 0
        while self.peek() == Some('\'') {
          ignore(self.bump())
          run += 1
        }
        if run >= 3 {
          if run > 5 {
            raise self.error_here(
              InvalidSyntax(self.here(), "too many consecutive quotes"),
            )
          }
          for _i in 0..<(run - 3) {
            sb.write_char('\'')
          }
          return sb.to_string()
        }
        for _i in 0.. c
        None => raise self.error_here(UnterminatedString(self.here()))
      }
      if c == '\'' {
        ignore(self.bump())
        return sb.to_string()
      } else if c == '\n' || c == '\r' {
        raise self.error_here(
          InvalidSyntax(
            self.here(),
            "newline is not allowed in a single-line string",
          ),
        )
      } else if is_forbidden_control(c) {
        raise self.error_here(UnexpectedChar(self.here(), c))
      } else {
        ignore(self.bump())
        sb.write_char(c)
      }
    }
    abort("unreachable")
  }
}

///|
/// Parses one escape sequence; the scanning position is at the backslash.
/// In multi-line strings `\` followed by whitespace up to a newline trims
/// that whitespace and all following whitespace ("line ending backslash").
fn Scanner::parse_escape(
  self : Scanner,
  sb : StringBuilder,
  multiline~ : Bool,
) -> Unit raise ParseError {
  let bs_pos = self.here()
  ignore(self.bump()) // backslash
  let c = match self.peek() {
    Some(c) => c
    None => raise self.error_here(UnterminatedString(bs_pos))
  }
  match c {
    'b' => {
      ignore(self.bump())
      sb.write_char('\u{0008}')
    }
    't' => {
      ignore(self.bump())
      sb.write_char('\t')
    }
    'n' => {
      ignore(self.bump())
      sb.write_char('\n')
    }
    'f' => {
      ignore(self.bump())
      sb.write_char('\u{000C}')
    }
    'r' => {
      ignore(self.bump())
      sb.write_char('\r')
    }
    '"' => {
      ignore(self.bump())
      sb.write_char('"')
    }
    '\\' => {
      ignore(self.bump())
      sb.write_char('\\')
    }
    'u' => {
      ignore(self.bump())
      sb.write_char(self.parse_unicode_escape(4))
    }
    'U' => {
      ignore(self.bump())
      sb.write_char(self.parse_unicode_escape(8))
    }
    _ =>
      if multiline {
        // Line-ending backslash: `\` followed by only whitespace up to a
        // newline trims the whitespace, the newline and all whitespace up
        // to the next non-whitespace character.
        let m = self.mark()
        self.skip_inline_ws()
        if self.peek() == Some('\r') || self.peek() == Some('\n') {
          ignore(self.newline())
          while true {
            match self.peek() {
              Some(' ') | Some('\t') => ignore(self.bump())
              Some('\n') => {
                ignore(self.bump())
                self.line += 1
                self.col = 1
              }
              Some('\r') => ignore(self.newline())
              _ => break
            }
          }
          return
        }
        self.reset(m)
        raise self.error_here(InvalidEscape(bs_pos, c))
      } else {
        raise self.error_here(InvalidEscape(bs_pos, c))
      }
  }
}

///|
/// Parses a `\uXXXX` or `\UXXXXXXXX` escape (the marker already consumed).
fn Scanner::parse_unicode_escape(
  self : Scanner,
  digits : Int,
) -> Char raise ParseError {
  let start = self.pos
  let mut value : Int64 = 0L
  for _i in 0.. c
      None =>
        raise self.error_here(
          InvalidUnicodeEscape(self.here(), self.text_since(start)),
        )
    }
    if !is_hex_digit(c) {
      raise self.error_here(
        InvalidUnicodeEscape(self.here(), self.text_since(start)),
      )
    }
    value = value * 16L + Int64::from_int(hex_value(c))
    if value > 0x10FFFFL {
      raise self.error_here(
        InvalidUnicodeEscape(self.here(), self.text_since(start)),
      )
    }
    ignore(self.bump())
  }
  // Surrogates are not Unicode scalar values.
  if value >= 0xD800L && value <= 0xDFFFL {
    raise self.error_here(
      InvalidUnicodeEscape(self.here(), self.text_since(start)),
    )
  }
  value.to_int().to_char().unwrap_or('\u{FFFD}')
}