///| Keyword matching against a message's subject, body or addresses.

///|
pub struct RuleMatch {
  rule : Rule
  keywords : Array[String]
  fields : Array[String]
}

///|
pub fn rule_applies(rule : Rule, account : String, mailbox : String) -> Bool {
  let mut account_ok = rule.accounts.length() == 0
  for name in rule.accounts {
    if name == account {
      account_ok = true
    }
  }
  let mut mailbox_ok = rule.mailboxes.length() == 0
  for name in rule.mailboxes {
    if name == mailbox {
      mailbox_ok = true
    }
  }
  account_ok && mailbox_ok
}

///|
fn field_text(message : MailMessage, field : MatchField) -> String {
  match field {
    Subject => message.subject
    Body => message.body
    From => message.from
    To => message.to
  }
}

///|
fn keyword_matches(
  keyword : String,
  haystack : String,
  ignore_case : Bool,
  regex : Bool,
) -> Bool {
  if regex {
    let pattern = if ignore_case { "(?i:" + keyword + ")" } else { keyword }
    let compiled = @string.Regex::Regex(pattern) catch { _ => return false }
    match compiled.execute(haystack) {
      Some(_) => true
      None => false
    }
  } else if ignore_case {
    haystack.to_lower().contains(keyword.to_lower())
  } else {
    haystack.contains(keyword)
  }
}

///|
/// Test one rule against one message.
pub fn match_rule(rule : Rule, message : MailMessage) -> RuleMatch? {
  let matched_keywords : Array[String] = []
  let matched_fields : Array[String] = []
  for field in rule.fields {
    let text = field_text(message, field)
    for keyword in rule.keywords {
      if keyword_matches(keyword, text, rule.ignore_case, rule.regex) {
        let mut already = false
        for seen in matched_keywords {
          if seen == keyword {
            already = true
          }
        }
        if !already {
          matched_keywords.push(keyword)
        }
        let mut field_seen = false
        for seen in matched_fields {
          if seen == field.as_string() {
            field_seen = true
          }
        }
        if !field_seen {
          matched_fields.push(field.as_string())
        }
      }
    }
  }
  let hit = if rule.require_all {
    matched_keywords.length() == rule.keywords.length()
  } else {
    matched_keywords.length() > 0
  }
  if hit {
    Some({ rule, keywords: matched_keywords, fields: matched_fields, })
  } else {
    None
  }
}

///|
/// Every rule that fires for `message`, in configuration order.
pub fn match_rules(
  rules : Array[Rule],
  account : String,
  mailbox : String,
  message : MailMessage,
) -> Array[RuleMatch] {
  let out : Array[RuleMatch] = []
  for rule in rules {
    if !rule_applies(rule, account, mailbox) {
      continue
    }
    match match_rule(rule, message) {
      Some(hit) => out.push(hit)
      None => ()
    }
  }
  out
}