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

///|
pub struct Diagnostic {
  severity : String
  message : String
  suggestion : String
} derive(Debug, Eq)

///|
pub fn diagnostic(
  severity : String,
  message : String,
  suggestion : String,
) -> Diagnostic {
  { severity, message, suggestion }
}

///|
pub fn diagnostics(text : String) -> Array[Diagnostic] {
  let items : Array[Diagnostic] = []
  let parsed = parse(text)
  match parsed.expr {
    Some(expr) => {
      for id in licenses(expr) {
        if !is_known_license(id) {
          items.push(
            diagnostic(
              "error",
              "unknown license: " + id,
              license_suggestion(id),
            ),
          )
        } else if is_deprecated_license(id) {
          items.push(
            diagnostic("warning", "deprecated SPDX license id: " + id, ""),
          )
        }
      }
      for ex in exceptions(expr) {
        if !is_known_exception(ex) {
          items.push(
            diagnostic(
              "error",
              "unknown exception: " + ex,
              exception_suggestion(ex),
            ),
          )
        } else if is_deprecated_exception(ex) {
          items.push(
            diagnostic("warning", "deprecated SPDX exception id: " + ex, ""),
          )
        }
      }
      if contains_string(operators(expr), "OR") {
        items.push(
          diagnostic(
            "info", "expression contains OR alternatives", "policy checks should accept the expression when at least one branch is allowed",
          ),
        )
      }
    }
    None =>
      items.push(
        diagnostic(
          "error",
          parsed.error,
          "check SPDX operator spelling and parentheses",
        ),
      )
  }
  items
}

///|
pub fn diagnostics_report(text : String) -> String {
  let rows : Array[String] = []
  for item in diagnostics(text) {
    rows.push(item.severity + ": " + item.message + suggestion_suffix(item))
  }
  if rows.is_empty() {
    "ok"
  } else {
    join_lines(rows)
  }
}

///|
pub fn suggestion_suffix(item : Diagnostic) -> String {
  if item.suggestion == "" {
    ""
  } else {
    " (" + item.suggestion + ")"
  }
}

///|
pub fn license_suggestion(id : String) -> String {
  let value = lower_ascii(id)
  let common = common_license_alias(value)
  if common != "" {
    return "did you mean " + common + "?"
  }
  for known in known_licenses() {
    let candidate = lower_ascii(known)
    if candidate == value {
      return "use canonical id " + known
    }
    if !is_deprecated_license(known) &&
      (candidate.has_prefix(value) || value.has_prefix(candidate)) {
      return "did you mean " + known + "?"
    }
  }
  for known in known_licenses() {
    let candidate = lower_ascii(known)
    match candidate.find(value) {
      Some(_) if !is_deprecated_license(known) =>
        return "did you mean " + known + "?"
      Some(_) => ()
      None => ()
    }
  }
  ""
}

///|
pub fn common_license_alias(value : String) -> String {
  if value == "apache" || value == "apache2" {
    "Apache-2.0"
  } else if value == "bsd" {
    "BSD-3-Clause"
  } else if value == "gpl3" {
    "GPL-3.0-only"
  } else if value == "gpl2" {
    "GPL-2.0-only"
  } else if value == "lgpl3" {
    "LGPL-3.0-only"
  } else if value == "mpl" {
    "MPL-2.0"
  } else {
    ""
  }
}

///|
pub fn exception_suggestion(id : String) -> String {
  let value = lower_ascii(id)
  for known in known_exceptions() {
    let candidate = lower_ascii(known)
    if candidate == value {
      return "use canonical id " + known
    }
    if candidate.has_prefix(value) || value.has_prefix(candidate) {
      return "did you mean " + known + "?"
    }
  }
  ""
}

///|
pub fn quick_fix(text : String) -> String {
  let parsed = parse(text)
  match parsed.expr {
    Some(expr) => format(expr)
    None => text
  }
}