///|
fn check_item(status : String, message : String) -> CheckItem {
  { status, message }
}

///|
fn has_check_status(items : Array[CheckItem], status : String) -> Bool {
  for item in items {
    if item.status == status {
      return true
    }
  }
  false
}

///|
fn render_check_report(items : Array[CheckItem]) -> String {
  let passed : Array[String] = []
  let warnings : Array[String] = []
  let failures : Array[String] = []
  for item in items {
    match item.status {
      "pass" => passed.push(item.message)
      "warn" => warnings.push(item.message)
      "fail" => failures.push(item.message)
      _ => ()
    }
  }
  let status = if !failures.is_empty() {
    "failed"
  } else if !warnings.is_empty() {
    "passed with warnings"
  } else {
    "passed"
  }
  let lines : Array[String] = []
  lines.push("Status: " + status)
  lines.push("")
  lines.push("Summary:")
  lines.push("  passed: " + passed.length().to_string())
  lines.push("  warnings: " + warnings.length().to_string())
  lines.push("  failures: " + failures.length().to_string())
  if !failures.is_empty() {
    lines.push("")
    lines.push("Failures:")
    for message in failures {
      lines.push("  - " + message)
    }
  }
  if !warnings.is_empty() {
    lines.push("")
    lines.push("Warnings:")
    for message in warnings {
      lines.push("  - " + message)
    }
  }
  if !passed.is_empty() {
    lines.push("")
    lines.push("Passed:")
    for message in passed {
      lines.push("  - " + message)
    }
  }
  lines.join("\n") + "\n"
}

///|
fn read_required_file(
  results : Array[CheckItem],
  dir : String,
  file : String,
) -> String? raise {
  let file_path = path_join(dir, file)
  if !path_exists(file_path) {
    results.push(check_item("fail", "missing " + file))
    return None
  }
  if @miniio.is_dir(file_path) {
    results.push(check_item("fail", file + " is a directory"))
    return None
  }
  results.push(check_item("pass", file + " exists"))
  Some(@miniio.read_text_file(file_path))
}

///|
fn check_contains(
  results : Array[CheckItem],
  content : String,
  needle : String,
  pass_message : String,
  fail_message : String,
) -> Unit {
  if content.contains(needle) {
    results.push(check_item("pass", pass_message))
  } else {
    results.push(check_item("fail", fail_message))
  }
}

///|
fn has_frontmatter_field(content : String, field : String) -> Bool {
  if !content.has_prefix("---\n") {
    return false
  }
  let prefix = field + ":"
  let mut first = true
  for line in content.split("\n") {
    if first {
      first = false
    } else if line.trim() == "---" {
      return false
    } else if line.trim_start().has_prefix(prefix) {
      return true
    }
  }
  false
}

///|
fn check_frontmatter_field(
  results : Array[CheckItem],
  content : String,
  field : String,
) -> Unit {
  if has_frontmatter_field(content, field) {
    results.push(check_item("pass", "SKILL.md frontmatter has " + field))
  } else {
    results.push(check_item("fail", "SKILL.md frontmatter missing " + field))
  }
}

///|
fn check_skill_file_contents(
  results : Array[CheckItem],
  moon_mod : String?,
  moon_pkg : String?,
  skill_md : String?,
) -> Unit {
  match moon_mod {
    Some(content) => {
      check_contains(
        results, content, "supported_targets = \"wasm\"", "moon.mod configures wasm target",
        "moon.mod missing supported_targets = \"wasm\"",
      )
      check_contains(
        results, content, "moonbit-community/miniio", "moon.mod depends on miniio",
        "moon.mod missing moonbit-community/miniio dependency",
      )
      if content.contains("name = \"username/") {
        results.push(
          check_item("warn", "moon.mod still uses username placeholder"),
        )
      } else {
        results.push(
          check_item("pass", "moon.mod module name is not username placeholder"),
        )
      }
    }
    None => ()
  }
  match moon_pkg {
    Some(content) => {
      check_contains(
        results, content, "supported_targets = \"wasm\"", "moon.pkg configures wasm target",
        "moon.pkg missing supported_targets = \"wasm\"",
      )
      check_contains(
        results, content, "\"is-main\": true", "moon.pkg configures main package",
        "moon.pkg missing options(\"is-main\": true)",
      )
      check_contains(
        results, content, "moonbit-community/miniio", "moon.pkg imports miniio",
        "moon.pkg missing moonbit-community/miniio import",
      )
    }
    None => ()
  }
  match skill_md {
    Some(content) => {
      check_frontmatter_field(results, content, "name")
      check_frontmatter_field(results, content, "description")
    }
    None => ()
  }
}

///|
fn should_skip_scan_dir(name : String) -> Bool {
  name == ".git" || name == ".mooncakes" || name == "_build"
}

///|
fn should_scan_text_file(path : String) -> Bool {
  let name = @path.Path(path).basename().to_owned()
  name == "moon.mod" ||
  name == "moon.pkg" ||
  path.strip_suffix(".mbt") is Some(_) ||
  path.strip_suffix(".mbti") is Some(_) ||
  path.strip_suffix(".mbt.md") is Some(_)
}

