///|
/// Explainable policy metadata keeps CI failures actionable for humans.
pub struct PolicyRule {
  id : String
  title : String
  rationale : String
  severity : String
} derive(Debug, Eq)

///|
pub fn PolicyRule::new(
  id : String,
  title : String,
  rationale : String,
  severity : String,
) -> PolicyRule {
  { id, title, rationale, severity }
}

///|
pub fn PolicyRule::to_markdown(self : PolicyRule) -> String {
  "| `" +
  self.id +
  "` | " +
  self.title +
  " | " +
  self.severity +
  " | " +
  self.rationale +
  " |"
}

///|
pub fn PolicyRule::to_json(self : PolicyRule) -> String {
  "{\"id\":\"" +
  json_escape(self.id) +
  "\",\"title\":\"" +
  json_escape(self.title) +
  "\",\"rationale\":\"" +
  json_escape(self.rationale) +
  "\",\"severity\":\"" +
  json_escape(self.severity) +
  "\"}"
}

///|
pub struct PolicyCatalog {
  name : String
  rules : Array[PolicyRule]
} derive(Debug, Eq)

///|
pub fn PolicyCatalog::brandbook() -> PolicyCatalog {
  {
    name: "brandbook quality policy",
    rules: [
      PolicyRule::new(
        "schema.required", "Required sections", "A guide must be importable without guessing missing structure.",
        "error",
      ),
      PolicyRule::new(
        "color.hex", "Valid colors", "Color values must be portable across CSS and design tools.",
        "error",
      ),
      PolicyRule::new(
        "type.rhythm", "Readable rhythm", "Line height and size bounds protect long-form readability.",
        "error",
      ),
      PolicyRule::new(
        "a11y.contrast", "Contrast guidance", "Text combinations need an explicit accessibility review.",
        "warning",
      ),
      PolicyRule::new(
        "asset.path", "Safe asset paths", "Generated references must not escape the asset root.",
        "error",
      ),
      PolicyRule::new(
        "component.contract", "Component contracts", "States and token references make guidance implementable.",
        "warning",
      ),
      PolicyRule::new(
        "export.stable", "Stable exports", "Consumers need deterministic machine-readable artifacts.",
        "error",
      ),
    ],
  }
}

///|
pub fn PolicyCatalog::len(self : PolicyCatalog) -> Int {
  self.rules.length()
}

///|
pub fn PolicyCatalog::rule(self : PolicyCatalog, id : String) -> PolicyRule? {
  for rule in self.rules {
    if rule.id == id {
      return Some(rule)
    }
  }
  None
}

///|
pub fn PolicyCatalog::rule_ids(self : PolicyCatalog) -> Array[String] {
  self.rules.map(fn(rule) { rule.id })
}

///|
pub fn PolicyCatalog::contains(self : PolicyCatalog, id : String) -> Bool {
  self.rule(id) is Some(_)
}

///|
pub fn PolicyCatalog::errors(self : PolicyCatalog) -> Array[PolicyRule] {
  self.rules.filter(fn(rule) { rule.severity == "error" })
}

///|
pub fn PolicyCatalog::warnings(self : PolicyCatalog) -> Array[PolicyRule] {
  self.rules.filter(fn(rule) { rule.severity == "warning" })
}

///|
pub fn PolicyCatalog::critical_ids(self : PolicyCatalog) -> Array[String] {
  self.errors().map(fn(rule) { rule.id })
}

///|
pub fn PolicyCatalog::is_complete(self : PolicyCatalog) -> Bool {
  self.contains("schema.required") &&
  self.contains("a11y.contrast") &&
  self.contains("export.stable")
}

///|
pub fn PolicyCatalog::summary(self : PolicyCatalog) -> String {
  let counts = self.severity_counts()
  self.len().to_string() +
  " rules: " +
  counts[0].to_string() +
  " errors, " +
  counts[1].to_string() +
  " warnings, " +
  counts[2].to_string() +
  " notes"
}

///|
pub fn PolicyCatalog::rule_for_path(
  self : PolicyCatalog,
  path : String,
) -> PolicyRule? {
  let normalized = normalize_identifier(path)
  for rule in self.rules {
    let rule_id = normalize_identifier(rule.id)
    if normalized == rule_id || normalized.contains(rule_id) {
      return Some(rule)
    }
  }
  None
}

