///|
fn join_path(base : StringView, child : StringView) -> String {
  if base.has_suffix("/") || base.has_suffix("\\") {
    "\{base}\{child}"
  } else {
    "\{base}/\{child}"
  }
}

///|
fn relative_path(root : StringView, path : StringView) -> String {
  let normalized_root = root.replace_all(old="\\", new="/").trim_end(chars="/")
  let normalized_path = path.replace_all(old="\\", new="/")
  match normalized_path.strip_prefix(normalized_root) {
    Some(rest) =>
      match rest.strip_prefix("/") {
        Some(relative) => relative.to_owned()
        None => rest.to_owned()
      }
    None => normalized_path.to_owned()
  }
}

///|
fn count_lines(text : StringView) -> Int {
  if text.is_empty() {
    return 0
  }
  let mut count = 0
  for _ in text.split("\n") {
    count += 1
  }
  count
}

///|
async fn find_manifest(root : String) -> (String, String) {
  let current = join_path(root, "moon.mod")
  if @fs.exists(current) {
    return (current, @fs.read_file(current).text())
  }
  let legacy = join_path(root, "moon.mod.json")
  if @fs.exists(legacy) {
    return (legacy, @fs.read_file(legacy).text())
  }
  fail("MoonSeal could not find moon.mod or moon.mod.json in \{root}")
}

///|
async fn read_license(root : String) -> String? {
  for name in ["LICENSE", "LICENSE.md", "LICENSE.txt"] {
    let path = join_path(root, name)
    if @fs.exists(path) {
      return Some(@fs.read_file(path).text())
    }
  }
  None
}

///|
fn excluded_directory(path : String) -> Bool {
  let path = path.replace_all(old="\\", new="/")
  path.has_suffix("/.git") ||
  path.has_suffix("/_build") ||
  path.has_suffix("/.mooncakes") ||
  path.has_suffix("/node_modules")
}

///|
async fn collect_facts(root : String) -> @moonseal.ScanFacts {
  let (manifest_path, manifest_text) = find_manifest(root)
  let project = @moonseal.parse_manifest(manifest_path, manifest_text)
  let files : Array[String] = []
  let mut source_lines = 0
  @fs.walk(root, max_concurrency=1, exclude=excluded_directory, async fn(
    directory,
    entries,
  ) {
    for entry in entries {
      let full_path = join_path(directory, entry)
      let relative = relative_path(root, full_path)
      files.push(relative)
      if entry.has_suffix(".mbt") {
        let content = @fs.read_file(full_path).text()
        source_lines += count_lines(content)
      }
    }
  })
  {
    project,
    files,
    moonbit_source_lines: source_lines,
    license_text: read_license(root),
  }
}

///|
fn usage() -> String {
  let text =
    #|MoonSeal 0.2.1 — MoonBit supply-chain audit and CycloneDX SBOM generator
    #|
    #|USAGE
    #|  moonseal scan [PATH] [--json | --markdown | --sarif]
    #|  moonseal check [PATH]
    #|  moonseal sbom [PATH] [-o FILE]
    #|  moonseal dependencies [PATH] [--json]
    #|  moonseal policy [PATH] [--policy FILE] [--json]
    #|  moonseal help
    #|
    #|COMMANDS
    #|  scan   Audit a project; output text, JSON, or Markdown
    #|  check  Run the audit and fail when blocking findings exist
    #|  sbom   Generate a CycloneDX 1.6 software bill of materials
    #|  dependencies  Analyze direct Mooncakes dependency metadata
    #|  policy  Enforce repository-specific release requirements
    #|
  text
}

///|
fn positional_path(args : Array[String]) -> String {
  let mut index = 0
  while index < args.length() {
    let arg = args[index]
    if arg == "-o" || arg == "--output" || arg == "--policy" {
      index += 2
      continue
    }
    if !arg.has_prefix("-") {
      return arg
    }
    index += 1
  }
  "."
}

