///|
fn push_if_missing(
  policy : Policy,
  findings : Array[Finding],
  directive : String,
  code : String,
  message : String,
) -> Unit {
  if !has_directive(policy, directive) {
    findings.push(make_finding(Warning, code, directive, message, 0))
  }
}

///|
fn push_if_source(
  policy : Policy,
  findings : Array[Finding],
  directive_name : String,
  source : String,
  code : String,
  message : String,
) -> Unit {
  match get_directive(policy, directive_name) {
    None => ()
    Some(directive) =>
      if directive_has_source(directive, source) {
        findings.push(
          make_finding(Warning, code, directive_name, message, directive.line),
        )
      }
  }
}

///|
fn audit_required_directives(
  policy : Policy,
  findings : Array[Finding],
) -> Unit {
  push_if_missing(
    policy, findings, "default-src", "missing-default-src", "default-src should bound fallback fetches",
  )
  push_if_missing(
    policy, findings, "object-src", "missing-object-src", "object-src 'none' is recommended",
  )
  push_if_missing(
    policy, findings, "base-uri", "missing-base-uri", "base-uri should prevent injected base tags",
  )
  push_if_missing(
    policy, findings, "frame-ancestors", "missing-frame-ancestors", "frame-ancestors should control embedding",
  )
}

///|
fn audit_unsafe_sources(policy : Policy, findings : Array[Finding]) -> Unit {
  push_if_source(
    policy, findings, "script-src", "'unsafe-inline'", "script-unsafe-inline", "inline script should use nonce or hash instead",
  )
  push_if_source(
    policy, findings, "script-src", "'unsafe-eval'", "script-unsafe-eval", "unsafe eval weakens script isolation",
  )
  push_if_source(
    policy, findings, "style-src", "'unsafe-inline'", "style-unsafe-inline", "inline styles should be migrated to nonce or hash",
  )
  push_if_source(
    policy, findings, "default-src", "*", "default-wildcard", "default-src wildcard allows broad fallback fetches",
  )
}

///|
fn audit_none_misuse(policy : Policy, findings : Array[Finding]) -> Unit {
  for directive in policy.directives {
    if directive_has_source(directive, "'none'") &&
      directive.sources.length() > 1 {
      findings.push(
        make_finding(
          Warning,
          "none-with-other-sources",
          directive.name,
          "'none' is ignored when combined with other source expressions",
          directive.line,
        ),
      )
    }
  }
}

///|
fn audit_plain_http(policy : Policy, findings : Array[Finding]) -> Unit {
  for directive in policy.directives {
    for source in directive.sources {
      if starts_with(source.value, "http:") {
        findings.push(
          make_finding(
            Warning,
            "plain-http-source",
            directive.name,
            "plain http source should be replaced with https",
            directive.line,
          ),
        )
      }
    }
  }
}

///|
fn audit_parser_diagnostics(policy : Policy, findings : Array[Finding]) -> Unit {
  for finding in policy.diagnostics {
    findings.push(finding)
  }
}

///|
/// Run the built-in CSP audit rule set.
pub fn audit(policy : Policy) -> Array[Finding] {
  let findings : Array[Finding] = []
  audit_parser_diagnostics(policy, findings)
  audit_required_directives(policy, findings)
  audit_unsafe_sources(policy, findings)
  audit_none_misuse(policy, findings)
  audit_plain_http(policy, findings)
  findings
}