///|
priv enum ParseState {
  FieldStart
  Unquoted
  Quoted
  AfterQuote
}

///|
/// CSV dialect options for parsing and writing related tabular text formats.
pub(all) struct CsvDialect {
  delimiter : Char
  newline : String
  skip_empty_lines : Bool
} derive(Eq, Debug)

///|
/// Header-aware table view over CSV rows.
pub(all) struct CsvTable {
  headers : Array[String]
  rows : Array[Array[String]]
} derive(Eq, Debug)

///|
/// Supported schema validation types.
#warnings("-unused_constructor")
pub(all) enum CsvColumnType {
  Text
  Integer
  Float
  Boolean
} derive(Eq, Debug)

///|
/// A single column validation rule.
#warnings("-struct_never_constructed")
pub(all) struct CsvColumnRule {
  name : String
  kind : CsvColumnType
  required : Bool
} derive(Eq, Debug)

///|
/// A validation issue with one-based CSV row numbering.
pub(all) struct CsvValidationError {
  row : Int
  column : String
  message : String
} derive(Eq, Debug)

///|
/// Inferred type for a profiled column.
pub(all) enum CsvInferredType {
  EmptyOnly
  IntegerColumn
  FloatColumn
  BooleanColumn
  TextColumn
} derive(Eq, Debug)

///|
/// Summary statistics for a single CSV column.
pub(all) struct ColumnProfile {
  name : String
  total : Int
  empty : Int
  non_empty : Int
  unique : Int
  inferred : CsvInferredType
  min : Double?
  max : Double?
  average : Double?
} derive(Eq, Debug)

///|
/// RFC-4180-style defaults used by `parse` and `stringify`.
pub fn default_dialect() -> CsvDialect {
  { delimiter: ',', newline: "\n", skip_empty_lines: false }
}

///|
/// Tab-separated value dialect.
pub fn tsv_dialect() -> CsvDialect {
  { delimiter: '\t', newline: "\n", skip_empty_lines: false }
}

///|
/// Semicolon-separated dialect used by some spreadsheet exports.
pub fn semicolon_dialect() -> CsvDialect {
  { delimiter: ';', newline: "\n", skip_empty_lines: false }
}

///|
/// Parse CSV text into rows and fields.
pub fn parse(input : String) -> Array[Array[String]] {
  parse_with_dialect(input, default_dialect())
}

///|
/// Parse delimited text with custom dialect options.
pub fn parse_with_dialect(
  input : String,
  dialect : CsvDialect,
) -> Array[Array[String]] {
  if input.is_empty() {
    return []
  }
  let rows : Array[Array[String]] = Array::new()
  let mut row : Array[String] = Array::new()
  let field = StringBuilder()
  let mut state = FieldStart
  let mut skip_lf = false
  let mut ended_with_row = false
  for ch in input.iter() {
    if skip_lf && ch == '\n' {
      skip_lf = false
      continue
    }
    skip_lf = false
    ended_with_row = false
    match state {
      FieldStart =>
        if ch == '"' {
          state = Quoted
        } else if ch == dialect.delimiter {
          row.push(field.to_string())
          field.reset()
        } else if ch == '\n' {
          let value = field.to_string()
          if !(dialect.skip_empty_lines && row.length() == 0 && value.is_empty()) {
            row.push(value)
            rows.push(row)
          }
          field.reset()
          row = Array::new()
          ended_with_row = true
        } else if ch == '\r' {
          let value = field.to_string()
          if !(dialect.skip_empty_lines && row.length() == 0 && value.is_empty()) {
            row.push(value)
            rows.push(row)
          }
          field.reset()
          row = Array::new()
          skip_lf = true
          ended_with_row = true
        } else {
          field.write_char(ch)
          state = Unquoted
        }
      Unquoted =>
        if ch == dialect.delimiter {
          row.push(field.to_string())
          field.reset()
          state = FieldStart
        } else if ch == '\n' {
          let value = field.to_string()
          if !(dialect.skip_empty_lines && row.length() == 0 && value.is_empty()) {
            row.push(value)
            rows.push(row)
          }
          field.reset()
          row = Array::new()
          state = FieldStart
          ended_with_row = true
        } else if ch == '\r' {
          let value = field.to_string()
          if !(dialect.skip_empty_lines && row.length() == 0 && value.is_empty()) {
            row.push(value)
            rows.push(row)
          }
          field.reset()
          row = Array::new()
          state = FieldStart
          skip_lf = true
          ended_with_row = true
        } else {
          field.write_char(ch)
        }
      Quoted =>
        if ch == '"' {
          state = AfterQuote
        } else {
          field.write_char(ch)
        }
      AfterQuote =>
        if ch == '"' {
          field.write_char('"')
          state = Quoted
        } else if ch == dialect.delimiter {
          row.push(field.to_string())
          field.reset()
          state = FieldStart
        } else if ch == '\n' {
          let value = field.to_string()
          if !(dialect.skip_empty_lines && row.length() == 0 && value.is_empty()) {
            row.push(value)
            rows.push(row)
          }
          field.reset()
          row = Array::new()
          state = FieldStart
          ended_with_row = true
        } else if ch == '\r' {
          let value = field.to_string()
          if !(dialect.skip_empty_lines && row.length() == 0 && value.is_empty()) {
            row.push(value)
            rows.push(row)
          }
          field.reset()
          row = Array::new()
          state = FieldStart
          skip_lf = true
          ended_with_row = true
        } else {
          field.write_char(ch)
          state = Unquoted
        }
    }
  }
  if !ended_with_row {
    let value = field.to_string()
    if !(dialect.skip_empty_lines && row.length() == 0 && value.is_empty()) {
      row.push(value)
      rows.push(row)
    }
  }
  rows
}

