///|
/// Match selection policy used by applications that need deterministic output.
pub(all) struct MatchPolicy {
  min_weight : Int
  max_matches : Int
  require_label : String
  prefer_longest : Bool
} derive(Debug, Eq, ToJson)

///|
pub fn MatchPolicy::default() -> MatchPolicy {
  {
    min_weight: -1000000,
    max_matches: 1000000,
    require_label: "",
    prefer_longest: true,
  }
}

///|
pub fn MatchPolicy::accepts(self : MatchPolicy, hit : LexiconMatch) -> Bool {
  hit.weight >= self.min_weight &&
  (self.require_label == "" || hit.label == self.require_label)
}

///|
/// Filter, order, and cap matches according to a policy.
pub fn apply_policy(
  matches : ArrayView[LexiconMatch],
  policy : MatchPolicy,
) -> Array[LexiconMatch] {
  let out : Array[LexiconMatch] = []
  for hit in matches {
    if policy.accepts(hit) {
      out.push(hit)
    }
  }
  out.sort_by(fn(a, b) {
    if policy.prefer_longest {
      let by_length = b.end - b.start - (a.end - a.start)
      if by_length != 0 {
        by_length
      } else {
        a.start - b.start
      }
    } else if a.start == b.start {
      b.weight - a.weight
    } else {
      a.start - b.start
    }
  })
  if out.length() > policy.max_matches {
    let bounded : Array[LexiconMatch] = []
    for i in 0.. Array[LexiconMatch] {
  let best : Map[Int, LexiconMatch] = Map([])
  for hit in matches {
    match best.get(hit.start) {
      None => best[hit.start] = hit
      Some(old) =>
        if hit.weight > old.weight || hit.end > old.end {
          best[hit.start] = hit
        }
    }
  }
  let out : Array[LexiconMatch] = []
  for _, hit in best {
    out.push(hit)
  }
  out.sort_by((a, b) => a.start - b.start)
  out
}