///|
fn directive_for_kind(kind : ResourceKind) -> Array[String] {
  let names : Array[String] = []
  match kind {
    Script => {
      names.push("script-src")
      names.push("default-src")
    }
    Style => {
      names.push("style-src")
      names.push("default-src")
    }
    Image => {
      names.push("img-src")
      names.push("default-src")
    }
    Font => {
      names.push("font-src")
      names.push("default-src")
    }
    Connect => {
      names.push("connect-src")
      names.push("default-src")
    }
    Frame => {
      names.push("frame-src")
      names.push("child-src")
      names.push("default-src")
    }
    Media => {
      names.push("media-src")
      names.push("default-src")
    }
    Worker => {
      names.push("worker-src")
      names.push("child-src")
      names.push("script-src")
      names.push("default-src")
    }
    Object => {
      names.push("object-src")
      names.push("default-src")
    }
    Manifest => {
      names.push("manifest-src")
      names.push("default-src")
    }
    Child => {
      names.push("child-src")
      names.push("default-src")
    }
    Base => names.push("base-uri")
    Form => names.push("form-action")
    Navigate => names.push("navigate-to")
    Prefetch => {
      names.push("prefetch-src")
      names.push("default-src")
    }
    Other => names.push("default-src")
  }
  names
}

///|
fn first_effective_directive(
  policy : Policy,
  kind : ResourceKind,
) -> Directive? {
  let names = directive_for_kind(kind)
  for name in names {
    match get_directive(policy, name) {
      Some(directive) => return Some(directive)
      None => ()
    }
  }
  None
}

///|
fn source_matches_origin(source : Source, request : ResourceRequest) -> Bool {
  let url = normalize_url(request.url)
  let origin = lower_ascii(request.origin)
  let request_origin = extract_origin(url)
  let request_scheme = extract_scheme(url)
  match source.kind {
    Wildcard => true
    Keyword => source.value == "self" && request_origin == lower_ascii(origin)
    Scheme => request_scheme == source.value
    Host => {
      let pattern = extract_host_pattern(source.value)
      let host = extract_host_pattern(request_origin)
      if starts_with(pattern, "*.") {
        ends_with(host, pattern[1:].to_owned())
      } else {
        host == pattern || request_origin == source.value
      }
    }
    Nonce => false
    Hash => false
    UnknownSource => false
  }
}

///|
fn source_matches_inline(source : Source, request : ResourceRequest) -> Bool {
  match source.kind {
    Keyword => source.value == "unsafe-inline"
    Nonce => request.nonce.length() > 0 && request.nonce == source.value
    Hash => false
    _ => false
  }
}

///|
/// Check whether a resource request is allowed by the policy.
pub fn allows(policy : Policy, request : ResourceRequest) -> Decision {
  match first_effective_directive(policy, request.kind) {
    None =>
      {
        allowed: true,
        directive: "",
        matched_source: "",
        reason: "no effective directive controls " +
        resource_kind_to_string(request.kind),
      }
    Some(directive) => {
      if directive_has_source(directive, "'none'") {
        return {
          allowed: false,
          directive: directive.name,
          matched_source: "'none'",
          reason: "directive explicitly contains 'none'",
        }
      }
      for source in directive.sources {
        let matched = if request.is_inline {
          source_matches_inline(source, request)
        } else {
          source_matches_origin(source, request)
        }
        if matched {
          return {
            allowed: true,
            directive: directive.name,
            matched_source: source.raw,
            reason: "matched source " + source.raw,
          }
        }
      }
      {
        allowed: false,
        directive: directive.name,
        matched_source: "",
        reason: "no source matched " + request.url,
      }
    }
  }
}