///|
pub struct AccessRecord {
  verb : String
  url : String
  status : Int
  agent : String
} derive(Debug, Eq)

///|
pub fn access_record(
  verb : String,
  url : String,
  status : Int,
  agent : String,
) -> AccessRecord {
  { verb, url, status, agent }
}

///|
pub fn replay_access(
  text : String,
  records : Array[AccessRecord],
) -> Array[String] {
  let robots = parse(text)
  let lines : Array[String] = []
  for record in records {
    let decision = decide_url(robots, record.agent, record.url)
    if !decision.allowed {
      lines.push(
        record.verb + " " + record.url + " violates " + decision.matched_pattern,
      )
    }
  }
  lines
}

///|
pub fn replay_report(text : String, records : Array[AccessRecord]) -> String {
  let lines = replay_access(text, records)
  if lines.is_empty() {
    "ok"
  } else {
    join_lines(lines)
  }
}

///|
pub fn records_for_agent(
  records : Array[AccessRecord],
  agent : String,
) -> Array[AccessRecord] {
  let result : Array[AccessRecord] = []
  let needle = normalize_agent(agent)
  for record in records {
    if normalize_agent(record.agent).find(needle) is Some(_) {
      result.push(record)
    }
  }
  result
}

///|
pub fn count_status(records : Array[AccessRecord], status : Int) -> Int {
  let mut total = 0
  for record in records {
    if record.status == status {
      total += 1
    }
  }
  total
}

///|
pub fn access_summary(records : Array[AccessRecord]) -> String {
  "records=" +
  records.length().to_string() +
  " ok=" +
  count_status(records, 200).to_string() +
  " not_found=" +
  count_status(records, 404).to_string()
}