///|
fn match_rule(
  rule : IgnoreRule,
  path_segments : Array[String],
  is_dir : Bool,
  allow_empty_last_star : Bool,
) -> Bool {
  if !rule.has_slash {
    let target = rule.segments[0]
    if rule.anchored {
      if path_segments.is_empty() {
        return false
      }
      if !match_segment_pattern(target, path_segments[0]) {
        return false
      }
      if !rule.directory_only {
        return path_segments.length() == 1
      }
      return path_segments.length() == 1 && is_dir
    }

    for i in 0.. Bool {
  if pi >= pattern_segments.length() {
    return !require_full || si >= path_segments.length()
  }

  if si >= path_segments.length() {
    if allow_empty_last_star &&
      pi == pattern_segments.length() - 1 &&
      pattern_segments[pi] == "*" {
      return true
    }
    return false
  }

  let token = pattern_segments[pi]
  if token == "**" {
    if pi == pattern_segments.length() - 1 {
      // A trailing `/**` should match children, but not the directory itself.
      return si < path_segments.length()
    }
    let mut k = si
    while k <= path_segments.length() {
      if match_segments(
          pattern_segments,
          pi + 1,
          path_segments,
          k,
          require_full,
          allow_empty_last_star,
        ) {
        return true
      }
      k = k + 1
    }
    return false
  }

  if !match_segment_pattern(token, path_segments[si]) {
    return false
  }
  match_segments(
    pattern_segments,
    pi + 1,
    path_segments,
    si + 1,
    require_full,
    allow_empty_last_star,
  )
}

///|
fn match_segment_pattern(pattern : String, input : String) -> Bool {
  match_pattern_chars(pattern, 0, input, 0)
}

///|
fn match_pattern_chars(
  pattern : String,
  pi : Int,
  input : String,
  si : Int,
) -> Bool {
  if pi >= pattern.length() {
    return si >= input.length()
  }

  let pch = pattern.code_unit_at(pi)

  if pch == '\\'.to_int().to_uint16() {
    let next = pi + 1
    if next >= pattern.length() {
      if si >= input.length() {
        return false
      }
      if input.code_unit_at(si) != '\\'.to_int().to_uint16() {
        return false
      }
      return match_pattern_chars(pattern, pi + 1, input, si + 1)
    }

    if si >= input.length() {
      return false
    }
    if input.code_unit_at(si) != pattern.code_unit_at(next) {
      return false
    }
    return match_pattern_chars(pattern, next + 1, input, si + 1)
  }

  if pch == '*'.to_int().to_uint16() {
    let mut next = pi
    while next < pattern.length() &&
          pattern.code_unit_at(next) == '*'.to_int().to_uint16() {
      next = next + 1
    }
    if next >= pattern.length() {
      return true
    }

    let mut k = si
    while k <= input.length() {
      if match_pattern_chars(pattern, next, input, k) {
        return true
      }
      k = k + 1
    }
    return false
  }

  if pch == '?'.to_int().to_uint16() {
    if si >= input.length() {
      return false
    }
    return match_pattern_chars(pattern, pi + 1, input, si + 1)
  }

  if pch == '['.to_int().to_uint16() {
    if si >= input.length() {
      return false
    }

    let mut idx = pi + 1
    if idx >= pattern.length() {
      return false
    }

    let mut negate = false
    if idx < pattern.length() {
      let first = pattern.code_unit_at(idx)
      if first == '!'.to_int().to_uint16() || first == '^'.to_int().to_uint16() {
        negate = true
        idx = idx + 1
      }
    }

    let class_start = idx
    let mut close = -1
    while idx < pattern.length() {
      let ch = pattern.code_unit_at(idx)
      if ch == '\\'.to_int().to_uint16() && idx + 1 < pattern.length() {
        idx = idx + 2
        continue
      }
      if ch == ']'.to_int().to_uint16() {
        close = idx
        break
      }
      idx = idx + 1
    }

    if close == -1 {
      return false
    }
    if close == class_start {
      return false
    }

    let target = input.code_unit_at(si)
    let mut matched = false
    let mut k = class_start
    while k < close {
      let mut c1 = pattern.code_unit_at(k)
      if c1 == '\\'.to_int().to_uint16() && k + 1 < close {
        c1 = pattern.code_unit_at(k + 1)
        k = k + 2
      } else {
        k = k + 1
      }

      if k < close - 1 && pattern.code_unit_at(k) == '-'.to_int().to_uint16() {
        let c2_idx = k + 1
        let mut c2 = pattern.code_unit_at(c2_idx)
        if c2 == '\\'.to_int().to_uint16() && c2_idx + 1 < close {
          c2 = pattern.code_unit_at(c2_idx + 1)
          k = c2_idx + 2
        } else {
          k = c2_idx + 1
        }

        if c1 <= c2 && target >= c1 && target <= c2 {
          matched = true
        }
      } else if target == c1 {
        matched = true
      }
    }

    if negate {
      matched = !matched
    }
    if !matched {
      return false
    }
    return match_pattern_chars(pattern, close + 1, input, si + 1)
  }

  if si >= input.length() {
    return false
  }

  if pch != input.code_unit_at(si) {
    return false
  }

  match_pattern_chars(pattern, pi + 1, input, si + 1)
}