///|
/// Write rows and fields back to CSV text.
pub fn stringify(rows : Array[Array[String]]) -> String {
  stringify_with_dialect(rows, default_dialect())
}

///|
/// Write rows and fields back to delimited text with custom dialect options.
pub fn stringify_with_dialect(
  rows : Array[Array[String]],
  dialect : CsvDialect,
) -> String {
  let out = StringBuilder()
  for row_index in 0.. 0 {
      out.write_string(dialect.newline)
    }
    let row = rows[row_index]
    for field_index in 0.. 0 {
        out.write_char(dialect.delimiter)
      }
      write_field(out, row[field_index], dialect.delimiter)
    }
  }
  out.to_string()
}

///|
/// Parse CSV text as a header-aware table.
pub fn parse_table(input : String) -> CsvTable {
  parse_table_with_dialect(input, default_dialect())
}

///|
/// Parse delimited text as a header-aware table with custom dialect options.
pub fn parse_table_with_dialect(
  input : String,
  dialect : CsvDialect,
) -> CsvTable {
  let rows = parse_with_dialect(input, dialect)
  if rows.length() == 0 {
    return { headers: [], rows: [] }
  }
  let body : Array[Array[String]] = Array::new()
  for i in 1.. String? {
  if row_index < 0 || row_index >= table.rows.length() {
    return None
  }
  match column_index(table.headers, column) {
    Some(index) =>
      if index < table.rows[row_index].length() {
        Some(table.rows[row_index][index])
      } else {
        Some("")
      }
    None => None
  }
}

///|
/// Convert a header-aware table to a Markdown table.
pub fn table_to_markdown(table : CsvTable) -> String {
  let out = StringBuilder()
  write_markdown_row(out, table.headers)
  out.write_char('\n')
  let separator : Array[String] = Array::new()
  for _ in 0.. String {
  let out = StringBuilder()
  for row_index in 0.. 0 {
      out.write_char('\n')
    }
    let row = table.rows[row_index]
    out.write_char('{')
    for column_index in 0.. 0 {
        out.write_char(',')
      }
      out.write_char('"')
      write_json_escaped(out, table.headers[column_index])
      out.write_string("\":\"")
      if column_index < row.length() {
        write_json_escaped(out, row[column_index])
      }
      out.write_char('"')
    }
    out.write_char('}')
  }
  out.to_string()
}

///|
/// Validate a table against required columns and simple scalar types.
pub fn validate_table(
  table : CsvTable,
  rules : Array[CsvColumnRule],
) -> Array[CsvValidationError] {
  let errors : Array[CsvValidationError] = Array::new()
  for rule in rules {
    match column_index(table.headers, rule.name) {
      None =>
        errors.push({ row: 1, column: rule.name, message: "missing column" })
      Some(index) =>
        for row_index in 0.. String {
  if errors.length() == 0 {
    return "ok"
  }
  let out = StringBuilder()
  for i in 0.. 0 {
      out.write_char('\n')
    }
    let err = errors[i]
    out.write_string("row \{err.row}, \{err.column}: \{err.message}")
  }
  out.to_string()
}

///|
/// Profile each column with counts, uniqueness, inferred type, and numeric stats.
pub fn profile_table(table : CsvTable) -> Array[ColumnProfile] {
  let profiles : Array[ColumnProfile] = Array::new()
  for column in 0.. {
            numeric_count += 1
            sum += number
            min = Some(
              match min {
                Some(current) => if number < current { number } else { current }
                None => number
              },
            )
            max = Some(
              match max {
                Some(current) => if number > current { number } else { current }
                None => number
              },
            )
          }
          None => ()
        }
      }
    }
    let inferred = if non_empty == 0 {
      EmptyOnly
    } else if all_bool {
      BooleanColumn
    } else if all_int {
      IntegerColumn
    } else if all_float {
      FloatColumn
    } else {
      TextColumn
    }
    let average = if numeric_count > 0 {
      Some(sum / numeric_count.to_double())
    } else {
      None
    }
    profiles.push({
      name: table.headers[column],
      total: table.rows.length(),
      empty,
      non_empty,
      unique: values.length(),
      inferred,
      min,
      max,
      average,
    })
  }
  profiles
}

