///|
/// The kind of owner accepted by a CODEOWNERS file.
pub(all) enum OwnerKind {
  User
  Team
  Email
}

///|
/// A single CODEOWNERS owner token.
pub(all) struct Owner {
  raw : String
  kind : OwnerKind
}

///|
/// A parsed CODEOWNERS rule.
pub(all) struct Rule {
  pattern : String
  owners : Array[Owner]
  line : Int32
}

///|
/// A recoverable parsing problem. GitHub skips invalid CODEOWNERS lines, so the
/// parser keeps valid rules and reports invalid lines separately.
pub(all) struct ParseIssue {
  line : Int32
  source : String
  message : String
}

///|
/// A parsed CODEOWNERS document.
pub(all) struct Codeowners {
  rules : Array[Rule]
  issues : Array[ParseIssue]
}

///|
pub fn Codeowners::is_valid(self : Self) -> Bool {
  self.issues.is_empty()
}

///|
pub fn Codeowners::rule_count(self : Self) -> Int32 {
  self.rules.length()
}

///|
/// Return the last matching rule. CODEOWNERS deliberately gives the last
/// matching rule precedence.
pub fn Codeowners::find_rule(self : Self, path : String) -> Rule? {
  let normalized = normalize_path(path)
  if normalized.is_empty() {
    return None
  }
  let mut matched = None
  for rule in self.rules {
    if rule.matches(normalized) {
      matched = Some(rule)
    }
  }
  matched
}

///|
/// Return the effective owners for a repository path.
pub fn Codeowners::owners_for(self : Self, path : String) -> Array[Owner] {
  match self.find_rule(path) {
    Some(rule) => clone_owners(rule.owners)
    None => []
  }
}

///|
fn clone_owners(owners : Array[Owner]) -> Array[Owner] {
  let result = []
  for owner in owners {
    result.push(owner)
  }
  result
}