///|
pub fn lint(text : String) -> Array[String] {
  let warnings : Array[String] = []
  let mut line_no = 0
  let mut current_agents : Array[String] = []
  let mut current_rules : Array[String] = []
  let mut saw_rule = false
  for raw in split_lines(text) {
    line_no += 1
    let line = trim_ascii(strip_inline_comment(raw))
    if line == "" {
      current_agents = []
      current_rules = []
      saw_rule = false
      continue
    }
    let parts = split_once(line, ":")
    if parts.length() < 2 {
      warnings.push(
        "line " + line_no.to_string() + ": ignored malformed directive",
      )
      continue
    }
    let key = lower_ascii(trim_ascii(parts[0]))
    let value = trim_ascii(parts[1])
    if key == "user-agent" {
      let agent = lower_ascii(value)
      if value == "" {
        warnings.push("line " + line_no.to_string() + ": empty user-agent")
      }
      if saw_rule {
        warnings.push(
          "line " +
          line_no.to_string() +
          ": user-agent starts a new implicit group",
        )
        current_agents = []
        current_rules = []
        saw_rule = false
      }
      if contains_string(current_agents, agent) {
        warnings.push(
          "line " + line_no.to_string() + ": duplicate user-agent " + value,
        )
      }
      current_agents.push(agent)
    } else if key == "allow" || key == "disallow" {
      saw_rule = true
      let normalized = if value == "" && key == "disallow" {
        ""
      } else {
        normalize_path(value)
      }
      let signature = key + ":" + normalized
      if contains_string(current_rules, signature) {
        warnings.push(
          "line " + line_no.to_string() + ": duplicate rule " + signature,
        )
      }
      current_rules.push(signature)
      if current_agents.is_empty() {
        warnings.push(
          "line " + line_no.to_string() + ": rule without user-agent",
        )
      }
    } else if key == "crawl-delay" {
      saw_rule = true
      if parse_non_negative_int(value) is None {
        warnings.push("line " + line_no.to_string() + ": invalid crawl-delay")
      }
    } else if key == "sitemap" || key == "host" {
      ()
    } else {
      warnings.push(
        "line " + line_no.to_string() + ": unknown directive " + key,
      )
    }
  }
  warnings
}

///|
pub fn lint_report(text : String) -> String {
  let warnings = lint(text)
  if warnings.is_empty() {
    "ok"
  } else {
    join_lines(warnings)
  }
}

///|
pub fn is_clean(text : String) -> Bool {
  lint(text).is_empty() && parse_errors(text).is_empty()
}

///|
pub fn health_report(text : String) -> String {
  let parse = validation_report(text)
  let style = lint_report(text)
  if parse == "ok" && style == "ok" {
    "ok"
  } else {
    "parse:\n" + parse + "\nlint:\n" + style
  }
}