// Companion program that runs the same battery of Seq test cases as
// tests/python_reference.py and emits results as JSON. The two outputs can
// be diffed to verify the MoonBit reimplementation matches BioPython.
//
// Run:  moon run cmd/main

///|
/// One test-case result: a name and its stringified result.
struct Case {
  name : String
  result : String
}

///|
/// Helper to record a case result.
fn record(cases : Array[Case], name : String, result : String) -> Unit {
  cases.push(Case::{ name, result, })
}

///|
/// Helper to record a Bool result as "True"/"False" (matching Python's repr).
fn record_bool(cases : Array[Case], name : String, result : Bool) -> Unit {
  record(cases, name, if result { "True" } else { "False" })
}

///|
/// Run every test case and return the list of results, mirroring
/// tests/python_reference.py.
fn run_cases() -> Array[Case] {
  let cases = Array(capacity=0)
  let s = @lib.Seq::new("ATGGCCATTGTAATGGGCCGCTGAAAGGGTGCCCGATAG")

  // --- Basic construction ---
  record(cases, "to_string", s.to_string())
  record(cases, "length", s.length().to_string())

  // --- complement / complement_rna ---
  record(cases, "complement_CGA", @lib.Seq::new("CGA").complement().to_string())
  record(
    cases,
    "complement_CGAUT",
    @lib.Seq::new("CGAUT").complement().to_string(),
  )
  record(
    cases,
    "complement_rna_CGAUT",
    @lib.Seq::new("CGAUT").complement_rna().to_string(),
  )
  record(
    cases,
    "complement_lower",
    @lib.Seq::new("cgaut").complement().to_string(),
  )
  record(
    cases,
    "complement_rna_lower",
    @lib.Seq::new("cgaut").complement_rna().to_string(),
  )

  // --- reverse_complement ---
  record(cases, "reverse_complement", s.reverse_complement().to_string())
  record(
    cases,
    "reverse_complement_rna",
    s.reverse_complement_rna().to_string(),
  )
  record(cases, "reverse_ACGT", @lib.Seq::new("ACGT").reverse().to_string())

  // --- transcribe / back_transcribe ---
  record(cases, "transcribe", s.transcribe().to_string())
  let rna = @lib.Seq::new("AUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAG")
  record(cases, "back_transcribe", rna.back_transcribe().to_string())

  // --- translate ---
  record(cases, "translate_default", translate_default(s))
  record(cases, "translate_to_stop", translate_to_stop(s))
  record(cases, "translate_stop_at", translate_stop_at(s, '@'))
  record(
    cases,
    "translate_partial",
    translate_default(@lib.Seq::new("ATGGCCAT")),
  )
  record(cases, "translate_TAN", translate_default(@lib.Seq::new("TAN")))
  record(cases, "translate_NNN", translate_default(@lib.Seq::new("NNN")))

  // Valid CDS
  let cds = @lib.Seq::new("ATGGCCATTGGCCTGAAAGGGTGCCCCCCGTAG")
  record(cases, "translate_cds", translate_cds(cds))

  // Error cases — mirror Python: try each operation, report exception class
  // name on error ("TranslationError"), or "NO_ERROR" if it surprisingly succeeds.
  record(cases, "translate_cds_bad_start", cds_bad_start())
  record(cases, "translate_cds_internal_stop", cds_internal_stop(s))
  record(cases, "translate_invalid_codon", invalid_codon())

  // --- upper / lower ---
  record(cases, "upper", @lib.Seq::new("acgtACGT").upper().to_string())
  record(cases, "lower", @lib.Seq::new("acgtACGT").lower().to_string())

  // --- count / count_overlap ---
  let c = @lib.Seq::new("ATATGAAATTTGAAAA")
  record(cases, "count_AAA", c.count("AAA").to_string())
  record(cases, "count_overlap_AAA", c.count_overlap("AAA").to_string())
  record(
    cases,
    "count_ATA_ATATA",
    @lib.Seq::new("ATATA").count("ATA").to_string(),
  )
  record(
    cases,
    "count_overlap_ATA_ATATA",
    @lib.Seq::new("ATATA").count_overlap("ATA").to_string(),
  )

  // --- find / rfind ---
  record(cases, "find_GCC", s.find("GCC").to_string())
  record(cases, "rfind_GCC", s.rfind("GCC").to_string())
  record(cases, "find_XYZ", s.find("XYZ").to_string())
  record(cases, "rfind_XYZ", s.rfind("XYZ").to_string())

  // --- startswith / endswith ---
  record_bool(cases, "startswith_ATG", s.startswith("ATG"))
  record_bool(cases, "endswith_TAG", s.endswith("TAG"))
  record_bool(cases, "startswith_GTG", s.startswith("GTG"))

  // --- contains ---
  record_bool(
    cases,
    "contains_AAA",
    @lib.Seq::new("ATATGAAATTTGAAAA").contains("AAA"),
  )
  record_bool(
    cases,
    "contains_XYZ",
    @lib.Seq::new("ATATGAAATTTGAAAA").contains("XYZ"),
  )

  // --- add / mul ---
  record(cases, "add", (@lib.Seq::new("AC") + @lib.Seq::new("GT")).to_string())
  record(cases, "mul3", @lib.Seq::new("AC").mul(3).to_string())

  // --- slice ---
  let sl = @lib.Seq::new("ATGGCCATTGTA")
  record(cases, "slice_1_4", sl.slice(start=1, end=4).to_string())
  record(cases, "slice_0_3", sl.slice(start=0, end=3).to_string())

  // --- strip / lstrip / rstrip ---
  record(
    cases,
    "strip_dash",
    @lib.Seq::new("---ACGT---").strip(chars="-").to_string(),
  )
  record(
    cases,
    "lstrip_dash",
    @lib.Seq::new("---ACGT---").lstrip(chars="-").to_string(),
  )
  record(
    cases,
    "rstrip_dash",
    @lib.Seq::new("---ACGT---").rstrip(chars="-").to_string(),
  )

  // --- replace ---
  record(
    cases,
    "replace_TU",
    @lib.Seq::new("ATGGCC").replace(old="T", new="U").to_string(),
  )

  // --- split ---
  let parts = @lib.Seq::new("ACGTACGTACGT").split("AC")
  record(cases, "split_count", parts.length().to_string())
  record(cases, "split_1", parts[1].to_string())

  // --- join ---
  let spacer = @lib.Seq::new("-")
  let join_parts = [
    @lib.Seq::new("AC"),
    @lib.Seq::new("GT"),
    @lib.Seq::new("TT"),
  ]
  record(cases, "join", spacer.join(join_parts).to_string())

  // --- equality ---
  record_bool(cases, "eq_same", @lib.Seq::new("ACGT") == @lib.Seq::new("ACGT"))
  record_bool(cases, "eq_diff", @lib.Seq::new("ACGT") == @lib.Seq::new("TTTT"))

  cases
}

