//! Relicario CLI — the platform layer for the Relicario password manager.
//!
//! See module docs for the unlock flow and vault layout.

mod commands;
mod device;
mod gitea;
mod helpers;
mod parse;
mod prompt;
mod session;
mod org_session;

use std::path::PathBuf;

use anyhow::Result;
use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::{generate, Shell};

#[derive(Parser)]
#[command(
    name = "relicario",
    version,
    about = "Relicario — git-backed password manager with reference-image two-factor unlock"
)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Initialize a new vault in the current directory.
    Init {
        /// Carrier JPEG to embed the secret into.
        #[arg(long)]
        image: PathBuf,
        /// Output path for the reference image (gitignored).
        #[arg(long, default_value = "reference.jpg")]
        output: PathBuf,
    },

    /// Add a new item. Type-specific flags populate the core; missing fields
    /// are prompted for interactively.
    Add {
        #[command(subcommand)]
        kind: AddKind,
    },

    /// Print an item. Secrets are masked by default; pass --show to reveal.
    Get {
        /// Item id or case-insensitive title substring.
        query: String,
        /// Print secret field values in plaintext.
        #[arg(long)]
        show: bool,
        /// Copy the primary secret (Login.password, Card.number, etc.) to clipboard.
        #[arg(long)]
        copy: bool,
    },

    /// List items.
    List {
        #[arg(long)]
        r#type: Option<String>,
        #[arg(long)]
        group: Option<String>,
        #[arg(long)]
        tag: Option<String>,
        #[arg(long)]
        trashed: bool,
    },

    /// Edit an item interactively.
    Edit {
        query: String,
        /// Decode an `otpauth://` QR image to set the TOTP secret (login items only).
        #[arg(long, value_name = "PATH")]
        totp_qr: Option<PathBuf>,
    },

    /// View captured field history for an item. Values are masked by
    /// default; pass `--show` to reveal them.
    History {
        query: String,
        #[arg(long)]
        show: bool,
        /// Filter to a single field (matches against the synthetic key,
        /// e.g. `login_password`, `card_number`, `totp_secret`).
        #[arg(long)]
        field: Option<String>,
    },

    /// Soft-delete an item (moves to trash; reversible via `restore`).
    Rm { query: String },

    /// Restore a soft-deleted item.
    Restore { query: String },

    /// Permanently purge an item (and its attachments).
    Purge { query: String },

    /// Trash operations.
    Trash {
        #[command(subcommand)]
        action: TrashAction,
    },

    /// Backup operations: pack and unpack `.relbak` archives.
    Backup {
        #[command(subcommand)]
        action: BackupAction,
    },

    /// Import items from another password manager into the unlocked vault.
    Import {
        #[command(subcommand)]
        action: ImportAction,
    },

    /// Attach a file to an item.
    Attach { query: String, file: PathBuf },

    /// List attachments on an item.
    Attachments { query: String },

    /// Extract an attachment to disk.
    Extract {
        query: String,
        aid: String,
        #[arg(long)]
        out: Option<PathBuf>,
    },

    /// Remove an individual attachment from an item (deletes the encrypted
    /// blob and updates the item + manifest). Use `purge` to drop the entire
    /// item and all its attachments at once.
    Detach { query: String, aid: String },

    /// Generate a password or passphrase. When run inside an initialized
    /// vault, falls back to `settings generator-defaults` for unspecified
    /// flags; outside a vault, uses built-in defaults (length 20, safe
    /// symbol set, 5 BIP39 words, space separator).
    #[command(alias = "gen")]
    Generate {
        #[arg(short = 'l', long)]
        length: Option<u32>,
        #[arg(long)]
        bip39: bool,
        #[arg(short = 'w', long)]
        words: Option<u32>,
        #[arg(long)]
        symbols: Option<String>,
        /// Separator for BIP39 words.
        #[arg(long)]
        separator: Option<String>,
    },

    /// View or change vault settings.
    Settings {
        #[command(subcommand)]
        action: SettingsAction,
    },

    /// Sync with the git remote (pull --rebase + push).
    Sync,

    /// Print a summary of the vault: items, attachments, last commit.
    Status,

    /// Lock the vault (no-op in CLI; present for UX parity with the extension).
    Lock,

    /// Emit a shell completion script for the given shell.
    ///
    /// For `--group <TAB>` autocomplete, the bash/zsh/fish scripts read
    /// the plaintext `${RELICARIO_VAULT}/.relicario/groups.cache` file,
    /// which the CLI refreshes on every manifest read. In debug builds, set
    /// `RELICARIO_NO_GROUPS_CACHE=1` to opt out of the cache (completion
    /// will fall back to no value enumeration).
    ///
    /// Pipe stdout to your shell's completion location (e.g.
    /// `relicario completions bash > /etc/bash_completion.d/relicario`).
    Completions {
        #[arg(value_enum)]
        shell: Shell,
    },

    /// Rate a passphrase with zxcvbn — prints score (0-4) and estimated
    /// guesses. Informational only; does not gate vault operations.
    ///
    /// Pass `-` as the argument to read one line from stdin instead, which
    /// keeps the passphrase out of shell history.
    Rate {
        /// Passphrase to score, or `-` to read from stdin.
        passphrase: String,
    },

    /// Manage registered devices (signing keys + deploy keys).
    Device {
        #[command(subcommand)]
        action: DeviceAction,
    },

    /// Recovery QR operations — generate or unwrap the 2FA recovery code.
    RecoveryQr {
        #[command(subcommand)]
        cmd: RecoveryQrCmd,
    },

    /// Manage a multi-user org vault.
    Org {
        /// Path to the org vault directory (overrides RELICARIO_ORG_DIR).
        #[arg(long, global = true)]
        dir: Option<PathBuf>,
        #[command(subcommand)]
        subcommand: OrgCommands,
    },
}

