///|
/// Catalog category for a CSPKit audit rule.
pub(all) enum AuditRuleCategory {
  RuleBaseline
  RuleScript
  RuleTransport
  RuleEmbedding
  RuleLegacy
  RuleReporting
  RuleSourceSyntax
  RuleMaintenance
  RuleTrustedTypes
  RuleWorker
  RuleMedia
  RuleNavigation
} derive(Eq, Debug)

///|
/// Static metadata for audit rules and recommendation mapping.
pub(all) struct AuditRuleSpec {
  code : String
  category : AuditRuleCategory
  severity : Severity
  directive : String?
  trigger : String
  remediation : String
  rationale : String
} derive(Eq, Debug)

///|
fn make_audit_rule_spec(
  code : String,
  category : AuditRuleCategory,
  severity : Severity,
  directive : String?,
  trigger : String,
  remediation : String,
  rationale : String,
) -> AuditRuleSpec {
  { code, category, severity, directive, trigger, remediation, rationale }
}

///|
pub fn AuditRuleCategory::name(self : AuditRuleCategory) -> String {
  match self {
    RuleBaseline => "baseline"
    RuleScript => "script"
    RuleTransport => "transport"
    RuleEmbedding => "embedding"
    RuleLegacy => "legacy"
    RuleReporting => "reporting"
    RuleSourceSyntax => "source-syntax"
    RuleMaintenance => "maintenance"
    RuleTrustedTypes => "trusted-types"
    RuleWorker => "worker"
    RuleMedia => "media"
    RuleNavigation => "navigation"
  }
}

///|
pub fn AuditRuleSpec::summary(self : AuditRuleSpec) -> String {
  let directive = match self.directive {
    Some(name) => name
    None => "-"
  }
  self.code +
  " " +
  self.category.name() +
  " " +
  self.severity.label() +
  " " +
  directive
}

///|
pub fn baseline_audit_rule_specs() -> Array[AuditRuleSpec] {
  let rules : Array[AuditRuleSpec] = []
  rules.push(
    make_audit_rule_spec(
      "missing-default-src",
      RuleBaseline,
      High,
      Some("default-src"),
      "Policy has no default-src directive.",
      "Add default-src with a narrow baseline such as 'self' or 'none'.",
      "Most fetch directives use default-src as their fallback.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "missing-object-src",
      RuleBaseline,
      Warning,
      Some("object-src"),
      "Policy has no object-src directive.",
      "Add object-src 'none' unless legacy plugin loading is required.",
      "Object and embed content expands attack surface in modern applications.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "missing-base-uri",
      RuleBaseline,
      Warning,
      Some("base-uri"),
      "Policy has no base-uri directive.",
      "Add base-uri 'self' or base-uri 'none'.",
      "A hostile base element can rewrite relative URL resolution.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "missing-frame-ancestors",
      RuleEmbedding,
      Warning,
      Some("frame-ancestors"),
      "Policy has no frame-ancestors directive.",
      "Add frame-ancestors 'none' or an explicit embedding allowlist.",
      "Clickjacking and unwanted embedding are common deployment risks.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "missing-form-action",
      RuleNavigation,
      Warning,
      Some("form-action"),
      "Policy has no form-action directive.",
      "Add form-action 'self' or an explicit target list.",
      "Unexpected form submissions can leak data or bypass intended workflows.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "missing-worker-src",
      RuleWorker,
      Info,
      Some("worker-src"),
      "Policy has no worker-src directive.",
      "Add worker-src when the app uses workers or service workers.",
      "Worker loading can otherwise inherit a broader fallback than intended.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "missing-connect-src",
      RuleTransport,
      Info,
      Some("connect-src"),
      "Policy has no connect-src directive.",
      "Add connect-src 'self' or a narrow API endpoint list.",
      "Network endpoints deserve explicit review in applications.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "missing-img-src",
      RuleMedia,
      Info,
      Some("img-src"),
      "Policy has no img-src directive.",
      "Add img-src with the image origins used by the page.",
      "Explicit media directives make default-src stricter without breaking images.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "missing-style-src",
      RuleSourceSyntax,
      Info,
      Some("style-src"),
      "Policy has no style-src directive.",
      "Add style-src with local styles or an explicit stylesheet allowlist.",
      "Stylesheet rules often need a different boundary from scripts.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "missing-font-src",
      RuleMedia,
      Info,
      Some("font-src"),
      "Policy has no font-src directive.",
      "Add font-src if the app loads custom fonts.",
      "Explicit font policy avoids accidental inheritance from broad defaults.",
    ),
  )
  rules
}