///|
/// Human-readable name for an inferred column type.
pub fn inferred_type_name(kind : CsvInferredType) -> String {
  match kind {
    EmptyOnly => "empty"
    IntegerColumn => "integer"
    FloatColumn => "float"
    BooleanColumn => "boolean"
    TextColumn => "text"
  }
}

///|
/// Render a compact data quality report for a header-aware table.
pub fn profile_report(table : CsvTable) -> String {
  let profiles = profile_table(table)
  let out = StringBuilder()
  out.write_string("rows: \{table.rows.length()}")
  out.write_char('\n')
  out.write_string("columns: \{table.headers.length()}")
  for profile in profiles {
    out.write_string(
      "\n- \{profile.name}: type=\{inferred_type_name(profile.inferred)}, empty=\{profile.empty}/\{profile.total}, unique=\{profile.unique}",
    )
    match profile.min {
      Some(value) => out.write_string(", min=\{value}")
      None => ()
    }
    match profile.max {
      Some(value) => out.write_string(", max=\{value}")
      None => ()
    }
    match profile.average {
      Some(value) => out.write_string(", avg=\{value}")
      None => ()
    }
  }
  out.to_string()
}

///|
fn write_field(out : StringBuilder, field : String, delimiter : Char) -> Unit {
  if needs_quotes(field, delimiter) {
    out.write_char('"')
    for ch in field.iter() {
      if ch == '"' {
        out.write_string("\"\"")
      } else {
        out.write_char(ch)
      }
    }
    out.write_char('"')
  } else {
    out.write_string(field)
  }
}

///|
fn needs_quotes(field : String, delimiter : Char) -> Bool {
  for ch in field.iter() {
    if ch == delimiter || ch == '"' || ch == '\n' || ch == '\r' {
      return true
    }
  }
  false
}

///|
fn column_index(headers : Array[String], column : String) -> Int? {
  for i in 0.. Array[String] {
  let result : Array[String] = Array::new()
  for i in 0.. Unit {
  out.write_string("|")
  for field in row {
    out.write_char(' ')
    write_markdown_cell(out, field)
    out.write_string(" |")
  }
}

///|
fn write_markdown_cell(out : StringBuilder, field : String) -> Unit {
  for ch in field.iter() {
    if ch == '|' {
      out.write_string("\\|")
    } else if ch == '\n' || ch == '\r' {
      out.write_char(' ')
    } else {
      out.write_char(ch)
    }
  }
}

///|
fn write_json_escaped(out : StringBuilder, value : String) -> Unit {
  for ch in value.iter() {
    if ch == '"' {
      out.write_string("\\\"")
    } else if ch == '\\' {
      out.write_string("\\\\")
    } else if ch == '\n' {
      out.write_string("\\n")
    } else if ch == '\r' {
      out.write_string("\\r")
    } else if ch == '\t' {
      out.write_string("\\t")
    } else {
      out.write_char(ch)
    }
  }
}

///|
fn matches_column_type(value : String, kind : CsvColumnType) -> Bool {
  match kind {
    Text => true
    Integer => is_int_value(value)
    Float => parse_double_option(value) is Some(_)
    Boolean => is_bool_value(value)
  }
}

///|
fn column_type_name(kind : CsvColumnType) -> String {
  match kind {
    Text => "text"
    Integer => "integer"
    Float => "float"
    Boolean => "boolean"
  }
}

///|
fn is_int_value(value : String) -> Bool {
  try @string.parse_int64(value) catch {
    _ => false
  } noraise {
    _ => true
  }
}

///|
fn parse_double_option(value : String) -> Double? {
  try @string.parse_double(value) catch {
    _ => None
  } noraise {
    number => Some(number)
  }
}

///|
fn is_bool_value(value : String) -> Bool {
  match value.to_lower() {
    "true" | "false" | "1" | "0" | "yes" | "no" | "on" | "off" => true
    _ => false
  }
}

///|
fn values_contains(values : Array[String], value : String) -> Bool {
  for item in values {
    if item == value {
      return true
    }
  }
  false
}