///|
fn translate_default(s : @lib.Seq) -> String {
  s.translate().to_string() catch {
    _ => "ERROR"
  }
}

///|
fn translate_to_stop(s : @lib.Seq) -> String {
  s.translate(to_stop=true).to_string() catch {
    _ => "ERROR"
  }
}

///|
fn translate_stop_at(s : @lib.Seq, sym : Char) -> String {
  s.translate(stop_symbol=sym).to_string() catch {
    _ => "ERROR"
  }
}

///|
fn translate_cds(cds : @lib.Seq) -> String {
  cds.translate(cds=true).to_string() catch {
    _ => "ERROR"
  }
}

///|
/// CDS with a bad start codon (AAA instead of ATG). BioPython raises
/// TranslationError; MoonBit raises SeqError, which we report as
/// "TranslationError" so the JSON output matches.
fn cds_bad_start() -> String {
  try {
    ignore(@lib.Seq::new("AAACCCTAG").translate(cds=true))
    "NO_ERROR"
  } catch {
    _ => "TranslationError"
  }
}

///|
/// CDS with an internal stop codon. The sequence `s` contains TAA/TGA/TAG
/// in the middle, so `translate(cds=true)` must reject it.
fn cds_internal_stop(s : @lib.Seq) -> String {
  try {
    ignore(s.translate(cds=true))
    "NO_ERROR"
  } catch {
    _ => "TranslationError"
  }
}

///|
/// translate() with an invalid codon containing '?' — should raise.
fn invalid_codon() -> String {
  try {
    ignore(@lib.Seq::new("TA?").translate())
    "NO_ERROR"
  } catch {
    _ => "TranslationError"
  }
}

///|
fn main {
  // Run the Seq comparison tests (JSON array output for compare.sh)
  let cases = run_cases()
  // Output as JSON array
  let buf = StringBuilder()
  buf.write_char('[')
  for i, c in cases {
    if i > 0 {
      buf.write_char(',')
    }
    buf.write_string("{\"name\": \"")
    buf.write_string(c.name)
    buf.write_string("\", \"result\": \"")
    buf.write_string(c.result)
    buf.write_string("\"}")
  }
  buf.write_char(']')
  println(buf.to_string())
}