pasejo 2026.5.24

passage re-implementation in Rust for teams
// SPDX-FileCopyrightText: The pasejo Authors
// SPDX-License-Identifier: 0BSD

use std::path::{Path, absolute};

use clap::error::ErrorKind;

use crate::cli::errors::error_exit;
use crate::cli::i18n;
use crate::models::cli::IdentityCommands;
use crate::models::configuration::{Configuration, Identity};

pub fn dispatch(command: &IdentityCommands, configuration: &Configuration) -> anyhow::Result<()> {
    match command {
        IdentityCommands::Add(args) => {
            let mut owned = configuration.clone();
            add(
                &mut owned,
                args.store_selection.store.as_ref(),
                args.file.as_path(),
                args.global,
            )
        }
        IdentityCommands::Remove(args) => {
            let mut owned = configuration.clone();
            remove(
                &mut owned,
                args.store_selection.store.as_ref(),
                args.file.as_path(),
                args.global,
                args.ignore_unknown,
            )
        }
        IdentityCommands::List(args) => list(
            configuration,
            args.store_selection.store.as_ref(),
            args.global,
        ),
    }
}

fn add(
    configuration: &mut Configuration,
    store_name: Option<&String>,
    identity_file: &Path,
    global: bool,
) -> anyhow::Result<()> {
    let absolute_path = absolute(identity_file)?;
    let identity = Identity {
        file: absolute_path.clone(),
    };
    if configuration.has_identity(&identity, store_name, global) {
        error_exit(
            "identity",
            "add",
            ErrorKind::InvalidValue,
            &format!(
                "invalid value '{}' for '--file <FILE>': file was already added as an identity",
                identity_file.display()
            ),
        )
    } else {
        if global {
            configuration.identities.push(identity);
            configuration.save_configuration()?;
        } else if let Some(store) = configuration.select_store_mut(store_name) {
            store.identities.push(identity);
            configuration.save_configuration()?;
        } else {
            anyhow::bail!(i18n::error_cannot_identify_store());
        }

        i18n::identity_added(absolute_path.as_path());
        Ok(())
    }
}

fn remove(
    configuration: &mut Configuration,
    store_name: Option<&String>,
    identity_file: &Path,
    global: bool,
    ignore_missing: bool,
) -> anyhow::Result<()> {
    let absolute_path = absolute(identity_file)?;
    let identity = Identity {
        file: absolute_path.clone(),
    };
    if configuration.has_identity(&identity, store_name, global) {
        configuration.remove_identity(&identity, store_name, global)?;
        i18n::identity_removed(absolute_path.as_path());
        Ok(())
    } else if ignore_missing {
        Ok(())
    } else {
        error_exit(
            "identity",
            "remove",
            ErrorKind::InvalidValue,
            &format!(
                "invalid value '{}' for '--file <FILE>': file does not match any known identity",
                identity_file.display()
            ),
        )
    }
}

fn list(
    configuration: &Configuration,
    store_name: Option<&String>,
    global: bool,
) -> anyhow::Result<()> {
    if global {
        for identity in &configuration.identities {
            i18n::list_global_identity(&identity.file);
        }
        Ok(())
    } else if let Some(registration) = configuration.select_store(store_name) {
        for identity in &configuration.identities {
            i18n::list_global_identity(&identity.file);
        }
        for identity in &registration.identities {
            i18n::list_store_identity(&identity.file);
        }

        Ok(())
    } else {
        anyhow::bail!(i18n::error_no_store_or_global())
    }
}