///|
fn option_value(args : Array[String], names : Array[String]) -> String? {
  let mut index = 0
  while index < args.length() {
    if names.any(fn(name) { args[index] == name }) && index + 1 < args.length() {
      return Some(args[index + 1])
    }
    index += 1
  }
  None
}

///|
fn output_path(args : Array[String]) -> String? {
  option_value(args, ["-o", "--output"])
}

///|
async fn run_scan(args : Array[String]) -> Unit {
  let facts = collect_facts(positional_path(args))
  let report = @moonseal.audit(facts)
  if args.any(fn(arg) { arg == "--json" }) {
    @stdio.stdout.write(@moonseal.render_json(report) + "\n")
  } else if args.any(fn(arg) { arg == "--sarif" }) {
    @stdio.stdout.write(@moonseal.render_sarif(report) + "\n")
  } else if args.any(fn(arg) { arg == "--markdown" }) {
    @stdio.stdout.write(@moonseal.render_markdown(report))
  } else {
    @stdio.stdout.write(@moonseal.render_text(report))
  }
}

///|
async fn run_dependencies(args : Array[String]) -> Unit {
  let facts = collect_facts(positional_path(args))
  let health = @moonseal.analyze_dependencies(facts.project)
  if args.any(fn(arg) { arg == "--json" }) {
    @stdio.stdout.write(@moonseal.render_dependency_json(health) + "\n")
  } else {
    @stdio.stdout.write(@moonseal.render_dependency_text(health))
  }
  if !health.is_healthy() {
    fail("MoonSeal found \{health.error_count()} blocking dependency issue(s)")
  }
}

///|
async fn run_policy(args : Array[String]) -> Unit {
  let root = positional_path(args)
  let policy_path = match option_value(args, ["--policy"]) {
    Some(path) => path
    None => join_path(root, "moonseal-policy.json")
  }
  guard @fs.exists(policy_path) else {
    fail("MoonSeal policy file does not exist: \{policy_path}")
  }
  let policy = @moonseal.parse_policy(@fs.read_file(policy_path).text())
  let facts = collect_facts(root)
  let report = @moonseal.audit(facts)
  let result = @moonseal.evaluate_policy(policy, facts, report)
  if args.any(fn(arg) { arg == "--json" }) {
    @stdio.stdout.write(@moonseal.render_policy_json(result) + "\n")
  } else {
    @stdio.stdout.write(@moonseal.render_policy_text(result))
  }
  if !result.is_allowed() {
    fail(
      "MoonSeal policy blocked release with \{result.violations.length()} violation(s)",
    )
  }
}

///|
async fn run_check(args : Array[String]) -> Unit {
  let facts = collect_facts(positional_path(args))
  let report = @moonseal.audit(facts)
  @stdio.stdout.write(@moonseal.render_text(report))
  if !report.is_ready() {
    fail(
      "MoonSeal check failed with \{report.error_count()} blocking finding(s)",
    )
  }
}

///|
async fn run_sbom(args : Array[String]) -> Unit {
  let facts = collect_facts(positional_path(args))
  let output = @moonseal.cyclonedx_string(facts.project) + "\n"
  match output_path(args) {
    Some(path) => {
      @fs.write_file(path, output, create_mode=CreateOrTruncate)
      @stdio.stdout.write("Wrote CycloneDX 1.6 SBOM to \{path}\n")
    }
    None => @stdio.stdout.write(output)
  }
}

///|
async fn main {
  let argv = @env.args()
  let command = if argv.length() >= 2 { argv[1] } else { "help" }
  let args = if argv.length() > 2 { argv[2:].to_owned() } else { [] }
  match command {
    "scan" => run_scan(args)
    "check" => run_check(args)
    "sbom" => run_sbom(args)
    "dependencies" | "deps" => run_dependencies(args)
    "policy" => run_policy(args)
    "help" | "--help" | "-h" => @stdio.stdout.write(usage())
    "version" | "--version" | "-V" => @stdio.stdout.write("MoonSeal 0.2.1\n")
    other => {
      @stdio.stderr.write("Unknown command: \{other}\n\n")
      @stdio.stderr.write(usage())
      fail("unknown command")
    }
  }
}