#[derive(Subcommand)]
pub(crate) enum AddKind {
    Login {
        #[arg(long)] title: Option<String>,
        #[arg(long)] username: Option<String>,
        #[arg(long)] url: Option<String>,
        /// Prompt for password (vs reading from stdin or --password).
        #[arg(long)] password_prompt: bool,
        #[arg(long)] password: Option<String>,
        /// Read the password from stdin (one line) instead of prompting.
        #[arg(long)] password_stdin: bool,
        #[arg(long)] group: Option<String>,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
        #[arg(long)] favorite: bool,
        /// Decode an `otpauth://` QR image to fill the TOTP secret.
        #[arg(long, value_name = "PATH")] totp_qr: Option<PathBuf>,
    },
    SecureNote {
        #[arg(long)] title: Option<String>,
        /// Read the note body from stdin (to EOF) instead of printing the Ctrl-D hint.
        #[arg(long)] body_stdin: bool,
        #[arg(long)] group: Option<String>,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
    },
    Identity {
        #[arg(long)] title: Option<String>,
        #[arg(long)] full_name: Option<String>,
        #[arg(long)] email: Option<String>,
        #[arg(long)] phone: Option<String>,
        #[arg(long)] date_of_birth: Option<String>,
        #[arg(long)] group: Option<String>,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
    },
    Card {
        #[arg(long)] title: Option<String>,
        #[arg(long)] holder: Option<String>,
        #[arg(long)] expiry: Option<String>, // MM/YYYY
        #[arg(long, default_value = "credit")] kind: String,
        /// Read the card number from stdin (one line) instead of prompting.
        #[arg(long)] number_stdin: bool,
        /// Read the CVV from stdin (one line) instead of prompting.
        #[arg(long)] cvv_stdin: bool,
        /// Read the PIN from stdin (one line) instead of prompting.
        #[arg(long)] pin_stdin: bool,
        #[arg(long)] group: Option<String>,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
    },
    Key {
        #[arg(long)] title: Option<String>,
        #[arg(long)] label: Option<String>,
        #[arg(long)] algorithm: Option<String>,
        /// Read the key material from stdin (to EOF) instead of printing the Ctrl-D hint.
        #[arg(long)] material_stdin: bool,
        #[arg(long)] group: Option<String>,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
    },
    Document {
        #[arg(long)] title: Option<String>,
        #[arg(long)] file: PathBuf,
        #[arg(long)] group: Option<String>,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
    },
    Totp {
        #[arg(long)] title: Option<String>,
        #[arg(long)] issuer: Option<String>,
        #[arg(long)] label: Option<String>,
        #[arg(long)] secret: Option<String>, // base32
        /// Read the TOTP secret from stdin (one line) instead of prompting.
        #[arg(long)] secret_stdin: bool,
        #[arg(long, default_value = "30")] period: u32,
        #[arg(long, default_value = "6")] digits: u8,
        #[arg(long, default_value = "sha1")] algorithm: String,
        #[arg(long)] group: Option<String>,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
    },
}

