///|
pub(all) struct NamedPolicy {
  name : String
  chain : PolicyChain
  description : String
} derive(Eq, Debug)

///|
pub(all) struct PolicyRegistry {
  policies : Array[NamedPolicy]
  default_name : String?
} derive(Eq, Debug)

///|
pub(all) enum RegistryError {
  EmptyPolicyName
  DuplicatePolicyName(String)
  PolicyNotFound(String)
  DefaultPolicyNotFound(String)
} derive(Eq, Debug)

///|
pub fn new_policy_registry() -> PolicyRegistry {
  { policies: [], default_name: None }
}

///|
pub fn registry_add(
  registry : PolicyRegistry,
  name : String,
  chain : PolicyChain,
  description? : String = "",
) -> Result[PolicyRegistry, RegistryError] {
  if name.length() == 0 {
    return Err(EmptyPolicyName)
  }
  if registry_contains(registry, name) {
    return Err(DuplicatePolicyName(name))
  }
  let policies = registry.policies.copy()
  policies.push({ name, chain, description })
  Ok({ ..registry, policies, })
}

///|
pub fn registry_replace(
  registry : PolicyRegistry,
  name : String,
  chain : PolicyChain,
  description? : String = "",
) -> Result[PolicyRegistry, RegistryError] {
  if !registry_contains(registry, name) {
    return Err(PolicyNotFound(name))
  }
  let policies : Array[NamedPolicy] = []
  for policy in registry.policies {
    if policy.name == name {
      policies.push({ name, chain, description })
    } else {
      policies.push(policy)
    }
  }
  Ok({ ..registry, policies, })
}

///|
pub fn registry_remove(
  registry : PolicyRegistry,
  name : String,
) -> Result[PolicyRegistry, RegistryError] {
  if !registry_contains(registry, name) {
    return Err(PolicyNotFound(name))
  }
  let policies : Array[NamedPolicy] = []
  for policy in registry.policies {
    if policy.name != name {
      policies.push(policy)
    }
  }
  let default_name = match registry.default_name {
    Some(current) => if current == name { None } else { Some(current) }
    None => None
  }
  Ok({ policies, default_name })
}

///|
pub fn registry_set_default(
  registry : PolicyRegistry,
  name : String,
) -> Result[PolicyRegistry, RegistryError] {
  if registry_contains(registry, name) {
    Ok({ ..registry, default_name: Some(name) })
  } else {
    Err(DefaultPolicyNotFound(name))
  }
}

///|
pub fn registry_find(
  registry : PolicyRegistry,
  name : String,
) -> Result[PolicyChain, RegistryError] {
  for policy in registry.policies {
    if policy.name == name {
      return Ok(policy.chain)
    }
  }
  Err(PolicyNotFound(name))
}

///|
pub fn registry_default(
  registry : PolicyRegistry,
) -> Result[PolicyChain, RegistryError] {
  match registry.default_name {
    Some(name) => registry_find(registry, name)
    None => Err(DefaultPolicyNotFound(""))
  }
}

///|
pub fn registry_contains(registry : PolicyRegistry, name : String) -> Bool {
  for policy in registry.policies {
    if policy.name == name {
      return true
    }
  }
  false
}

///|
pub fn registry_names(registry : PolicyRegistry) -> Array[String] {
  let names : Array[String] = []
  for policy in registry.policies {
    names.push(policy.name)
  }
  names
}

///|
pub fn format_registry_error(error : RegistryError) -> String {
  match error {
    EmptyPolicyName => "policy name must not be empty"
    DuplicatePolicyName(name) => "policy already exists: " + name
    PolicyNotFound(name) => "policy not found: " + name
    DefaultPolicyNotFound(name) => "default policy not found: " + name
  }
}