///|
/// CSP directive families used by the catalog and reports.
pub(all) enum DirectiveCategory {
  DirectiveFetch
  DirectiveDocument
  DirectiveNavigation
  DirectiveReporting
  DirectiveSandbox
  DirectiveTrustedTypes
  DirectiveUpgrade
  DirectiveDeprecated
  DirectiveExperimental
  DirectiveOther
} derive(Eq, Debug)

///|
/// Metadata for a known CSP directive.
pub(all) struct DirectiveSpec {
  name : String
  category : DirectiveCategory
  fallback : String?
  repeatable : Bool
  level : String
  description : String
} derive(Eq, Debug)

///|
fn make_directive_spec(
  name : String,
  category : DirectiveCategory,
  fallback : String?,
  repeatable : Bool,
  level : String,
  description : String,
) -> DirectiveSpec {
  { name, category, fallback, repeatable, level, description }
}

///|
pub fn DirectiveCategory::name(self : DirectiveCategory) -> String {
  match self {
    DirectiveFetch => "fetch"
    DirectiveDocument => "document"
    DirectiveNavigation => "navigation"
    DirectiveReporting => "reporting"
    DirectiveSandbox => "sandbox"
    DirectiveTrustedTypes => "trusted-types"
    DirectiveUpgrade => "upgrade"
    DirectiveDeprecated => "deprecated"
    DirectiveExperimental => "experimental"
    DirectiveOther => "other"
  }
}

///|
pub fn DirectiveCategory::is_security_core(self : DirectiveCategory) -> Bool {
  self == DirectiveFetch ||
  self == DirectiveDocument ||
  self == DirectiveNavigation ||
  self == DirectiveSandbox ||
  self == DirectiveTrustedTypes
}

///|
pub fn DirectiveSpec::summary(self : DirectiveSpec) -> String {
  let fallback = match self.fallback {
    Some(name) => name
    None => "-"
  }
  self.name +
  " category=" +
  self.category.name() +
  " level=" +
  self.level +
  " fallback=" +
  fallback
}

///|
pub fn DirectiveSpec::is_fetch(self : DirectiveSpec) -> Bool {
  self.category == DirectiveFetch
}

///|
pub fn DirectiveSpec::has_fallback(self : DirectiveSpec) -> Bool {
  self.fallback is Some(_)
}

///|
pub fn DirectiveSpec::is_deprecated(self : DirectiveSpec) -> Bool {
  self.category == DirectiveDeprecated
}

///|
pub fn DirectiveSpec::is_experimental(self : DirectiveSpec) -> Bool {
  self.category == DirectiveExperimental
}

