///|
/// CSV parsing error type
pub(all) enum CsvError {
  UnexpectedEof(Int, Int)
  UnterminatedQuote(Int, Int)
  InvalidQuoteEscape(Int, Int)
} derive(Show, Eq, ToJson)

///|
/// CSV parser configuration
pub(all) struct CsvConfig {
  delimiter : Char // Field delimiter (default: ',')
  quote : Char // Quote character (default: '"')
  skip_empty_lines : Bool // Skip empty lines (default: true)
} derive(Show, Eq)

///|
/// Default CSV configuration (comma-delimited, double-quote, skip empty lines)
pub fn CsvConfig::default() -> CsvConfig {
  { delimiter: ',', quote: '"', skip_empty_lines: true }
}

///|
/// Create a CSV configuration with custom delimiter
pub fn CsvConfig::with_delimiter(delimiter : Char) -> CsvConfig {
  { delimiter, quote: '"', skip_empty_lines: true }
}

///|
/// A parsed CSV row
pub type Row = Array[String]

///|
/// A parsed CSV document (array of rows)
pub type Document = Array[Row]

///|
/// Internal parser state
priv struct Parser {
  input : String
  mut pos : Int // Current position in input
  mut row : Int // Current row number (1-indexed)
  mut col : Int // Current column number (1-indexed)
  config : CsvConfig
}

///|
/// Create a new parser
fn Parser::new(input : String, config : CsvConfig) -> Parser {
  { input, pos: 0, row: 1, col: 1, config }
}

///|
/// Peek at current character without consuming
fn Parser::peek(self : Parser) -> Char? {
  if self.pos < self.input.length() {
    self.input.get_char(self.pos)
  } else {
    None
  }
}

///|
/// Consume and return current character
fn Parser::next(self : Parser) -> Char? {
  match self.peek() {
    Some(ch) => {
      self.pos += 1
      if ch == '\n' {
        self.row += 1
        self.col = 1
      } else {
        self.col += 1
      }
      Some(ch)
    }
    None => None
  }
}

///|
/// Check if we're at end of input
fn Parser::is_eof(self : Parser) -> Bool {
  self.pos >= self.input.length()
}

///|
/// Parse a quoted field
fn Parser::parse_quoted_field(self : Parser) -> Result[String, CsvError] {
  let quote = self.config.quote
  let buffer = @buffer.new()

  // Consume opening quote
  ignore(self.next())
  while true {
    match self.next() {
      None => return Err(CsvError::UnterminatedQuote(self.row, self.col))
      Some(ch) =>
        if ch == quote {
          // Check if this is an escaped quote (two quotes in a row)
          match self.peek() {
            Some(next_ch) if next_ch == quote => {
              buffer.write_char(quote)
              ignore(self.next()) // Consume second quote
              continue
            }
            _ => return Ok(buffer.to_string()) // End of quoted field
          }
        } else {
          buffer.write_char(ch)
          continue
        }
    }
  }
  // Unreachable, but needed for type checking
  Err(CsvError::UnterminatedQuote(self.row, self.col))
}

///|
/// Parse an unquoted field
fn Parser::parse_unquoted_field(self : Parser) -> String {
  let buffer = @buffer.new()
  let delimiter = self.config.delimiter
  while true {
    match self.peek() {
      None | Some('\n') | Some('\r') => break
      Some(ch) if ch == delimiter => break
      Some(ch) => {
        buffer.write_char(ch)
        ignore(self.next())
      }
    }
  }
  buffer.to_string()
}

///|
/// Parse a single field
fn Parser::parse_field(self : Parser) -> Result[String, CsvError] {
  match self.peek() {
    Some(ch) if ch == self.config.quote => self.parse_quoted_field()
    _ => Ok(self.parse_unquoted_field())
  }
}

