///|
pub struct OwnerRule {
  pattern : PathGlob
  owners : Array[String]
} derive(Eq, @debug.Debug)

///|
pub fn OwnerRule::new(
  pattern : PathGlob,
  owners : Array[String],
) -> Result[OwnerRule, Diagnostic] {
  if owners.is_empty() {
    return Err(
      Diagnostic::new(
        "policy.owner.empty", "owner.owners", "owner rule has no owners", "at least one @owner",
        "none",
      ),
    )
  }
  let normalized : Array[String] = []
  for index, owner in owners {
    if !is_principal(owner) {
      return Err(
        Diagnostic::new(
          "policy.principal.invalid",
          "owner.owners[" + index.to_string() + "]",
          "owner principal is invalid",
          "@ followed by letters, digits, dot, underscore, slash, or hyphen",
          owner,
        ),
      )
    }
    push_unique_string(normalized, owner)
  }
  Ok({ pattern, owners: normalized, })
}

///|
pub fn OwnerRule::pattern(self : OwnerRule) -> PathGlob {
  self.pattern
}

///|
pub fn OwnerRule::owners(self : OwnerRule) -> Array[String] {
  self.owners.copy()
}

///|
pub struct GovernanceRule {
  name : String
  pattern : PathGlob
  approvals : Int
  checks : Array[String]
  labels : Array[String]
  forbidden : Array[ChangeKind]
  max_lines : Int?
  release_note : Bool
  allow_binary : Bool
} derive(Eq, @debug.Debug)

///|
pub fn GovernanceRule::new(
  name : String,
  pattern : PathGlob,
  approvals? : Int = 0,
  checks? : Array[String] = [],
  labels? : Array[String] = [],
  forbidden? : Array[ChangeKind] = [],
  max_lines? : Int? = None,
  release_note? : Bool = false,
  allow_binary? : Bool = true,
) -> Result[GovernanceRule, Diagnostic] {
  if !is_simple_name(name) {
    return Err(
      Diagnostic::new(
        "policy.rule.name", "rule.name", "rule name is invalid", "1-64 letters, digits, dot, underscore, or hyphen",
        name,
      ),
    )
  }
  if approvals < 0 || approvals > 20 {
    return Err(
      Diagnostic::new(
        "policy.approvals.limit",
        "rule.approvals",
        "approval count is outside the limit",
        "0 through 20",
        approvals.to_string(),
      ),
    )
  }
  match max_lines {
    Some(value) =>
      if value < 0 || value > 10000000 {
        return Err(
          Diagnostic::new(
            "policy.lines.limit",
            "rule.max_lines",
            "line budget is outside the limit",
            "0 through 10000000",
            value.to_string(),
          ),
        )
      }
    None => ()
  }
  let normalized_checks = match normalize_names(checks, "rule.checks") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let normalized_labels = match normalize_names(labels, "rule.labels") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let normalized_forbidden : Array[ChangeKind] = []
  for kind in forbidden {
    if !contains_kind(normalized_forbidden, kind) {
      normalized_forbidden.push(kind)
    }
  }
  Ok({
    name,
    pattern,
    approvals,
    checks: normalized_checks,
    labels: normalized_labels,
    forbidden: normalized_forbidden,
    max_lines,
    release_note,
    allow_binary,
  })
}

///|
pub fn GovernanceRule::name(self : GovernanceRule) -> String {
  self.name
}

///|
pub fn GovernanceRule::pattern(self : GovernanceRule) -> PathGlob {
  self.pattern
}

///|
pub fn GovernanceRule::approvals(self : GovernanceRule) -> Int {
  self.approvals
}

///|
pub fn GovernanceRule::checks(self : GovernanceRule) -> Array[String] {
  self.checks.copy()
}

///|
pub fn GovernanceRule::labels(self : GovernanceRule) -> Array[String] {
  self.labels.copy()
}

///|
pub fn GovernanceRule::forbidden(self : GovernanceRule) -> Array[ChangeKind] {
  self.forbidden.copy()
}

///|
pub fn GovernanceRule::max_lines(self : GovernanceRule) -> Int? {
  self.max_lines
}

///|
pub fn GovernanceRule::requires_release_note(self : GovernanceRule) -> Bool {
  self.release_note
}

///|
pub fn GovernanceRule::allows_binary(self : GovernanceRule) -> Bool {
  self.allow_binary
}

///|
pub struct Policy {
  default_approvals : Int
  max_total_lines : Int?
  require_owned : Bool
  owner_rules : Array[OwnerRule]
  rules : Array[GovernanceRule]
} derive(Eq, @debug.Debug)

///|
pub fn Policy::new(
  default_approvals? : Int = 1,
  max_total_lines? : Int? = None,
  require_owned? : Bool = true,
  owner_rules? : Array[OwnerRule] = [],
  rules? : Array[GovernanceRule] = [],
) -> Result[Policy, Diagnostic] {
  if default_approvals < 0 || default_approvals > 20 {
    return Err(
      Diagnostic::new(
        "policy.approvals.limit",
        "policy.default_approvals",
        "default approval count is outside the limit",
        "0 through 20",
        default_approvals.to_string(),
      ),
    )
  }
  match max_total_lines {
    Some(value) =>
      if value < 0 || value > 100000000 {
        return Err(
          Diagnostic::new(
            "policy.lines.limit",
            "policy.max_total_lines",
            "total line budget is outside the limit",
            "0 through 100000000",
            value.to_string(),
          ),
        )
      }
    None => ()
  }
  let names : Array[String] = []
  for index, rule in rules {
    if contains_string(names, rule.name) {
      return Err(
        Diagnostic::new(
          "policy.rule.duplicate",
          "policy.rules[" + index.to_string() + "].name",
          "rule name appears more than once",
          "unique rule name",
          rule.name,
        ),
      )
    }
    names.push(rule.name)
  }
  Ok({
    default_approvals,
    max_total_lines,
    require_owned,
    owner_rules: owner_rules.copy(),
    rules: rules.copy(),
  })
}

///|
pub fn Policy::default_approvals(self : Policy) -> Int {
  self.default_approvals
}

///|
pub fn Policy::max_total_lines(self : Policy) -> Int? {
  self.max_total_lines
}

///|
pub fn Policy::requires_owned_paths(self : Policy) -> Bool {
  self.require_owned
}

///|
pub fn Policy::owner_rules(self : Policy) -> Array[OwnerRule] {
  self.owner_rules.copy()
}

///|
pub fn Policy::rules(self : Policy) -> Array[GovernanceRule] {
  self.rules.copy()
}

///|
fn contains_kind(values : Array[ChangeKind], expected : ChangeKind) -> Bool {
  for value in values {
    if value == expected {
      return true
    }
  }
  false
}