///|
pub fn directive_spec(name : StringView) -> DirectiveSpec? {
  let key = lower_ascii(name)
  if key == "base-uri" {
    Some(
      make_directive_spec(
        "base-uri",
        DirectiveDocument,
        None,
        false,
        "CSP2",
        "Restricts URLs that may be used in a document base element.",
      ),
    )
  } else if key == "child-src" {
    Some(
      make_directive_spec(
        "child-src",
        DirectiveFetch,
        Some("default-src"),
        false,
        "CSP2",
        "Controls nested browsing contexts and workers in older policies.",
      ),
    )
  } else if key == "connect-src" {
    Some(
      make_directive_spec(
        "connect-src",
        DirectiveFetch,
        Some("default-src"),
        false,
        "CSP1",
        "Controls fetch, websocket, EventSource, beacon, and XHR endpoints.",
      ),
    )
  } else if key == "default-src" {
    Some(
      make_directive_spec(
        "default-src",
        DirectiveFetch,
        None,
        false,
        "CSP1",
        "Provides fallback sources for fetch directives.",
      ),
    )
  } else if key == "font-src" {
    Some(
      make_directive_spec(
        "font-src",
        DirectiveFetch,
        Some("default-src"),
        false,
        "CSP1",
        "Controls font loading sources.",
      ),
    )
  } else if key == "form-action" {
    Some(
      make_directive_spec(
        "form-action",
        DirectiveNavigation,
        None,
        false,
        "CSP2",
        "Restricts form submission targets.",
      ),
    )
  } else if key == "frame-ancestors" {
    Some(
      make_directive_spec(
        "frame-ancestors",
        DirectiveNavigation,
        None,
        false,
        "CSP2",
        "Restricts which pages may embed the protected resource.",
      ),
    )
  } else if key == "frame-src" {
    Some(
      make_directive_spec(
        "frame-src",
        DirectiveFetch,
        Some("child-src"),
        false,
        "CSP1/CSP3",
        "Controls frame and iframe sources.",
      ),
    )
  } else if key == "img-src" {
    Some(
      make_directive_spec(
        "img-src",
        DirectiveFetch,
        Some("default-src"),
        false,
        "CSP1",
        "Controls image and favicon sources.",
      ),
    )
  } else if key == "manifest-src" {
    Some(
      make_directive_spec(
        "manifest-src",
        DirectiveFetch,
        Some("default-src"),
        false,
        "CSP3",
        "Controls web app manifest loading.",
      ),
    )
  } else if key == "media-src" {
    Some(
      make_directive_spec(
        "media-src",
        DirectiveFetch,
        Some("default-src"),
        false,
        "CSP1",
        "Controls audio, video, and text track sources.",
      ),
    )
  } else if key == "object-src" {
    Some(
      make_directive_spec(
        "object-src",
        DirectiveFetch,
        Some("default-src"),
        false,
        "CSP1",
        "Controls plugin-like object, embed, and applet sources.",
      ),
    )
  } else if key == "prefetch-src" {
    Some(
      make_directive_spec(
        "prefetch-src",
        DirectiveFetch,
        Some("default-src"),
        false,
        "CSP3",
        "Controls prefetch and prerender hints.",
      ),
    )
  } else if key == "script-src" {
    Some(
      make_directive_spec(
        "script-src",
        DirectiveFetch,
        Some("default-src"),
        false,
        "CSP1",
        "Controls script execution and loading.",
      ),
    )
  } else if key == "script-src-elem" {
    Some(
      make_directive_spec(
        "script-src-elem",
        DirectiveFetch,
        Some("script-src"),
        false,
        "CSP3",
        "Controls script elements.",
      ),
    )
  } else if key == "script-src-attr" {
    Some(
      make_directive_spec(
        "script-src-attr",
        DirectiveFetch,
        Some("script-src"),
        false,
        "CSP3",
        "Controls inline event handler attributes.",
      ),
    )
  } else if key == "style-src" {
    Some(
      make_directive_spec(
        "style-src",
        DirectiveFetch,
        Some("default-src"),
        false,
        "CSP1",
        "Controls stylesheet sources and inline style behavior.",
      ),
    )
  } else if key == "style-src-elem" {
    Some(
      make_directive_spec(
        "style-src-elem",
        DirectiveFetch,
        Some("style-src"),
        false,
        "CSP3",
        "Controls stylesheet elements.",
      ),
    )
  } else if key == "style-src-attr" {
    Some(
      make_directive_spec(
        "style-src-attr",
        DirectiveFetch,
        Some("style-src"),
        false,
        "CSP3",
        "Controls inline style attributes.",
      ),
    )
  } else if key == "worker-src" {
    Some(
      make_directive_spec(
        "worker-src",
        DirectiveFetch,
        Some("child-src"),
        false,
        "CSP3",
        "Controls worker, shared worker, and service worker sources.",
      ),
    )
  } else if key == "navigate-to" {
    Some(
      make_directive_spec(
        "navigate-to",
        DirectiveNavigation,
        None,
        false,
        "CSP3",
        "Restricts top-level navigations initiated by the protected page.",
      ),
    )
  } else if key == "report-uri" {
    Some(
      make_directive_spec(
        "report-uri",
        DirectiveReporting,
        None,
        true,
        "CSP1",
        "Defines legacy violation reporting endpoints.",
      ),
    )
  } else if key == "report-to" {
    Some(
      make_directive_spec(
        "report-to",
        DirectiveReporting,
        None,
        false,
        "CSP3",
        "Defines a Reporting API endpoint group for violations.",
      ),
    )
  } else if key == "sandbox" {
    Some(
      make_directive_spec(
        "sandbox",
        DirectiveSandbox,
        None,
        false,
        "CSP1",
        "Applies sandbox flags to the protected resource.",
      ),
    )
  } else if key == "upgrade-insecure-requests" {
    Some(
      make_directive_spec(
        "upgrade-insecure-requests",
        DirectiveUpgrade,
        None,
        false,
        "CSP2",
        "Asks user agents to upgrade HTTP subresource requests to HTTPS.",
      ),
    )
  } else if key == "block-all-mixed-content" {
    Some(
      make_directive_spec(
        "block-all-mixed-content",
        DirectiveDeprecated,
        None,
        false,
        "CSP2",
        "Deprecated mixed-content blocking directive.",
      ),
    )
  } else if key == "require-sri-for" {
    Some(
      make_directive_spec(
        "require-sri-for",
        DirectiveDeprecated,
        None,
        false,
        "CSP3 removed",
        "Removed directive for requiring subresource integrity.",
      ),
    )
  } else if key == "plugin-types" {
    Some(
      make_directive_spec(
        "plugin-types",
        DirectiveDeprecated,
        None,
        false,
        "CSP2",
        "Deprecated plugin MIME type restriction.",
      ),
    )
  } else if key == "referrer" {
    Some(
      make_directive_spec(
        "referrer",
        DirectiveDeprecated,
        None,
        false,
        "CSP2",
        "Deprecated referrer directive replaced by Referrer-Policy.",
      ),
    )
  } else if key == "reflected-xss" {
    Some(
      make_directive_spec(
        "reflected-xss",
        DirectiveDeprecated,
        None,
        false,
        "CSP1",
        "Deprecated reflected XSS filter directive.",
      ),
    )
  } else if key == "require-trusted-types-for" {
    Some(
      make_directive_spec(
        "require-trusted-types-for",
        DirectiveTrustedTypes,
        None,
        false,
        "CSP3",
        "Requires Trusted Types for selected DOM injection sinks.",
      ),
    )
  } else if key == "trusted-types" {
    Some(
      make_directive_spec(
        "trusted-types",
        DirectiveTrustedTypes,
        None,
        false,
        "CSP3",
        "Restricts Trusted Types policy names.",
      ),
    )
  } else if key == "webrtc" {
    Some(
      make_directive_spec(
        "webrtc",
        DirectiveExperimental,
        None,
        false,
        "CSP experimental",
        "Experimental control for WebRTC IP handling behavior.",
      ),
    )
  } else if key == "fenced-frame-src" {
    Some(
      make_directive_spec(
        "fenced-frame-src",
        DirectiveExperimental,
        Some("child-src"),
        false,
        "CSP experimental",
        "Experimental fenced frame source control.",
      ),
    )
  } else {
    None
  }
}