///|
pub fn source_audit_rule_specs() -> Array[AuditRuleSpec] {
  let rules : Array[AuditRuleSpec] = []
  rules.push(
    make_audit_rule_spec(
      "wildcard-source",
      RuleSourceSyntax,
      High,
      None,
      "A directive contains *.",
      "Replace the wildcard with explicit origins or safer keywords.",
      "Wildcard sources remove much of the protection CSP is meant to provide.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "wildcard-host",
      RuleSourceSyntax,
      Warning,
      None,
      "A directive contains a wildcard host.",
      "Prefer explicit subdomains or split policy by environment.",
      "Wildcard hosts can accidentally include untrusted tenants or forgotten services.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "none-with-other-sources",
      RuleSourceSyntax,
      Warning,
      None,
      "'none' is mixed with other source expressions.",
      "Use 'none' alone or remove it.",
      "'none' mixed with other values is confusing and often ignored by readers.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "unknown-source-token",
      RuleSourceSyntax,
      Info,
      None,
      "A source token is not recognized by CSPKit.",
      "Review the token spelling and browser support.",
      "Unknown source expressions may be typos or new CSP syntax.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "unknown-keyword",
      RuleSourceSyntax,
      Warning,
      None,
      "A quoted keyword is not recognized.",
      "Use a standard CSP keyword or document the extension.",
      "Quoted keywords are easy to mistype and may be ignored.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "host-with-path",
      RuleSourceSyntax,
      Info,
      None,
      "A host source includes a path.",
      "Keep path allowlists only when path scoping is intentionally required.",
      "Path matching can be subtle and deserves review.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "host-with-port",
      RuleSourceSyntax,
      Info,
      None,
      "A host source includes a port.",
      "Use explicit ports only when deployment truly needs them.",
      "Ports in CSP often indicate environment-specific policy.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "scheme-wide-source",
      RuleSourceSyntax,
      Warning,
      None,
      "A directive allows a full scheme.",
      "Prefer explicit host sources.",
      "Scheme-wide allowlists can include too many origins.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "broad-https-scheme",
      RuleSourceSyntax,
      Warning,
      None,
      "A directive uses https: as a scheme-wide allowlist.",
      "Prefer explicit host sources when possible.",
      "Scheme-wide trust can include origins outside the application's control.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "broad-http-scheme",
      RuleTransport,
      High,
      None,
      "A directive uses http: as a scheme-wide allowlist.",
      "Use HTTPS and explicit origins.",
      "Plain HTTP plus scheme-wide trust is a severe transport weakness.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "data-source-sensitive-context",
      RuleSourceSyntax,
      High,
      None,
      "data: appears in script, object, or default sources.",
      "Avoid data: for sensitive directives.",
      "data: URLs are difficult to reason about and can carry executable content.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "blob-source",
      RuleSourceSyntax,
      Warning,
      None,
      "A directive allows blob: URLs.",
      "Limit blob: to image, media, or worker contexts that need it.",
      "Blob URLs can hide provenance when used broadly.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "filesystem-source",
      RuleSourceSyntax,
      Warning,
      None,
      "A directive allows filesystem:.",
      "Remove filesystem: unless supporting a known legacy environment.",
      "filesystem: is rarely needed and difficult to audit.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "mediastream-source",
      RuleMedia,
      Warning,
      None,
      "A directive allows mediastream:.",
      "Limit mediastream: to media contexts that require it.",
      "Media stream URLs should not be allowed broadly.",
    ),
  )
  rules
}