#[derive(Subcommand)]
pub(crate) enum TrashAction {
    /// List trashed items.
    List,
    /// Purge every trashed item past its retention window.
    Empty,
}

#[derive(Subcommand)]
pub(crate) enum SettingsAction {
    /// Show current settings as JSON.
    Show,
    /// Set trash retention (e.g., --days 30 or --forever).
    TrashRetention {
        #[arg(long)] days: Option<u32>,
        #[arg(long)] forever: bool,
    },
    /// Set field history retention.
    HistoryRetention {
        #[arg(long)] last_n: Option<u32>,
        #[arg(long)] days: Option<u32>,
        #[arg(long)] forever: bool,
    },
    /// Set per-attachment max size in bytes.
    AttachmentCap {
        #[arg(long)] per_attachment_max_bytes: Option<u64>,
        #[arg(long)] per_item_max_count: Option<u32>,
        #[arg(long)] per_vault_soft_cap_bytes: Option<u64>,
        #[arg(long)] per_vault_hard_cap_bytes: Option<u64>,
    },
    /// Update the default password / passphrase generator settings used by
    /// `relicario generate` when run inside this vault. Pass `--bip39` or
    /// `--random` to switch mode; per-attribute flags update fields of the
    /// chosen mode.
    GeneratorDefaults {
        /// Switch the default mode to random-character password.
        #[arg(long, conflicts_with = "bip39")]
        random: bool,
        /// Switch the default mode to BIP39 passphrase.
        #[arg(long, conflicts_with = "random")]
        bip39: bool,
        /// Random mode: total password length.
        #[arg(long)] length: Option<u32>,
        /// BIP39 mode: number of words.
        #[arg(long)] words: Option<u32>,
        /// Random mode: symbol charset (`safe`, `extended`, or a custom literal).
        #[arg(long)] symbols: Option<String>,
        /// BIP39 mode: word separator.
        #[arg(long)] separator: Option<String>,
    },
}

#[derive(Subcommand)]
pub(crate) enum BackupAction {
    /// Pack the local vault into a single encrypted `.relbak` file.
    /// Backup passphrase is independent of the vault passphrase.
    Export {
        /// Output `.relbak` path.
        out: PathBuf,
        /// Bundle the reference JPEG into the encrypted envelope.
        #[arg(long)]
        include_image: bool,
        /// Override the reference image path (defaults to the vault's
        /// `reference.jpg` or `RELICARIO_IMAGE`).
        #[arg(long)]
        image: Option<PathBuf>,
        /// Skip bundling `.git/` history.
        #[arg(long)]
        no_history: bool,
    },
    /// Unpack a `.relbak` file into a fresh vault directory.
    Restore {
        /// Input `.relbak` path.
        input: PathBuf,
        /// Target directory (must NOT already contain `.relicario/`).
        /// Defaults to the current directory.
        #[arg(default_value = ".")]
        target: PathBuf,
    },
}

