///|
pub(all) enum ExplanationKind {
  ForbiddenRoute
  MissingSanitizer
  ReviewedException
  UnknownRule
} derive(Eq, Debug, ToJson)

///|
pub(all) struct RemediationAction {
  title : String
  command : String
  rationale : String
  priority : String
} derive(Eq, Debug, ToJson)

///|
pub(all) struct FindingExplanation {
  kind : ExplanationKind
  title : String
  summary : String
  affected_path : String
  risk : String
  actions : Array[RemediationAction]
} derive(Eq, Debug, ToJson)

///|
pub fn explanation_kind_name(kind : ExplanationKind) -> String {
  match kind {
    ForbiddenRoute => "forbidden_route"
    MissingSanitizer => "missing_sanitizer"
    ReviewedException => "reviewed_exception"
    UnknownRule => "unknown_rule"
  }
}

///|
pub fn explain_finding(finding : Finding) -> FindingExplanation {
  let kind = if finding.rule == "deny" {
    ForbiddenRoute
  } else if finding.rule == "require" {
    MissingSanitizer
  } else {
    UnknownRule
  }
  let path = finding.path.join(" -> ")
  let actions = remediation_actions(kind, finding)
  {
    kind,
    title: explanation_title(kind),
    summary: explanation_summary(kind, finding),
    affected_path: path,
    risk: finding.severity,
    actions,
  }
}

///|
pub fn explain_findings(findings : Array[Finding]) -> Array[FindingExplanation] {
  let explanations : Array[FindingExplanation] = []
  for finding in findings {
    explanations.push(explain_finding(finding))
  }
  explanations
}

///|
pub fn explanations_json(findings : Array[Finding]) -> String {
  explain_findings(findings).to_json().stringify(indent=2)
}

///|
pub fn format_explanation(explanation : FindingExplanation) -> String {
  let out = StringBuilder()
  out.write_string("[\{explanation.risk}] \{explanation.title}")
  out.write_string("\npath=\{explanation.affected_path}")
  out.write_string("\n\{explanation.summary}")
  for action in explanation.actions {
    out.write_string("\n- \{action.priority}: \{action.title}")
    out.write_string("\n  command: \{action.command}")
    out.write_string("\n  why: \{action.rationale}")
  }
  out.to_string()
}

///|
pub fn format_explanations(findings : Array[Finding]) -> String {
  let explanations = explain_findings(findings)
  let out = StringBuilder()
  out.write_string("explanations=\{explanations.length()}")
  for explanation in explanations {
    out.write_char('\n')
    out.write_string(format_explanation(explanation))
  }
  out.to_string()
}

///|
fn explanation_title(kind : ExplanationKind) -> String {
  match kind {
    ForbiddenRoute => "Forbidden source-to-sink route"
    MissingSanitizer => "Required control point is missing"
    ReviewedException => "Reviewed exception requires confirmation"
    UnknownRule => "Policy finding requires review"
  }
}

///|
fn explanation_summary(kind : ExplanationKind, finding : Finding) -> String {
  match kind {
    ForbiddenRoute =>
      "The declared deny policy found a reachable path that crosses the trust boundary without an explicit exception. \{finding.message}"
    MissingSanitizer =>
      "The declared require policy found a reachable path that does not pass through its control point. \{finding.message}"
    ReviewedException =>
      "The finding uses a rule kind that is not part of the standard explainability vocabulary. \{finding.message}"
    UnknownRule =>
      "The analyzer returned a finding with an unrecognized rule label. \{finding.message}"
  }
}

///|
fn remediation_actions(
  kind : ExplanationKind,
  finding : Finding,
) -> Array[RemediationAction] {
  let actions : Array[RemediationAction] = []
  let path = finding.path.join(" -> ")
  match kind {
    ForbiddenRoute => {
      actions.push({
        title: "Route the data through a sanitizer",
        command: "add sanitizer  and an edge before \{finding.sink}",
        rationale: "A deny finding should be removed by changing the flow, not by hiding the report.",
        priority: "P0",
      })
      actions.push({
        title: "Add a reviewed allow path only when justified",
        command: "allow \{path} \"reviewed reason\"",
        rationale: "An exact-path exception documents a deliberate boundary decision and remains auditable.",
        priority: "P1",
      })
    }
    MissingSanitizer => {
      actions.push({
        title: "Insert the required control point",
        command: "add sanitizer \{finding.suggestion}",
        rationale: "The require rule is an enforceable contract for every reachable path.",
        priority: "P0",
      })
      actions.push({
        title: "Test both safe and unsafe branches",
        command: "add one fixture for each branch in fixtures/benchmarks",
        rationale: "Branch-specific fixtures prevent a future refactor from silently bypassing the control point.",
        priority: "P1",
      })
    }
    ReviewedException =>
      actions.push({
        title: "Confirm the policy vocabulary",
        command: "use allow, deny, or require in the .mtf model",
        rationale: "Stable rule names keep CLI, JSON, and SARIF consumers interoperable.",
        priority: "P1",
      })
    UnknownRule =>
      actions.push({
        title: "Review the generated finding",
        command: "run moon run cmd/main -- --json",
        rationale: "The structured report is the canonical handoff between the analyzer and external tooling.",
        priority: "P1",
      })
  }
  actions
}

///|
pub fn explanation_priority(explanation : FindingExplanation) -> String {
  if explanation.risk == "high" {
    "P0"
  } else if explanation.risk == "medium" {
    "P1"
  } else {
    "P2"
  }
}

///|
pub fn explanation_has_action(
  explanation : FindingExplanation,
  title : String,
) -> Bool {
  for action in explanation.actions {
    if action.title == title {
      return true
    }
  }
  false
}

///|
pub fn count_explanations_by_priority(
  explanations : Array[FindingExplanation],
  priority : String,
) -> Int {
  let mut count = 0
  for explanation in explanations {
    if explanation_priority(explanation) == priority {
      count += 1
    }
  }
  count
}

///|
pub fn explanation_summary_line(
  explanations : Array[FindingExplanation],
) -> String {
  let p0 = count_explanations_by_priority(explanations, "P0")
  let p1 = count_explanations_by_priority(explanations, "P1")
  let p2 = count_explanations_by_priority(explanations, "P2")
  "P0=\{p0}, P1=\{p1}, P2=\{p2}"
}