///|
fn has_dependency_line(content : String, dependency : String) -> Bool {
  let quoted = "\"" + dependency
  for line in content.split("\n") {
    if line.trim_start().has_prefix(quoted) {
      return true
    }
  }
  false
}

///|
fn scan_is_dir(path : String) -> Bool raise {
  @miniio.is_dir(path) catch {
    @miniio.Errno::Loop => false
    err => raise err
  }
}

///|
fn scan_project_text(dir : String) -> (Bool, Bool) raise {
  let mut found_async = false
  let mut found_x_fs = false
  for name in @miniio.readdir(dir, include_hidden=true, sort=true) {
    if !should_skip_scan_dir(name) {
      let child = path_join(dir, name)
      if scan_is_dir(child) {
        let (child_async, child_x_fs) = scan_project_text(child)
        if child_async {
          found_async = true
        }
        if child_x_fs {
          found_x_fs = true
        }
      } else if should_scan_text_file(child) {
        let content = @miniio.read_text_file(child)
        if has_dependency_line(content, "moonbitlang/async") {
          found_async = true
        }
        if has_dependency_line(content, "moonbitlang/x/fs") {
          found_x_fs = true
        }
      }
    }
  }
  (found_async, found_x_fs)
}

///|
fn check_disallowed_usage(
  results : Array[CheckItem],
  dir : String,
) -> Unit raise {
  let (found_async, found_x_fs) = scan_project_text(dir)
  check_disallowed_flags(results, found_async, found_x_fs)
}

///|
fn check_disallowed_flags(
  results : Array[CheckItem],
  found_async : Bool,
  found_x_fs : Bool,
) -> Unit {
  if found_async {
    results.push(check_item("fail", "uses moonbitlang/async"))
  } else {
    results.push(check_item("pass", "does not use moonbitlang/async"))
  }
  if found_x_fs {
    results.push(check_item("fail", "uses moonbitlang/x/fs"))
  } else {
    results.push(check_item("pass", "does not use moonbitlang/x/fs"))
  }
}

///|
fn is_main_package_content(content : String) -> Bool {
  content.contains("\"is-main\": true")
}

///|
fn main_package_label(base : String, moon_pkg_path : String) -> String {
  let dir = @path.Path(moon_pkg_path).dirname().to_string()
  if dir == base {
    "."
  } else {
    @path.Path(dir).relative(base=@path.Path(base)).to_string()
  }
}

///|
fn is_nested_module_root(base : String, dir : String) -> Bool raise {
  if dir == base {
    false
  } else {
    let moon_mod = path_join(dir, "moon.mod")
    path_exists(moon_mod) && !scan_is_dir(moon_mod)
  }
}

///|
fn collect_main_packages(
  base : String,
  dir : String,
  found : Array[String],
) -> Unit raise {
  if is_nested_module_root(base, dir) {
    return
  }
  let root_pkg = path_join(dir, "moon.pkg")
  if path_exists(root_pkg) && !scan_is_dir(root_pkg) {
    let content = @miniio.read_text_file(root_pkg)
    if is_main_package_content(content) {
      found.push(main_package_label(base, root_pkg))
    }
  }
  for name in @miniio.readdir(dir, include_hidden=true, sort=true) {
    if name != "moon.pkg" && !should_skip_scan_dir(name) {
      let child = path_join(dir, name)
      if scan_is_dir(child) {
        collect_main_packages(base, child, found)
      } else if name == "moon.pkg" {
        let content = @miniio.read_text_file(child)
        if is_main_package_content(content) {
          found.push(main_package_label(base, child))
        }
      }
    }
  }
}

///|
fn check_main_package_count(
  results : Array[CheckItem],
  dir : String,
) -> Unit raise {
  let packages : Array[String] = []
  collect_main_packages(dir, dir, packages)
  if packages.length() > 1 {
    results.push(
      check_item(
        "warn",
        "module has multiple main packages (" +
        packages.join(", ") +
        "); prefer only the root package as main",
      ),
    )
  } else {
    results.push(check_item("pass", "module has a single main package"))
  }
}

///|
fn check_wasm_skill(dir : String) -> Array[CheckItem] raise {
  let results : Array[CheckItem] = []
  if !path_exists(dir) {
    results.push(check_item("fail", "target does not exist: " + dir))
    return results
  }
  if !@miniio.is_dir(dir) {
    results.push(check_item("fail", "target is not a directory: " + dir))
    return results
  }
  results.push(check_item("pass", "target is a directory"))
  let moon_mod = read_required_file(results, dir, "moon.mod")
  let moon_pkg = read_required_file(results, dir, "moon.pkg")
  let skill_md = read_required_file(results, dir, "SKILL.md")
  check_skill_file_contents(results, moon_mod, moon_pkg, skill_md)
  check_disallowed_usage(results, dir)
  check_main_package_count(results, dir)
  results
}