///|
/// Parse a single row
fn Parser::parse_row(self : Parser) -> Result[Row, CsvError] {
  let fields : Array[String] = []
  while true {
    // Parse field
    let field = self.parse_field()
    match field {
      Err(e) => return Err(e)
      Ok(f) => fields.push(f)
    }

    // Check what comes next
    match self.peek() {
      None => return Ok(fields) // End of input
      Some('\n') => {
        ignore(self.next())
        return Ok(fields) // End of row
      }
      Some('\r') => {
        ignore(self.next())
        if self.peek() is Some('\n') {
          ignore(self.next())
        }
        return Ok(fields) // End of row (CRLF)
      }
      Some(ch) if ch == self.config.delimiter => {
        ignore(self.next()) // Consume delimiter
        continue // Parse next field
      }
      Some(_ch) =>
        // Unexpected character after field
        return Err(CsvError::InvalidQuoteEscape(self.row, self.col))
    }
  }
  // Unreachable, but needed for type checking
  Ok(fields)
}

///|
/// Skip empty lines
fn Parser::skip_empty_lines(self : Parser) -> Unit {
  while true {
    match self.peek() {
      Some('\n') => ignore(self.next())
      Some('\r') => {
        ignore(self.next())
        if self.peek() is Some('\n') {
          ignore(self.next())
        }
      }
      _ => break
    }
  }
}

///|
/// Parse entire CSV document
fn Parser::parse(self : Parser) -> Result[Document, CsvError] {
  let rows : Array[Row] = []
  while not(self.is_eof()) {
    // Skip empty lines if configured
    if self.config.skip_empty_lines {
      self.skip_empty_lines()
      if self.is_eof() {
        break
      }
    }

    // Parse row
    let row = self.parse_row()
    match row {
      Err(e) => return Err(e)
      Ok(r) =>
        // Only add non-empty rows if skip_empty_lines is true
        if not(self.config.skip_empty_lines) || r.length() > 0 {
          rows.push(r)
        }
    }
  }
  Ok(rows)
}

///|
/// Parse CSV string with default configuration
pub fn parse(input : String) -> Result[Document, CsvError] {
  let parser = Parser::new(input, CsvConfig::default())
  parser.parse()
}

///|
/// Parse CSV string with custom configuration
pub fn parse_with_config(
  input : String,
  config : CsvConfig,
) -> Result[Document, CsvError] {
  let parser = Parser::new(input, config)
  parser.parse()
}

///|
/// Parse CSV from comma-separated values
pub fn parse_csv(input : String) -> Result[Document, CsvError] {
  parse(input)
}

///|
/// Parse TSV (tab-separated values)
pub fn parse_tsv(input : String) -> Result[Document, CsvError] {
  parse_with_config(input, CsvConfig::with_delimiter('\t'))
}

///|
/// Convert a row to a CSV string
pub fn row_to_string(row : Row, config? : CsvConfig) -> String {
  let config = if config is Some(c) { c } else { CsvConfig::default() }
  let buffer = @buffer.new()
  for i = 0; i < row.length(); i = i + 1 {
    if i > 0 {
      buffer.write_char(config.delimiter)
    }
    let field = row[i]
    let needs_quote = field.contains(config.delimiter.to_string()) ||
      field.contains(config.quote.to_string()) ||
      field.contains("\n") ||
      field.contains("\r")
    if needs_quote {
      buffer.write_char(config.quote)
      for ch in field {
        if ch == config.quote {
          buffer.write_char(config.quote)
          buffer.write_char(config.quote)
        } else {
          buffer.write_char(ch)
        }
      }
      buffer.write_char(config.quote)
    } else {
      buffer.write_string(field)
    }
  }
  buffer.to_string()
}

///|
/// Convert a document to a CSV string
pub fn document_to_string(doc : Document, config? : CsvConfig) -> String {
  let config = if config is Some(c) { c } else { CsvConfig::default() }
  let buffer = @buffer.new()
  for i = 0; i < doc.length(); i = i + 1 {
    if i > 0 {
      buffer.write_char('\n')
    }
    buffer.write_string(row_to_string(doc[i], config~))
  }
  buffer.to_string()
}