#[derive(Subcommand)]
pub(crate) enum ImportAction {
    /// Import a LastPass CSV export into the unlocked vault.
    /// Each row creates a new item with a freshly-minted ID; title
    /// collisions are kept (no dedup). Failed rows are skipped and
    /// reported on stderr.
    Lastpass {
        /// Path to the LastPass-format CSV export.
        csv: PathBuf,
    },
}

#[derive(Subcommand)]
pub(crate) enum DeviceAction {
    /// Register this machine as a new device.
    ///
    /// Generates two ed25519 keypairs: one for signing commits, one for push
    /// access (deploy key). The deploy public key is registered via the Gitea
    /// API. Both private keys are stored locally in
    /// `~/.config/relicario/devices/<name>/`. The vault's `.relicario/devices.json`
    /// is updated and committed.
    ///
    /// Required environment variables (or flags):
    ///   RELICARIO_GITEA_URL    — e.g. https://git.example.com
    ///   RELICARIO_GITEA_TOKEN  — personal access token with repo write access
    ///   RELICARIO_GITEA_OWNER  — repository owner
    ///   RELICARIO_GITEA_REPO   — repository name
    Add {
        /// Human-readable name for this device (e.g. "laptop-2026").
        #[arg(long)]
        name: String,
        /// Gitea API base URL (overrides RELICARIO_GITEA_URL).
        #[arg(long)]
        gitea_url: Option<String>,
        /// Gitea personal access token (overrides RELICARIO_GITEA_TOKEN).
        #[arg(long)]
        gitea_token: Option<String>,
        /// Gitea repository owner (overrides RELICARIO_GITEA_OWNER).
        #[arg(long)]
        owner: Option<String>,
        /// Gitea repository name (overrides RELICARIO_GITEA_REPO).
        #[arg(long)]
        repo: Option<String>,
        /// Skip Gitea API registration (useful when the remote is not Gitea).
        #[arg(long)]
        no_gitea: bool,
    },
    /// Revoke a registered device.
    ///
    /// Removes the device from `devices.json`, adds it to `revoked.json`,
    /// deletes the deploy key from Gitea, and commits the change.
    Revoke {
        /// Name of the device to revoke.
        #[arg(long)]
        name: String,
    },
    /// List registered devices.
    List,
}

#[derive(clap::Subcommand)]
pub(crate) enum RecoveryQrCmd {
    /// Generate a recovery QR code and display it as ASCII art in the terminal.
    Generate,
    /// Unwrap a recovery QR payload (base64) to recover the image_secret as hex.
    Unwrap,
}

