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

///|
pub struct Obligation {
  key : String
  title : String
  detail : String
  severity : String
} derive(Debug, Eq)

///|
pub fn obligation(
  key : String,
  title : String,
  detail : String,
  severity : String,
) -> Obligation {
  { key, title, detail, severity }
}

///|
pub fn obligations_for_license(id : String) -> Array[Obligation] {
  let license = canonical_license(id)
  if license == "MIT" ||
    license == "ISC" ||
    license == "BSD-2-Clause" ||
    license == "BSD-3-Clause" ||
    license == "Zlib" ||
    license == "0BSD" {
    permissive_obligations(license)
  } else if license == "Apache-2.0" {
    apache_obligations()
  } else if license == "MPL-2.0" ||
    license == "EPL-2.0" ||
    license == "CDDL-1.0" {
    file_copyleft_obligations(license)
  } else if license.has_prefix("LGPL") {
    weak_copyleft_obligations(license)
  } else if license.has_prefix("GPL") {
    strong_copyleft_obligations(license)
  } else if license == "CC0-1.0" || license == "Unlicense" {
    public_domain_style_obligations(license)
  } else {
    [
      obligation(
        "manual-review", "Manual license review", "The license is not in the compact catalog; review the original text before release.",
        "high",
      ),
    ]
  }
}

///|
pub fn permissive_obligations(id : String) -> Array[Obligation] {
  [
    obligation(
      "keep-notice",
      "Keep copyright and license notice",
      id +
      " is permissive, but redistribution should keep the copyright and license text.",
      "medium",
    ),
    obligation(
      "keep-disclaimer", "Keep warranty disclaimer", "Do not remove the warranty and liability disclaimer from redistributed copies.",
      "low",
    ),
  ]
}

///|
pub fn apache_obligations() -> Array[Obligation] {
  [
    obligation(
      "keep-license", "Keep Apache-2.0 license text", "Redistribution should include the Apache-2.0 license text.",
      "medium",
    ),
    obligation(
      "notice-file", "Preserve NOTICE information", "If upstream provides a NOTICE file, keep required attribution notices.",
      "medium",
    ),
    obligation(
      "state-changes", "Mark modified files", "Apache-2.0 asks redistributors to state significant changes to files.",
      "medium",
    ),
    obligation(
      "patent-termination", "Respect patent termination clause", "Patent litigation can terminate the patent grant; flag legal review when relevant.",
      "low",
    ),
  ]
}

///|
pub fn file_copyleft_obligations(id : String) -> Array[Obligation] {
  [
    obligation(
      "publish-modified-files",
      "Publish modified covered files",
      id +
      " is file-level copyleft; modified covered files usually stay under the same license.",
      "high",
    ),
    obligation(
      "keep-boundary", "Keep file-level boundary clear", "Track which files are covered so unrelated files are not accidentally over-scoped.",
      "medium",
    ),
    obligation(
      "include-license", "Include license text", "Redistribution should include the relevant license text and notices.",
      "medium",
    ),
  ]
}

///|
pub fn weak_copyleft_obligations(id : String) -> Array[Obligation] {
  [
    obligation(
      "linking-review",
      "Review linking model",
      id +
      " is weak copyleft; binary distribution may need relinking or object-file handling.",
      "high",
    ),
    obligation(
      "publish-library-changes", "Publish changes to the library", "Changes to the covered library generally need to remain under the same license.",
      "high",
    ),
    obligation(
      "include-license", "Include license text", "Keep license text and notices with redistributed artifacts.",
      "medium",
    ),
  ]
}

///|
pub fn strong_copyleft_obligations(id : String) -> Array[Obligation] {
  [
    obligation(
      "source-offer",
      "Provide corresponding source",
      id +
      " is strong copyleft; distribution may require complete corresponding source.",
      "high",
    ),
    obligation(
      "derivative-scope-review", "Review derivative work scope", "Check whether combined work must be distributed under compatible copyleft terms.",
      "high",
    ),
    obligation(
      "include-license", "Include license text", "Keep license text, notices, and source availability information.",
      "medium",
    ),
  ]
}

///|
pub fn public_domain_style_obligations(id : String) -> Array[Obligation] {
  [
    obligation(
      "jurisdiction-review",
      "Review public-domain-style terms",
      id +
      " may behave differently across jurisdictions; keep the original text for traceability.",
      "medium",
    ),
  ]
}

///|
pub fn obligations_for_expr(expr : Expr) -> Array[Obligation] {
  let rows : Array[Obligation] = []
  for id in licenses(expr) {
    for item in obligations_for_license(id) {
      if !contains_obligation(rows, item.key) {
        rows.push(item)
      }
    }
  }
  rows
}

///|
pub fn contains_obligation(items : Array[Obligation], key : String) -> Bool {
  for item in items {
    if item.key == key {
      return true
    }
  }
  false
}

///|
pub fn obligations_report(text : String) -> String {
  match parse(text).expr {
    Some(expr) => {
      let rows : Array[String] = []
      for item in obligations_for_expr(expr) {
        rows.push(item.severity + ": " + item.title + " - " + item.detail)
      }
      if rows.is_empty() {
        "no obligations"
      } else {
        join_lines(rows)
      }
    }
    None => "parse error: " + parse(text).error
  }
}

///|
pub fn notice_checklist(text : String) -> Array[String] {
  match parse(text).expr {
    Some(expr) => {
      let rows : Array[String] = []
      for item in obligations_for_expr(expr) {
        if item.key == "keep-notice" ||
          item.key == "notice-file" ||
          item.key == "include-license" {
          rows.push(item.title)
        }
      }
      unique_strings(rows)
    }
    None => []
  }
}

///|
pub fn notice_checklist_report(text : String) -> String {
  let rows = notice_checklist(text)
  if rows.is_empty() {
    "no notice checklist"
  } else {
    join_lines(rows)
  }
}