///|
pub fn script_audit_rule_specs() -> Array[AuditRuleSpec] {
  let rules : Array[AuditRuleSpec] = []
  rules.push(
    make_audit_rule_spec(
      "unsafe-inline",
      RuleScript,
      High,
      Some("script-src"),
      "script-src contains unsafe-inline.",
      "Use nonces or hashes and remove unsafe-inline.",
      "Inline script permissions make injection bugs easier to exploit.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "unsafe-eval",
      RuleScript,
      High,
      Some("script-src"),
      "Script policy allows dynamic evaluation.",
      "Remove unsafe-eval or document a narrow runtime exception.",
      "Dynamic evaluation turns string injection into code execution.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "wasm-unsafe-eval",
      RuleScript,
      Warning,
      Some("script-src"),
      "Script policy allows dynamic WebAssembly compilation.",
      "Allow wasm-unsafe-eval only when required by the runtime.",
      "Dynamic wasm compilation broadens the executable surface.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "script-without-nonce-or-hash",
      RuleScript,
      Info,
      Some("script-src"),
      "script-src has no nonce or hash source.",
      "Consider nonce or hash based trust for strict deployments.",
      "Host-only script policies can be bypassed through trusted origin compromise.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "strict-source-token",
      RuleScript,
      Info,
      None,
      "A nonce or hash source is present.",
      "Keep nonce/hash values unpredictable and deployment-specific.",
      "Cryptographic source tokens are a positive signal but still need careful operations.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "strict-dynamic-without-nonce",
      RuleScript,
      Warning,
      Some("script-src"),
      "strict-dynamic appears without nonce or hash trust.",
      "Pair strict-dynamic with a nonce or hash source.",
      "strict-dynamic is useful only when a trusted script root exists.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "script-blob-source",
      RuleScript,
      High,
      Some("script-src"),
      "script-src allows blob:.",
      "Avoid blob: in script-src unless a documented worker pattern requires it.",
      "Blob scripts can weaken review of executable content provenance.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "script-data-source",
      RuleScript,
      High,
      Some("script-src"),
      "script-src allows data:.",
      "Remove data: from script-src.",
      "data: scripts are hard to audit and should be treated as severe risk.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "script-http-source",
      RuleTransport,
      High,
      Some("script-src"),
      "script-src allows plain HTTP.",
      "Use HTTPS script endpoints.",
      "Plain HTTP script can be modified in transit.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "script-host-allowlist",
      RuleScript,
      Info,
      Some("script-src"),
      "script-src uses host allowlists.",
      "Review every script host for ownership and supply chain risk.",
      "Trusted script origins become part of the application security boundary.",
    ),
  )
  rules
}

///|
pub fn transport_audit_rule_specs() -> Array[AuditRuleSpec] {
  let rules : Array[AuditRuleSpec] = []
  rules.push(
    make_audit_rule_spec(
      "insecure-http-source",
      RuleTransport,
      Warning,
      None,
      "A source uses http: or http://.",
      "Use HTTPS endpoints or upgrade-insecure-requests.",
      "Plain HTTP can be modified in transit.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "missing-upgrade-insecure-requests",
      RuleTransport,
      Info,
      Some("upgrade-insecure-requests"),
      "Policy does not request automatic HTTPS upgrade.",
      "Add upgrade-insecure-requests for HTTPS-only deployments.",
      "The directive helps migrate stale HTTP subresource references.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "connect-src-any-origin",
      RuleTransport,
      High,
      Some("connect-src"),
      "connect-src allows any origin.",
      "Limit connect-src to API, telemetry, and websocket endpoints.",
      "Broad network access can leak data or hide malicious exfiltration.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "connect-src-http",
      RuleTransport,
      Warning,
      Some("connect-src"),
      "connect-src uses plain HTTP.",
      "Move API endpoints to HTTPS.",
      "Plain HTTP APIs can be read or modified in transit.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "websocket-broad",
      RuleTransport,
      Warning,
      Some("connect-src"),
      "connect-src allows a broad websocket scheme.",
      "Prefer explicit wss:// endpoints.",
      "Broad websocket trust weakens network boundary review.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "reporting-http",
      RuleTransport,
      Warning,
      Some("report-uri"),
      "Violation reports are sent over HTTP.",
      "Use HTTPS reporting endpoints.",
      "Violation reports may contain sensitive paths and samples.",
    ),
  )
  rules
}

