// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 clbbbb

///|
pub struct ProjectAudit {
  package_name : String
  package_license : String
  readme_ok : Bool
  source_count : Int
  missing_headers : Int
  invalid_headers : Int
  policy_errors : Array[String]
  warnings : Array[String]
} derive(Debug, Eq)

///|
pub fn project_audit(
  moon_mod : String,
  readme : String,
  paths : Array[String],
  sources : Array[String],
  policy : Policy,
) -> ProjectAudit {
  let package_name = option_or(extract_moon_mod_name(moon_mod), "unknown")
  let package_license = option_or(extract_moon_mod_license(moon_mod), "")
  let source_results = scan_sources(paths, sources)
  let warnings : Array[String] = []
  let policy_items : Array[String] = []
  collect_manifest_findings(
    moon_mod, readme, package_license, warnings, policy_items, policy,
  )
  collect_source_findings(source_results, warnings, policy_items, policy)
  collect_source_alignment(source_results, package_license, warnings)
  {
    package_name,
    package_license: normalize(package_license),
    readme_ok: readme_mentions_license_safe(readme, package_license),
    source_count: source_results.length(),
    missing_headers: count_missing_headers(source_results),
    invalid_headers: count_invalid_headers(source_results),
    policy_errors: unique_strings(policy_items),
    warnings: unique_strings(warnings),
  }
}

///|
pub fn option_or(value : String?, fallback : String) -> String {
  match value {
    Some(item) => item
    None => fallback
  }
}

///|
pub fn collect_manifest_findings(
  moon_mod : String,
  readme : String,
  package_license : String,
  warnings : Array[String],
  policy_items : Array[String],
  policy : Policy,
) -> Unit {
  if extract_moon_mod_name(moon_mod) is None {
    warnings.push("moon.mod name is missing")
  }
  if package_license == "" {
    policy_items.push("moon.mod license is missing")
  } else {
    for item in validation_errors(package_license) {
      policy_items.push("moon.mod license: " + item)
    }
    for item in policy_errors(package_license, policy) {
      policy_items.push("moon.mod license: " + item)
    }
    if !readme_mentions_license_safe(readme, package_license) {
      warnings.push("README does not mention the declared license")
    }
  }
  if !moon_mod_has_repository(moon_mod) {
    warnings.push("moon.mod repository is missing")
  }
}

///|
pub fn collect_source_findings(
  results : Array[SourceLicense],
  warnings : Array[String],
  policy_items : Array[String],
  policy : Policy,
) -> Unit {
  for item in results {
    if !item.found {
      warnings.push(item.path + ": missing SPDX header")
    } else if !item.valid {
      policy_items.push(item.path + ": " + item.message)
    } else {
      for error in policy_errors(item.expression, policy) {
        policy_items.push(item.path + ": " + error)
      }
    }
  }
}

///|
pub fn collect_source_alignment(
  results : Array[SourceLicense],
  package_license : String,
  warnings : Array[String],
) -> Unit {
  if package_license != "" && is_valid(package_license) {
    let summary = mixed_license_warning(
      inventory_from_source_results(results),
      package_license,
    )
    if summary != "source headers match package license" {
      warnings.push(summary)
    }
  }
}

///|
pub fn readme_mentions_license_safe(readme : String, expr : String) -> Bool {
  if expr == "" || !is_valid(expr) {
    false
  } else {
    readme_mentions_license(readme, expr)
  }
}

///|
pub fn count_missing_headers(results : Array[SourceLicense]) -> Int {
  let mut count = 0
  for item in results {
    if !item.found {
      count = count + 1
    }
  }
  count
}

///|
pub fn count_invalid_headers(results : Array[SourceLicense]) -> Int {
  let mut count = 0
  for item in results {
    if item.found && !item.valid {
      count = count + 1
    }
  }
  count
}

///|
pub fn audit_passes(audit : ProjectAudit) -> Bool {
  audit.policy_errors.is_empty() &&
  audit.missing_headers == 0 &&
  audit.invalid_headers == 0
}

///|
pub fn audit_score(audit : ProjectAudit) -> Int {
  let mut score = 100
  score = score - audit.policy_errors.length() * 15
  score = score - audit.warnings.length() * 5
  score = score - audit.missing_headers * 8
  score = score - audit.invalid_headers * 12
  if score < 0 {
    0
  } else {
    score
  }
}

///|
pub fn audit_grade(audit : ProjectAudit) -> String {
  let score = audit_score(audit)
  if score >= 90 {
    "A"
  } else if score >= 75 {
    "B"
  } else if score >= 60 {
    "C"
  } else {
    "D"
  }
}

///|
pub fn project_audit_report(audit : ProjectAudit) -> String {
  let rows : Array[String] = [
    "package: " + audit.package_name,
    "license: " + audit.package_license,
    "grade: " + audit_grade(audit),
    "score: " + audit_score(audit).to_string(),
    "sources: " + audit.source_count.to_string(),
    "missing headers: " + audit.missing_headers.to_string(),
    "invalid headers: " + audit.invalid_headers.to_string(),
  ]
  push_section(rows, "policy errors", audit.policy_errors)
  push_section(rows, "warnings", audit.warnings)
  join_lines(rows)
}

///|
pub fn push_section(
  rows : Array[String],
  title : String,
  items : Array[String],
) -> Unit {
  if items.is_empty() {
    rows.push(title + ": none")
  } else {
    rows.push(title + ":")
    for item in items {
      rows.push("- " + item)
    }
  }
}

///|
pub fn project_audit_markdown(audit : ProjectAudit) -> String {
  let rows : Array[String] = [
    "# License audit report",
    "",
    "- package: `" + audit.package_name + "`",
    "- license: `" + audit.package_license + "`",
    "- grade: `" + audit_grade(audit) + "`",
    "- score: `" + audit_score(audit).to_string() + "`",
    "- source files: `" + audit.source_count.to_string() + "`",
    "- missing SPDX headers: `" + audit.missing_headers.to_string() + "`",
    "- invalid SPDX headers: `" + audit.invalid_headers.to_string() + "`",
    "",
  ]
  push_markdown_section(rows, "Policy errors", audit.policy_errors)
  push_markdown_section(rows, "Warnings", audit.warnings)
  join_lines(rows)
}

///|
pub fn push_markdown_section(
  rows : Array[String],
  title : String,
  items : Array[String],
) -> Unit {
  rows.push("## " + title)
  rows.push("")
  if items.is_empty() {
    rows.push("- none")
  } else {
    for item in items {
      rows.push("- " + item)
    }
  }
  rows.push("")
}

///|
pub fn audit_project_report(
  moon_mod : String,
  readme : String,
  paths : Array[String],
  sources : Array[String],
  policy : Policy,
) -> String {
  project_audit_report(project_audit(moon_mod, readme, paths, sources, policy))
}