#[derive(clap::Subcommand)]
pub(crate) enum OrgCommands {
    /// Create a new org vault.
    Init {
        #[arg(long)]
        name: String,
    },
    /// Add a member to the org.
    AddMember {
        /// OpenSSH ed25519 public key of the new member.
        #[arg(long)]
        key: String,
        /// Display name.
        #[arg(long)]
        name: String,
        /// Role: owner, admin, or member.
        #[arg(long, default_value = "member")]
        role: String,
    },
    /// Remove a member from the org.
    RemoveMember {
        /// Member ID prefix.
        member_id: String,
    },
    /// Change a member's role.
    SetRole {
        member_id: String,
        role: String,
    },
    /// Create a collection.
    CreateCollection {
        slug: String,
        #[arg(long)]
        name: String,
    },
    /// Grant a member access to a collection.
    Grant {
        member_id: String,
        collection: String,
    },
    /// Revoke a member's access to a collection.
    Revoke {
        member_id: String,
        collection: String,
    },
    /// Rotate the org master key (run after removing a member).
    RotateKey,
    /// Transfer ownership to another member (owner only). By default the caller
    /// is demoted to admin; pass --keep-owner for explicit co-ownership.
    TransferOwnership {
        member_id: String,
        /// Keep the caller as an owner too (co-ownership) instead of demoting.
        #[arg(long)]
        keep_owner: bool,
    },
    /// Delete the org (owner only; requires --confirm).
    DeleteOrg {
        #[arg(long)]
        confirm: bool,
    },
    /// Show org members and collections.
    Status,
    /// Query the org audit log.
    Audit {
        #[arg(long)]
        since: Option<String>,
        #[arg(long)]
        member: Option<String>,
        #[arg(long)]
        collection: Option<String>,
        #[arg(long)]
        action: Option<String>,
        /// Output format: `table` (default) or `json`.
        #[arg(long, default_value = "table")]
        format: String,
    },
    /// Add an item to a collection in the org vault.
    Add {
        #[command(subcommand)]
        kind: OrgAddKind,
    },
    /// Print an org item (secrets masked unless --show).
    Get {
        /// Item id or case-insensitive title substring.
        query: String,
        #[arg(long)] show: bool,
    },
    /// List org items visible to you (filtered by your collection grants).
    List {
        #[arg(long)] trashed: bool,
    },
    /// Edit an org item interactively (per-type prompts; blank keeps current).
    Edit {
        /// Item id or case-insensitive title substring.
        query: String,
        /// Replace the login TOTP secret from a QR image.
        #[arg(long)] totp_qr: Option<std::path::PathBuf>,
        /// Replace a Document item's attachment file.
        #[arg(long)] file: Option<std::path::PathBuf>,
    },
    /// Soft-delete an org item (reversible via `org restore`).
    Rm { query: String },
    /// Restore a soft-deleted org item.
    Restore { query: String },
    /// Permanently purge an org item (deletes the encrypted blob).
    Purge { query: String },
}

#[derive(clap::Subcommand)]
pub(crate) enum OrgAddKind {
    /// A login (username / url / password).
    Login {
        #[arg(long)] collection: String,
        #[arg(long)] title: String,
        #[arg(long)] username: Option<String>,
        #[arg(long)] url: Option<String>,
        #[arg(long)] password: Option<String>,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
        #[arg(long)] password_stdin: bool,
    },
    /// A secure note.
    SecureNote {
        #[arg(long)] collection: String,
        #[arg(long)] title: String,
        #[arg(long)] body: Option<String>,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
        #[arg(long)] body_stdin: bool,
    },
    /// An identity record.
    Identity {
        #[arg(long)] collection: String,
        #[arg(long)] title: String,
        #[arg(long)] full_name: Option<String>,
        #[arg(long)] email: Option<String>,
        #[arg(long)] phone: Option<String>,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
    },
    /// A payment card (number / cvv / pin entered via --*-stdin or prompt).
    Card {
        #[arg(long)] collection: String,
        #[arg(long)] title: String,
        #[arg(long)] holder: Option<String>,
        #[arg(long)] expiry: Option<String>,
        #[arg(long, default_value = "credit")] kind: String,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
        #[arg(long)] number_stdin: bool,
        #[arg(long)] cvv_stdin: bool,
        #[arg(long)] pin_stdin: bool,
    },
    /// A key / credential blob (material entered via --material-stdin or prompt).
    Key {
        #[arg(long)] collection: String,
        #[arg(long)] title: String,
        #[arg(long)] label: Option<String>,
        #[arg(long)] algorithm: Option<String>,
        #[arg(long)] public_key: Option<String>,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
        #[arg(long)] material_stdin: bool,
    },
    /// A TOTP authenticator (base32 secret via --secret or --secret-stdin).
    Totp {
        #[arg(long)] collection: String,
        #[arg(long)] title: String,
        #[arg(long)] issuer: Option<String>,
        #[arg(long)] label: Option<String>,
        #[arg(long)] secret: Option<String>,
        #[arg(long, default_value_t = 30)] period: u32,
        #[arg(long, default_value_t = 6)] digits: u8,
        #[arg(long, default_value = "sha1")] algorithm: String,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
        #[arg(long)] secret_stdin: bool,
    },
    /// A document (file payload encrypted into a collection-scoped attachment).
    Document {
        #[arg(long)] collection: String,
        #[arg(long)] title: String,
        #[arg(long)] file: std::path::PathBuf,
        #[arg(long, value_delimiter = ',')] tags: Vec<String>,
    },
}