///|
pub fn legacy_audit_rule_specs() -> Array[AuditRuleSpec] {
  let rules : Array[AuditRuleSpec] = []
  rules.push(
    make_audit_rule_spec(
      "deprecated-directive",
      RuleLegacy,
      Warning,
      None,
      "Policy contains a deprecated directive.",
      "Remove or replace deprecated directives.",
      "Deprecated directives can give a false sense of protection.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "plugin-types",
      RuleLegacy,
      Warning,
      Some("plugin-types"),
      "Policy uses plugin-types.",
      "Prefer object-src 'none' and avoid legacy plugins.",
      "Plugin controls are less relevant for modern browsers.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "referrer-directive",
      RuleLegacy,
      Warning,
      Some("referrer"),
      "Policy uses the deprecated referrer directive.",
      "Use the Referrer-Policy header instead.",
      "Referrer behavior moved out of CSP.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "reflected-xss",
      RuleLegacy,
      Warning,
      Some("reflected-xss"),
      "Policy uses reflected-xss.",
      "Remove reflected-xss and rely on modern browser defenses.",
      "Legacy XSS filters are removed or ignored in modern browsers.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "require-sri-for",
      RuleLegacy,
      Warning,
      Some("require-sri-for"),
      "Policy uses require-sri-for.",
      "Use build tooling and markup integrity attributes instead.",
      "The directive was removed from CSP.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "block-all-mixed-content",
      RuleLegacy,
      Info,
      Some("block-all-mixed-content"),
      "Policy uses block-all-mixed-content.",
      "Prefer upgrade-insecure-requests and HTTPS-only deployment.",
      "Modern mixed content handling made this directive less useful.",
    ),
  )
  rules
}

///|
pub fn reporting_audit_rule_specs() -> Array[AuditRuleSpec] {
  let rules : Array[AuditRuleSpec] = []
  rules.push(
    make_audit_rule_spec(
      "missing-reporting",
      RuleReporting,
      Info,
      Some("report-to"),
      "Policy has no reporting endpoint.",
      "Add report-to or report-uri when violation telemetry is useful.",
      "Reporting makes gradual CSP hardening safer.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "report-uri-only",
      RuleReporting,
      Info,
      Some("report-uri"),
      "Policy uses only report-uri.",
      "Consider report-to when the deployment supports Reporting API.",
      "report-to is the modern reporting path, while report-uri remains widely used.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "report-to-without-endpoint",
      RuleReporting,
      Info,
      Some("report-to"),
      "Policy names a report-to group.",
      "Ensure the matching Reporting-Endpoints header is deployed.",
      "CSP alone does not define the endpoint group transport details.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "report-sample-present",
      RuleReporting,
      Info,
      Some("script-src"),
      "Policy requests report-sample.",
      "Avoid collecting sensitive snippets in logs.",
      "Violation samples can help debugging but may include user data.",
    ),
  )
  rules
}