///|
pub fn is_known_directive_name(name : StringView) -> Bool {
  directive_spec(name) is Some(_)
}

///|
pub fn directive_category(name : StringView) -> DirectiveCategory {
  match directive_spec(name) {
    Some(spec) => spec.category
    None => DirectiveOther
  }
}

///|
pub fn directive_fallback_parent(name : StringView) -> String? {
  match directive_spec(name) {
    Some(spec) => spec.fallback
    None => None
  }
}

///|
pub fn known_directive_names() -> Array[String] {
  let names : Array[String] = []
  names.push("base-uri")
  names.push("child-src")
  names.push("connect-src")
  names.push("default-src")
  names.push("font-src")
  names.push("form-action")
  names.push("frame-ancestors")
  names.push("frame-src")
  names.push("img-src")
  names.push("manifest-src")
  names.push("media-src")
  names.push("object-src")
  names.push("prefetch-src")
  names.push("script-src")
  names.push("script-src-elem")
  names.push("script-src-attr")
  names.push("style-src")
  names.push("style-src-elem")
  names.push("style-src-attr")
  names.push("worker-src")
  names.push("navigate-to")
  names.push("report-uri")
  names.push("report-to")
  names.push("sandbox")
  names.push("upgrade-insecure-requests")
  names.push("block-all-mixed-content")
  names.push("require-sri-for")
  names.push("plugin-types")
  names.push("referrer")
  names.push("reflected-xss")
  names.push("require-trusted-types-for")
  names.push("trusted-types")
  names.push("webrtc")
  names.push("fenced-frame-src")
  names
}