///|
pub fn PolicyCatalog::checklist(self : PolicyCatalog) -> Array[String] {
  self.rules.map(fn(rule) { "[ ] " + rule.title })
}

///|
pub fn PolicyCatalog::filter_severity(
  self : PolicyCatalog,
  severity : String,
) -> Array[PolicyRule] {
  self.rules.filter(fn(rule) { rule.severity == severity })
}

///|
pub fn PolicyCatalog::titles(self : PolicyCatalog) -> Array[String] {
  self.rules.map(fn(rule) { rule.title })
}

///|
pub fn PolicyCatalog::rationales(self : PolicyCatalog) -> Array[String] {
  self.rules.map(fn(rule) { rule.rationale })
}

///|
pub fn PolicyCatalog::markdown_checklist(self : PolicyCatalog) -> String {
  "## Policy checklist\n\n" + self.checklist().join("\n")
}

///|
pub fn PolicyCatalog::json_rule_ids(self : PolicyCatalog) -> String {
  "[" +
  self.rule_ids().map(fn(id) { "\"" + json_escape(id) + "\"" }).join(",") +
  "]"
}

///|
pub fn PolicyCatalog::has_errors(self : PolicyCatalog) -> Bool {
  self.errors().length() > 0
}

///|
pub fn PolicyCatalog::has_warnings(self : PolicyCatalog) -> Bool {
  self.warnings().length() > 0
}

///|
pub fn PolicyCatalog::error_count(self : PolicyCatalog) -> Int {
  self.errors().length()
}

///|
pub fn PolicyCatalog::warning_count(self : PolicyCatalog) -> Int {
  self.warnings().length()
}

///|
pub fn PolicyCatalog::note_count(self : PolicyCatalog) -> Int {
  self.filter_severity("info").length()
}

///|
pub fn PolicyCatalog::first_error(self : PolicyCatalog) -> PolicyRule? {
  match self.errors() {
    [first, ..] => Some(first)
    _ => None
  }
}

///|
pub fn PolicyCatalog::last_rule(self : PolicyCatalog) -> PolicyRule? {
  if self.rules.length() == 0 {
    None
  } else {
    Some(self.rules[self.rules.length() - 1])
  }
}

///|
pub fn PolicyCatalog::all_ids_non_empty(self : PolicyCatalog) -> Bool {
  self.rules.all(fn(rule) { rule.id.trim().to_owned() != "" })
}

///|
pub fn PolicyCatalog::all_rationales_non_empty(self : PolicyCatalog) -> Bool {
  self.rules.all(fn(rule) { rule.rationale.trim().to_owned() != "" })
}

///|
pub fn PolicyCatalog::ready(self : PolicyCatalog) -> Bool {
  self.is_complete() &&
  self.all_ids_non_empty() &&
  self.all_rationales_non_empty()
}

///|
pub fn PolicyCatalog::status(self : PolicyCatalog) -> String {
  if self.ready() {
    "ready"
  } else {
    "incomplete"
  }
}

///|
pub fn PolicyCatalog::status_line(self : PolicyCatalog) -> String {
  self.name + ": " + self.status() + " (" + self.summary() + ")"
}

///|
pub fn PolicyCatalog::search(
  self : PolicyCatalog,
  text : String,
) -> Array[PolicyRule] {
  let query = normalize_identifier(text)
  self.rules.filter(fn(rule) {
    normalize_identifier(rule.id).contains(query) ||
    normalize_identifier(rule.title).contains(query) ||
    normalize_identifier(rule.rationale).contains(query)
  })
}

///|
pub fn PolicyCatalog::search_ids(
  self : PolicyCatalog,
  text : String,
) -> Array[String] {
  self.search(text).map(fn(rule) { rule.id })
}

///|
pub fn PolicyCatalog::severity_labels(self : PolicyCatalog) -> Array[String] {
  self.rules.map(fn(rule) { rule.severity })
}

///|
pub fn PolicyCatalog::unique_severities(self : PolicyCatalog) -> Array[String] {
  let result : Array[String] = []
  for severity in self.severity_labels() {
    if !result.contains(severity) {
      result.push(severity)
    }
  }
  result
}

///|
pub fn PolicyCatalog::rule_titles_markdown(self : PolicyCatalog) -> String {
  self.titles().map(fn(title) { "- " + title }).join("\n")
}

///|
pub fn PolicyCatalog::rationales_markdown(self : PolicyCatalog) -> String {
  self.rationales().map(fn(value) { "- " + value }).join("\n")
}

