///|
/// Whole-string glob matching.
///
/// `*` matches any run of characters (including path separators), `?` matches
/// exactly one character, everything else is literal. There is no escape
/// syntax and no path normalization: `reports/*.md` is compared against the
/// argument exactly as the agent sent it. Backtracking is linear in the text
/// because only the most recent `*` is ever revisited.
pub fn glob_match(pattern : String, text : String) -> Bool {
  let plen = pattern.length()
  let tlen = text.length()
  let mut p = 0
  let mut t = 0
  let mut star = -1
  let mut mark = 0
  while t < tlen {
    if p < plen && (pattern[p] == '?' || pattern[p] == text[t]) {
      p += 1
      t += 1
    } else if p < plen && pattern[p] == '*' {
      star = p
      p += 1
      mark = t
    } else if star >= 0 {
      p = star + 1
      mark += 1
      t = mark
    } else {
      return false
    }
  }
  while p < plen && pattern[p] == '*' {
    p += 1
  }
  p == plen
}

///|
/// True when at least one pattern matches. An empty pattern list matches
/// nothing, which keeps "no rule configured" fail-closed.
pub fn any_glob(patterns : Array[String], text : String) -> Bool {
  for pattern in patterns {
    if glob_match(pattern, text) {
      return true
    }
  }
  false
}