///|
pub fn trusted_types_audit_rule_specs() -> Array[AuditRuleSpec] {
  let rules : Array[AuditRuleSpec] = []
  rules.push(
    make_audit_rule_spec(
      "missing-trusted-types",
      RuleTrustedTypes,
      Info,
      Some("trusted-types"),
      "Policy has no Trusted Types controls.",
      "Consider trusted-types and require-trusted-types-for on script-heavy apps.",
      "Trusted Types can reduce DOM injection risk in compatible browsers.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "trusted-types-star",
      RuleTrustedTypes,
      Warning,
      Some("trusted-types"),
      "trusted-types allows any policy name.",
      "List the exact Trusted Types policy names used by the application.",
      "A wildcard policy name weakens Trusted Types governance.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "trusted-types-none",
      RuleTrustedTypes,
      Info,
      Some("trusted-types"),
      "trusted-types is set to 'none'.",
      "Ensure the application does not require custom Trusted Types policies.",
      "This is strict and may be appropriate for low-script pages.",
    ),
  )
  rules.push(
    make_audit_rule_spec(
      "require-trusted-types-script",
      RuleTrustedTypes,
      Info,
      Some("require-trusted-types-for"),
      "Policy requires Trusted Types for script sinks.",
      "Keep compatibility tests for supported browsers.",
      "This is a strong signal for DOM XSS hardening.",
    ),
  )
  rules
}

///|
pub fn extended_audit_rule_specs() -> Array[AuditRuleSpec] {
  let rules : Array[AuditRuleSpec] = []
  for rule in baseline_audit_rule_specs() {
    rules.push(rule)
  }
  for rule in source_audit_rule_specs() {
    rules.push(rule)
  }
  for rule in script_audit_rule_specs() {
    rules.push(rule)
  }
  for rule in transport_audit_rule_specs() {
    rules.push(rule)
  }
  for rule in legacy_audit_rule_specs() {
    rules.push(rule)
  }
  for rule in reporting_audit_rule_specs() {
    rules.push(rule)
  }
  for rule in trusted_types_audit_rule_specs() {
    rules.push(rule)
  }
  rules
}

///|
pub fn audit_rule_spec_by_code(code : StringView) -> AuditRuleSpec? {
  let key = lower_ascii(code)
  for rule in extended_audit_rule_specs() {
    if rule.code == key {
      return Some(rule)
    }
  }
  None
}

///|
pub fn audit_rules_by_category(
  category : AuditRuleCategory,
) -> Array[AuditRuleSpec] {
  extended_audit_rule_specs().filter(rule => rule.category == category)
}

///|
pub fn audit_rules_by_severity(severity : Severity) -> Array[AuditRuleSpec] {
  extended_audit_rule_specs().filter(rule => rule.severity == severity)
}

///|
pub fn audit_rule_catalog_report() -> String {
  let rows : Array[String] = []
  for rule in extended_audit_rule_specs() {
    rows.push(rule.summary() + " " + rule.remediation)
  }
  rows.join("\n")
}

///|
pub fn Finding::rule_spec(self : Finding) -> AuditRuleSpec? {
  audit_rule_spec_by_code(self.code[:])
}

///|
pub fn Finding::remediation(self : Finding) -> String {
  match self.rule_spec() {
    Some(rule) => rule.remediation
    None => "Review the finding and adjust the policy intentionally."
  }
}

///|
pub fn Finding::rationale(self : Finding) -> String {
  match self.rule_spec() {
    Some(rule) => rule.rationale
    None => self.message
  }
}

///|
pub fn Policy::finding_remediation_report(self : Policy) -> String {
  let rows : Array[String] = []
  for finding in self.audit() {
    rows.push(finding.code + ": " + finding.remediation())
  }
  rows.join("\n")
}

///|
pub fn audit_rule_catalog_summary() -> String {
  let all = extended_audit_rule_specs()
  let high = audit_rules_by_severity(High)
  let warning = audit_rules_by_severity(Warning)
  let info = audit_rules_by_severity(Info)
  "rules=\{all.length()} high=\{high.length()} warning=\{warning.length()} info=\{info.length()}"
}