///|
pub(all) enum RedactionMode {
  Redact
  Drop
} derive(Eq, Compare, Debug)

///|
pub fn RedactionMode::to_string(self : RedactionMode) -> String {
  match self {
    Redact => "REDACT"
    Drop => "DROP"
  }
}

///|
pub impl Show for RedactionMode with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
pub(all) struct RedactionPolicy {
  deny : Array[String]
  allow : Array[String]?
  mode : RedactionMode
  placeholder : String
} derive(Debug)

///|
pub fn redaction_policy(
  deny? : Array[String] = ["password", "token", "secret"],
  allow? : Array[String]? = None,
  mode? : RedactionMode = Redact,
  placeholder? : String = "[REDACTED]",
) -> RedactionPolicy {
  {
    deny: deny.copy(),
    allow: match allow {
      Some(allowed) => Some(allowed.copy())
      None => None
    },
    mode,
    placeholder,
  }
}

///|
pub fn default_redaction_policy() -> RedactionPolicy {
  redaction_policy()
}

///|
fn key_matches_allow(key : String, allow : Array[String]) -> Bool {
  let lower_key = key.to_lower()
  for allowed in allow {
    if lower_key == allowed.to_lower() {
      return true
    }
  }
  false
}

///|
fn key_matches_deny(key : String, deny : Array[String]) -> Bool {
  let lower_key = key.to_lower()
  for pattern in deny {
    let lower_pattern = pattern.to_lower()
    if lower_key.contains(lower_pattern) {
      return true
    }
  }
  false
}

///|
fn should_transform_field(key : String, policy : RedactionPolicy) -> Bool {
  match policy.allow {
    Some(allow) => !key_matches_allow(key, allow)
    None => key_matches_deny(key, policy.deny)
  }
}

///|
fn redact_field(field : Field, policy : RedactionPolicy) -> Field? {
  match should_transform_field(field.key, policy) {
    false => Some(field)
    true =>
      match policy.mode {
        Redact => Some({ key: field.key, value: policy.placeholder.to_json() })
        Drop => None
      }
  }
}

///|
fn redact_event(event : Event, policy : RedactionPolicy) -> Event {
  let fields : Array[Field] = []
  event.fields.each(fn(field) {
    match redact_field(field, policy) {
      Some(redacted) => fields.push(redacted)
      None => ()
    }
  })
  Event::{
    level: event.level,
    message: event.message,
    fields,
    timestamp: event.timestamp,
    source: event.source,
  }
}

///|
pub fn redact(
  inner : (Event) -> Unit,
  policy? : RedactionPolicy = default_redaction_policy(),
) -> (Event) -> Unit {
  fn(event) { inner(redact_event(event, policy)) }
}