fn main() -> Result<()> {
    let cli = Cli::parse();
    match cli.command {
        Commands::Init { image, output } => commands::init::cmd_init(image, output),
        Commands::Add { kind } => commands::add::cmd_add(kind),
        Commands::Get { query, show, copy } => commands::get::cmd_get(query, show, copy),
        Commands::List { r#type, group, tag, trashed } => commands::list::cmd_list(r#type, group, tag, trashed),
        Commands::Edit { query, totp_qr } => commands::edit::cmd_edit(query, totp_qr),
        Commands::History { query, show, field } => commands::list::cmd_history(query, show, field),
        Commands::Rm { query } => commands::trash::cmd_rm(query),
        Commands::Restore { query } => commands::trash::cmd_restore(query),
        Commands::Purge { query } => commands::trash::cmd_purge(query),
        Commands::Trash { action } => commands::trash::cmd_trash(action),
        Commands::Backup { action } => commands::backup::cmd_backup(action),
        Commands::Import { action } => commands::import::cmd_import(action),
        Commands::Attach { query, file } => commands::attach::cmd_attach(query, file),
        Commands::Attachments { query } => commands::attach::cmd_attachments(query),
        Commands::Extract { query, aid, out } => commands::attach::cmd_extract(query, aid, out),
        Commands::Detach { query, aid } => commands::attach::cmd_detach(query, aid),
        Commands::Generate { length, bip39, words, symbols, separator } => {
            commands::generate::cmd_generate(length, bip39, words, symbols, separator)
        }
        Commands::Settings { action } => commands::settings::cmd_settings(action),
        Commands::Sync => commands::sync::cmd_sync(),
        Commands::Status => commands::status::cmd_status(),
        Commands::Lock => { eprintln!("no cached session to lock"); Ok(()) }
        Commands::Completions { shell } => {
            let mut cmd = Cli::command();
            generate(shell, &mut cmd, "relicario", &mut std::io::stdout());
            Ok(())
        }
        Commands::Rate { passphrase } => commands::rate::cmd_rate(passphrase),
        Commands::Device { action } => commands::device::cmd_device(action),
        Commands::RecoveryQr { cmd } => commands::recovery_qr::cmd_recovery_qr(cmd),
        Commands::Org { dir, subcommand } => {
            let dir_path = dir.as_deref();
            match subcommand {
                OrgCommands::Init { name } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_init(&d, &name)?;
                }
                OrgCommands::AddMember { key, name, role } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    let role = parse_org_role(&role)?;
                    commands::org::run_add_member(&d, &key, &name, role)?;
                }
                OrgCommands::RemoveMember { member_id } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_remove_member(&d, &member_id)?;
                }
                OrgCommands::SetRole { member_id, role } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    let role = parse_org_role(&role)?;
                    commands::org::run_set_role(&d, &member_id, role)?;
                }
                OrgCommands::CreateCollection { slug, name } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_create_collection(&d, &slug, &name)?;
                }
                OrgCommands::Grant { member_id, collection } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_grant(&d, &member_id, &collection)?;
                }
                OrgCommands::Revoke { member_id, collection } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_revoke(&d, &member_id, &collection)?;
                }
                OrgCommands::RotateKey => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_rotate_key(&d)?;
                }
                OrgCommands::TransferOwnership { member_id, keep_owner } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_transfer_ownership(&d, &member_id, keep_owner)?;
                }
                OrgCommands::DeleteOrg { confirm } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_delete_org(&d, confirm)?;
                }
                OrgCommands::Status => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_status(&d)?;
                }
                OrgCommands::Audit { since, member, collection, action, format } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_audit(&d, since.as_deref(), member.as_deref(),
                        collection.as_deref(), action.as_deref(), &format)?;
                }
                OrgCommands::Add { kind } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    let (collection, add_kind, tags) = match kind {
                        OrgAddKind::Login { collection, title, username, url, password, tags, password_stdin } => (
                            collection,
                            commands::org::OrgAddKind::Login { title, username, url, password, password_stdin },
                            tags,
                        ),
                        OrgAddKind::SecureNote { collection, title, body, tags, body_stdin } => (
                            collection,
                            commands::org::OrgAddKind::SecureNote { title, body, body_stdin },
                            tags,
                        ),
                        OrgAddKind::Identity { collection, title, full_name, email, phone, tags } => (
                            collection,
                            commands::org::OrgAddKind::Identity { title, full_name, email, phone },
                            tags,
                        ),
                        OrgAddKind::Card { collection, title, holder, expiry, kind, tags, number_stdin, cvv_stdin, pin_stdin } => (
                            collection,
                            commands::org::OrgAddKind::Card { title, holder, expiry, kind, number_stdin, cvv_stdin, pin_stdin },
                            tags,
                        ),
                        OrgAddKind::Key { collection, title, label, algorithm, public_key, tags, material_stdin } => (
                            collection,
                            commands::org::OrgAddKind::Key { title, label, algorithm, public_key, material_stdin },
                            tags,
                        ),
                        OrgAddKind::Totp { collection, title, issuer, label, secret, period, digits, algorithm, tags, secret_stdin } => (
                            collection,
                            commands::org::OrgAddKind::Totp { title, issuer, label, secret, secret_stdin, period, digits, algorithm },
                            tags,
                        ),
                        OrgAddKind::Document { collection, title, file, tags } => (
                            collection,
                            commands::org::OrgAddKind::Document { title, file },
                            tags,
                        ),
                    };
                    commands::org::run_add(&d, &collection, add_kind, tags)?;
                }
                OrgCommands::Get { query, show } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_get(&d, &query, show)?;
                }
                OrgCommands::List { trashed } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_list(&d, trashed)?;
                }
                OrgCommands::Edit { query, totp_qr, file } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_edit(&d, &query, totp_qr, file)?;
                }
                OrgCommands::Rm { query } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_rm(&d, &query)?;
                }
                OrgCommands::Restore { query } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_restore(&d, &query)?;
                }
                OrgCommands::Purge { query } => {
                    let d = crate::org_session::org_dir(dir_path)?;
                    commands::org::run_purge(&d, &query)?;
                }
            }
            Ok(())
        }
    }
}

