///|
/// Roles available to local continuity users.
pub(all) enum Role {
  Viewer
  Operator
  Approver
  Administrator
}

///|
/// Domain permissions checked by the local authorization store.
pub(all) enum Permission {
  ReadProjects
  ReadAudit
  RunDrills
  ApproveRecovery
  ManageUsers
}

///|
/// Returns whether a role includes one domain permission.
pub fn Role::allows(self : Role, permission : Permission) -> Bool {
  match self {
    Viewer =>
      match permission {
        ReadProjects => true
        ReadAudit => true
        RunDrills => false
        ApproveRecovery => false
        ManageUsers => false
      }
    Operator =>
      match permission {
        ReadProjects => true
        ReadAudit => true
        RunDrills => true
        ApproveRecovery => false
        ManageUsers => false
      }
    Approver =>
      match permission {
        ReadProjects => true
        ReadAudit => true
        RunDrills => true
        ApproveRecovery => true
        ManageUsers => false
      }
    Administrator => true
  }
}

///|
fn permission_label(permission : Permission) -> String {
  match permission {
    ReadProjects => "read_projects"
    ReadAudit => "read_audit"
    RunDrills => "run_drills"
    ApproveRecovery => "approve_recovery"
    ManageUsers => "manage_users"
  }
}

///|
/// A local user identity and its assigned role.
///
/// The core model stores no password or credential material.
pub(all) struct LocalUser {
  id : String
  username : String
  role : Role
  active : Bool
}

///|
/// Creates an active local user.
pub fn LocalUser::new(
  id~ : String,
  username~ : String,
  role~ : Role,
) -> LocalUser raise ModelError {
  if id == "" {
    raise ModelError::EmptyField("user.id")
  }
  if username == "" {
    raise ModelError::EmptyField("user.username")
  }
  { id, username, role, active: true, }
}

///|
fn LocalUser::with_active(self : LocalUser, active : Bool) -> LocalUser {
  { id: self.id, username: self.username, role: self.role, active, }
}

///|
/// An immutable local user registry used by domain authorization checks.
pub(all) struct LocalAuthStore {
  users : Array[LocalUser]
}

///|
/// Creates an empty local authorization store.
pub fn LocalAuthStore::new() -> LocalAuthStore {
  { users: [], }
}

///|
fn user_index(users : Array[LocalUser], user_id : String) -> Int? {
  let mut result : Int? = None
  for i, user in users {
    if result is None && user.id == user_id {
      result = Some(i)
    }
  }
  result
}

///|
fn username_index(users : Array[LocalUser], username : String) -> Int? {
  let mut result : Int? = None
  for i, user in users {
    if result is None && user.username == username {
      result = Some(i)
    }
  }
  result
}

///|
fn copy_users(users : Array[LocalUser]) -> Array[LocalUser] {
  let result : Array[LocalUser] = []
  for user in users {
    result.push(user)
  }
  result
}

///|
fn replace_user(
  users : Array[LocalUser],
  index : Int,
  user : LocalUser,
) -> Array[LocalUser] {
  let result = copy_users(users)
  result[index] = user
  result
}

///|
/// Registers a user and rejects duplicate IDs or usernames.
pub fn LocalAuthStore::register_user(
  self : LocalAuthStore,
  user~ : LocalUser,
) -> LocalAuthStore raise ModelError {
  let valid_user = LocalUser::new(
    id=user.id,
    username=user.username,
    role=user.role,
  )
  if user_index(self.users, valid_user.id) is Some(_) {
    raise ModelError::DuplicateUser(valid_user.id)
  }
  if username_index(self.users, valid_user.username) is Some(_) {
    raise ModelError::DuplicateUser(valid_user.username)
  }
  let users = copy_users(self.users)
  users.push(valid_user)
  { users, }
}

///|
/// Returns a local user when its ID exists.
pub fn LocalAuthStore::get_user(
  self : LocalAuthStore,
  user_id~ : String,
) -> LocalUser? {
  match user_index(self.users, user_id) {
    Some(index) => Some(self.users[index])
    None => None
  }
}

///|
/// Returns all local users in registration order.
pub fn LocalAuthStore::list_users(self : LocalAuthStore) -> Array[LocalUser] {
  copy_users(self.users)
}

///|
/// Deactivates a local user without deleting its audit-relevant identity.
pub fn LocalAuthStore::deactivate_user(
  self : LocalAuthStore,
  user_id~ : String,
) -> LocalAuthStore raise ModelError {
  let index = match user_index(self.users, user_id) {
    Some(index) => index
    None => raise ModelError::UserNotFound(user_id)
  }
  {
    users: replace_user(self.users, index, self.users[index].with_active(false)),
  }
}

///|
/// Reactivates a previously deactivated local user.
pub fn LocalAuthStore::activate_user(
  self : LocalAuthStore,
  user_id~ : String,
) -> LocalAuthStore raise ModelError {
  let index = match user_index(self.users, user_id) {
    Some(index) => index
    None => raise ModelError::UserNotFound(user_id)
  }
  {
    users: replace_user(self.users, index, self.users[index].with_active(true)),
  }
}

///|
/// Returns whether an active user has a permission.
pub fn LocalAuthStore::can(
  self : LocalAuthStore,
  user_id~ : String,
  permission~ : Permission,
) -> Bool {
  match user_index(self.users, user_id) {
    Some(index) => {
      let user = self.users[index]
      user.active && user.role.allows(permission)
    }
    None => false
  }
}

///|
/// Requires an active user to hold a permission.
pub fn LocalAuthStore::authorize(
  self : LocalAuthStore,
  user_id~ : String,
  permission~ : Permission,
) -> Unit raise ModelError {
  let user = match user_index(self.users, user_id) {
    Some(index) => self.users[index]
    None => raise ModelError::UserNotFound(user_id)
  }
  if !user.active {
    raise ModelError::AccessDenied("user \{user.username} is inactive")
  }
  if !user.role.allows(permission) {
    raise ModelError::AccessDenied(
      "user \{user.username} lacks \{permission_label(permission)}",
    )
  }
}