///|
pub fn fetch_directive_names() -> Array[String] {
  let names : Array[String] = []
  names.push("child-src")
  names.push("connect-src")
  names.push("default-src")
  names.push("font-src")
  names.push("frame-src")
  names.push("img-src")
  names.push("manifest-src")
  names.push("media-src")
  names.push("object-src")
  names.push("prefetch-src")
  names.push("script-src")
  names.push("script-src-elem")
  names.push("script-src-attr")
  names.push("style-src")
  names.push("style-src-elem")
  names.push("style-src-attr")
  names.push("worker-src")
  names.push("fenced-frame-src")
  names
}

///|
pub fn document_directive_names() -> Array[String] {
  let names : Array[String] = []
  names.push("base-uri")
  names.push("sandbox")
  names.push("trusted-types")
  names.push("require-trusted-types-for")
  names
}

///|
pub fn navigation_directive_names() -> Array[String] {
  let names : Array[String] = []
  names.push("form-action")
  names.push("frame-ancestors")
  names.push("navigate-to")
  names
}

///|
pub fn reporting_directive_names() -> Array[String] {
  let names : Array[String] = []
  names.push("report-uri")
  names.push("report-to")
  names
}

///|
pub fn deprecated_directive_names() -> Array[String] {
  let names : Array[String] = []
  names.push("block-all-mixed-content")
  names.push("require-sri-for")
  names.push("plugin-types")
  names.push("referrer")
  names.push("reflected-xss")
  names
}

///|
pub fn hardening_directive_names() -> Array[String] {
  let names : Array[String] = []
  names.push("default-src")
  names.push("object-src")
  names.push("base-uri")
  names.push("frame-ancestors")
  names.push("form-action")
  names.push("upgrade-insecure-requests")
  names
}

///|
pub fn known_directive_specs() -> Array[DirectiveSpec] {
  let specs : Array[DirectiveSpec] = []
  for name in known_directive_names() {
    match directive_spec(name[:]) {
      Some(spec) => specs.push(spec)
      None => ()
    }
  }
  specs
}

///|
pub fn known_directives_by_category(
  category : DirectiveCategory,
) -> Array[DirectiveSpec] {
  known_directive_specs().filter(spec => spec.category == category)
}

///|
pub fn known_directive_catalog_report() -> String {
  let lines : Array[String] = []
  for spec in known_directive_specs() {
    lines.push(spec.summary())
  }
  lines.join("\n")
}

///|
pub fn Policy::unknown_directives(self : Policy) -> Array[Directive] {
  self.directives.filter(directive => {
    !is_known_directive_name(directive.name[:])
  })
}

///|
pub fn Policy::deprecated_directives(self : Policy) -> Array[Directive] {
  self.directives.filter(directive => {
    match directive_spec(directive.name[:]) {
      Some(spec) => spec.is_deprecated()
      None => false
    }
  })
}

///|
pub fn Policy::experimental_directives(self : Policy) -> Array[Directive] {
  self.directives.filter(directive => {
    match directive_spec(directive.name[:]) {
      Some(spec) => spec.is_experimental()
      None => false
    }
  })
}

///|
pub fn Policy::directives_in_category(
  self : Policy,
  category : DirectiveCategory,
) -> Array[Directive] {
  self.directives.filter(directive => {
    directive_category(directive.name[:]) == category
  })
}

///|
pub fn Policy::fetch_directives(self : Policy) -> Array[Directive] {
  self.directives_in_category(DirectiveFetch)
}

///|
pub fn Policy::document_directives(self : Policy) -> Array[Directive] {
  self.directives_in_category(DirectiveDocument)
}

///|
pub fn Policy::navigation_directives(self : Policy) -> Array[Directive] {
  self.directives_in_category(DirectiveNavigation)
}

///|
pub fn Policy::reporting_directives(self : Policy) -> Array[Directive] {
  self.directives_in_category(DirectiveReporting)
}

///|
pub fn Policy::directive_catalog_summary(self : Policy) -> String {
  let lines : Array[String] = []
  lines.push(
    "known=\{self.directives.length() - self.unknown_directives().length()}",
  )
  lines.push("unknown=\{self.unknown_directives().length()}")
  lines.push("deprecated=\{self.deprecated_directives().length()}")
  lines.push("experimental=\{self.experimental_directives().length()}")
  lines.push("fetch=\{self.fetch_directives().length()}")
  lines.push("document=\{self.document_directives().length()}")
  lines.push("navigation=\{self.navigation_directives().length()}")
  lines.push("reporting=\{self.reporting_directives().length()}")
  lines.join("\n")
}