fn parse_org_role(s: &str) -> anyhow::Result<relicario_core::OrgRole> {
    match s {
        "owner" => Ok(relicario_core::OrgRole::Owner),
        "admin" => Ok(relicario_core::OrgRole::Admin),
        "member" => Ok(relicario_core::OrgRole::Member),
        other => anyhow::bail!("unknown role `{other}` — use owner, admin, or member"),
    }
}

/// Check for test passphrase override (debug builds only; stripped from release).
#[cfg(debug_assertions)]
pub(crate) fn test_passphrase_override() -> Option<String> {
    std::env::var("RELICARIO_TEST_PASSPHRASE").ok()
}
#[cfg(not(debug_assertions))]
pub(crate) fn test_passphrase_override() -> Option<String> {
    None
}

/// Check for test item secret override (debug builds only; stripped from release).
#[cfg(debug_assertions)]
pub(crate) fn test_item_secret_override() -> Option<String> {
    std::env::var("RELICARIO_TEST_ITEM_SECRET").ok()
}
#[cfg(not(debug_assertions))]
pub(crate) fn test_item_secret_override() -> Option<String> {
    None
}

/// Check for test backup passphrase override (debug builds only; stripped from release).
#[cfg(debug_assertions)]
pub(crate) fn test_backup_passphrase_override() -> Option<String> {
    std::env::var("RELICARIO_TEST_BACKUP_PASSPHRASE").ok()
}
#[cfg(not(debug_assertions))]
pub(crate) fn test_backup_passphrase_override() -> Option<String> {
    None
}