///|
pub fn PolicyCatalog::compact_json(self : PolicyCatalog) -> String {
  "{\"count\":" +
  self.len().to_string() +
  ",\"status\":\"" +
  self.status() +
  "\",\"errors\":" +
  self.error_count().to_string() +
  "}"
}

///|
pub fn PolicyCatalog::severity_counts(self : PolicyCatalog) -> Array[Int] {
  [
    self.rules.filter(fn(rule) { rule.severity == "error" }).length(),
    self.rules.filter(fn(rule) { rule.severity == "warning" }).length(),
    self.rules.filter(fn(rule) { rule.severity == "info" }).length(),
  ]
}

///|
pub fn PolicyCatalog::to_markdown(self : PolicyCatalog) -> String {
  let lines : Array[String] = [
    "# " + self.name,
    "",
    "| Rule | Title | Severity | Rationale |",
    "| --- | --- | --- | --- |",
  ]
  for rule in self.rules {
    lines.push(rule.to_markdown())
  }
  lines.join("\n")
}

///|
pub fn PolicyCatalog::to_json(self : PolicyCatalog) -> String {
  "{\"name\":\"" +
  json_escape(self.name) +
  "\",\"rules\":[" +
  self.rules.map(fn(rule) { rule.to_json() }).join(",") +
  "]}"
}

///|
pub fn policy_catalog() -> PolicyCatalog {
  PolicyCatalog::brandbook()
}

///|
pub fn policy_markdown() -> String {
  policy_catalog().to_markdown()
}

///|
pub fn policy_json() -> String {
  policy_catalog().to_json()
}

///|
pub fn PolicyCatalog::has_rule_with_title(
  self : PolicyCatalog,
  title : String,
) -> Bool {
  self.rules.any(fn(rule) { rule.title == title })
}

///|
pub fn PolicyCatalog::ids_markdown(self : PolicyCatalog) -> String {
  self.rule_ids().map(fn(id) { "- `" + id + "`" }).join("\n")
}

///|
pub fn PolicyCatalog::severity_summary(self : PolicyCatalog) -> String {
  self
  .unique_severities()
  .map(fn(value) {
    value + "=" + self.filter_severity(value).length().to_string()
  })
  .join(", ")
}

///|
pub fn PolicyCatalog::coverage_score(self : PolicyCatalog) -> Int {
  if self.is_complete() {
    100
  } else {
    60
  }
}

///|
pub fn PolicyCatalog::coverage_label(self : PolicyCatalog) -> String {
  self.coverage_score().to_string() + "/100"
}

///|
pub fn PolicyCatalog::ready_summary(self : PolicyCatalog) -> String {
  self.status_line() + "; coverage " + self.coverage_label()
}

///|
pub fn PolicyCatalog::rule_count_for(
  self : PolicyCatalog,
  severity : String,
) -> Int {
  self.filter_severity(severity).length()
}

///|
pub fn PolicyCatalog::has_required_sections(self : PolicyCatalog) -> Bool {
  self.contains("schema.required") && self.contains("export.stable")
}

///|
pub fn PolicyCatalog::ready_for_ci(self : PolicyCatalog) -> Bool {
  self.ready() && self.has_required_sections()
}

///|
pub fn PolicyCatalog::ci_message(self : PolicyCatalog) -> String {
  if self.ready_for_ci() {
    "policy checks ready"
  } else {
    "policy catalog needs review"
  }
}

///|
pub fn PolicyCatalog::rule_ids_csv(self : PolicyCatalog) -> String {
  self.rule_ids().join(",")
}

///|
pub fn PolicyCatalog::rule_titles_csv(self : PolicyCatalog) -> String {
  self.titles().join(",")
}

///|
pub fn PolicyCatalog::is_empty(self : PolicyCatalog) -> Bool {
  self.rules.length() == 0
}

///|
pub fn PolicyCatalog::name_text(self : PolicyCatalog) -> String {
  self.name
}

///|
pub fn PolicyCatalog::rule_at(self : PolicyCatalog, index : Int) -> PolicyRule? {
  if index < 0 || index >= self.rules.length() {
    None
  } else {
    Some(self.rules[index])
  }
}

///|
pub fn PolicyCatalog::first_rule(self : PolicyCatalog) -> PolicyRule? {
  self.rule_at(0)
}