// `mtsc conformance` — corpus accuracy, without the test harness.
//
// It replicates the parser package's "checker: accuracy against
// TypeScript conformance baselines" whitebox test. That test bundles
// every parser test into a ~25 MB generated C unit `tcc` can take many
// minutes to build; running the same measurement from the CLI lets the
// recall / false-positive numbers be read without paying that cost.
//
//   mtsc conformance                       # walks the pinned conformance dirs, prints the 2x2 table
//   mtsc conformance --list-misses         # also print every recall miss (`RECALL_MISS `)
//                                          # and every false positive (`FALSE_POS  ::  :: `)
//   mtsc conformance --list-misses  # ...restricted to paths containing 
//
// This was the `tsacc` binary. Its reason for being separate was build
// cost — it linked only `parser` + `checker` — and that reason is gone:
// `mtsc` already links both through `transform`, so the separate binary
// was paying for a link of its own and buying nothing.

///|
fn ts_case_basename(path : String) -> String {
  let parts = path.split("/")
  let arr : Array[StringView] = []
  for p in parts {
    arr.push(p)
  }
  let mut name = if arr.length() == 0 {
    path
  } else {
    arr[arr.length() - 1].to_owned()
  }
  if name.has_suffix(".tsx") {
    name = name[:name.length() - 4].to_owned()
  } else if name.has_suffix(".ts") {
    name = name[:name.length() - 3].to_owned()
  }
  name
}

///|
async fn collect_baseline_basenames() -> Map[String, Bool] {
  let dir = "typescript/tests/baselines/reference"
  let entries = @fs.readdir(
    dir,
    include_hidden=false,
    include_special=false,
    sort=false,
  ) catch {
    _ => []
  }
  let suffix = ".errors.txt"
  let set : Map[String, Bool] = Map([])
  for name in entries {
    if name.has_suffix(suffix) {
      let stem = name[:name.length() - suffix.length()].to_owned()
      match stem.find("(") {
        Some(i) => set[stem[:i].to_owned()] = true
        None => ()
      }
      set[stem] = true
    }
  }
  set
}

///|
async fn collect_ts_source_files_recursive(dir : String) -> Array[String] {
  let kind = @fs.kind(dir, follow_symlink=false) catch {
    _ => @fs.FileKind::Unknown
  }
  if !(kind is @fs.FileKind::Directory) {
    return []
  }
  let entries = @fs.readdir(
    dir,
    include_hidden=false,
    include_special=false,
    sort=true,
  ) catch {
    _ => []
  }
  let files : Array[String] = []
  for name in entries {
    let path = dir + "/" + name
    if name.has_suffix(".ts") && !name.has_suffix(".d.ts") {
      files.push(path)
    } else {
      let sub_kind = @fs.kind(path, follow_symlink=false) catch {
        _ => @fs.FileKind::Unknown
      }
      if sub_kind is @fs.FileKind::Directory {
        for f in collect_ts_source_files_recursive(path) {
          files.push(f)
        }
      }
    }
  }
  files
}

///|
/// `mtsc conformance` — the checker's accuracy against the pinned
/// TypeScript conformance baselines.
///
/// `start` is the index of the first option, so the verb dispatch in
/// `driver.mbt` passes `2`. Every line it prints is unchanged from the
/// `tsacc` binary this replaces: the 2x2 table and the per-directory
/// breakdown are read by hand and by `docs/tsacc.md`, and a reworded
/// summary would invalidate every number recorded against it.
async fn mtsc_run_conformance(argv : Array[String], start : Int) -> Unit {
  // `--list-misses [substr]`: in addition to the 2x2 table, print one
  // `RECALL_MISS ` line per baseline-positive file we fail to flag
  // (a recall miss), optionally filtered to paths containing `substr`.
  // Handy for triaging which conformance cases to target next without
  // ad-hoc instrumentation.
  let mut list_misses = false
  let mut miss_filter = ""
  for i in start.. continue }
      let allow_jsx = path.has_suffix(".tsx")
      let case_name = ts_case_basename(path)
      let has_baseline = baseline_index.contains(case_name)
      let module_ = try
        @parser.Parser::from_source_with_jsx(content, allow_jsx).parse_module()
      catch {
        _ => {
          if has_baseline {
            recall_hit += 1
            d_rh += 1
          } else {
            precision_miss += 1
            d_pm += 1
          }
          continue
        }
      } noraise {
        m => {
          parsed += 1
          m
        }
      }
      let issues = @checker.check_module_function_bodies_permissive(module_)
      if issues.length() > 0 {
        if has_baseline {
          recall_hit += 1
          d_rh += 1
        } else {
          precision_miss += 1
          d_pm += 1
          if list_misses && (miss_filter == "" || path.contains(miss_filter)) {
            for iss in issues {
              println("FALSE_POS \{case_name} :: \{iss.path} :: \{iss.message}")
            }
          }
        }
      } else if has_baseline {
        recall_miss += 1
        d_rm += 1
        if list_misses && (miss_filter == "" || path.contains(miss_filter)) {
          println("RECALL_MISS \{case_name}")
        }
      } else {
        precision_hit += 1
        d_ph += 1
      }
    }
    per_dir[dir] = (d_rh, d_rm, d_ph, d_pm)
  }
  println(
    "=== per-directory breakdown (recall_hit / recall_miss / precision_hit / precision_miss) ===",
  )
  for kv in per_dir {
    println("  \{kv.0}: \{kv.1.0} / \{kv.1.1} / \{kv.1.2} / \{kv.1.3}")
  }
  let with_baseline = recall_hit + recall_miss
  let without_baseline = precision_hit + precision_miss
  println(
    "baseline accuracy: parsed=\{parsed}/\{total} | recall=\{recall_hit}/\{with_baseline} | precision=\{precision_hit}/\{without_baseline} (false-positives=\{precision_miss})",
  )
}