//! Clap argument definitions for every subcommand.
//!
//! Each subcommand gets its own `*Args` struct so the `cmd/*.rs` side
//! has a single, named place to pattern-match on. Human-facing help text
//! lives in `#[arg(help = ...)]` / `#[command(about = ...)]` attributes.
//! those are what users see via `--help`, so they're the source of truth
//! for user-facing documentation.

use std::path::PathBuf;

use clap::{Args, Parser, Subcommand, ValueEnum};

/// Top-level CLI parser.
///
/// Global flags (`--repo`, `--config`, `--json`, `--no-color`, `--yes`,
/// `--quiet`) apply to every subcommand; the selected subcommand and its
/// arguments live in [`Self::cmd`].
#[derive(Parser, Debug)]
#[command(
    name = "limb",
    version,
    about = "A polished CLI + TUI for working with git worktrees",
    long_about = "limb makes git worktree pleasant to use. Zero-config inside any git repo; \
                  progressive configuration when you want templates, hooks, or cross-repo mode.",
    propagate_version = true,
    disable_help_subcommand = true
)]
#[allow(clippy::struct_excessive_bools)]
pub struct Cli {
    #[arg(
        long,
        short = 'r',
        global = true,
        env = "LIMB_REPO",
        value_name = "PATH",
        help = "Target git repository (default: discovered from cwd)"
    )]
    pub repo: Option<PathBuf>,

    #[arg(
        long,
        global = true,
        env = "LIMB_CONFIG",
        value_name = "PATH",
        help = "Path to a specific config file (overrides ~/.config/limb/config.toml)"
    )]
    pub config: Option<PathBuf>,

    #[arg(
        long,
        global = true,
        help = "Emit machine-readable JSON where applicable"
    )]
    pub json: bool,

    #[arg(
        long,
        global = true,
        env = "NO_COLOR",
        help = "Disable colored output (NO_COLOR env also honored)"
    )]
    pub no_color: bool,

    #[arg(
        long,
        short = 'y',
        global = true,
        help = "Skip interactive confirmation prompts"
    )]
    pub yes: bool,

    #[arg(
        long,
        short = 'q',
        global = true,
        help = "Suppress non-essential informational output"
    )]
    pub quiet: bool,

    #[command(subcommand)]
    pub cmd: Cmd,
}

/// The subcommand the user invoked.
///
/// Variants map 1:1 to files under `src/cmd/`. Human-facing descriptions
/// are carried on the clap `#[command(about = "...")]` attributes.
#[derive(Subcommand, Debug)]
pub enum Cmd {
    #[command(about = "List worktrees in the current repo")]
    List(ListArgs),

    #[command(about = "Create a new worktree")]
    Add(AddArgs),

    #[command(about = "Remove a worktree")]
    Remove(RemoveArgs),

    #[command(about = "Print the path of a worktree (for shell eval)")]
    Cd(CdArgs),

    #[command(about = "Interactive TUI picker. Prints selected path on exit")]
    Pick(PickArgs),

    #[command(about = "Per-worktree status table (dirty, ahead/behind, upstream)")]
    Status(StatusArgs),

    #[command(about = "Fetch and fast-forward worktrees")]
    Update(UpdateArgs),

    #[command(about = "Lock a worktree so it cannot be pruned or removed")]
    Lock(LockArgs),

    #[command(about = "Unlock a previously-locked worktree")]
    Unlock(UnlockArgs),

    #[command(about = "Repair worktree administrative files after manual moves")]
    Repair(RepairArgs),

    #[command(about = "Remove administrative files for worktrees that no longer exist")]
    Prune(PruneArgs),

    #[command(about = "Rename a worktree. Move its directory and update git refs")]
    Rename(RenameArgs),

    #[command(about = "Remove worktrees whose upstream branches are gone from origin")]
    Clean(CleanArgs),

    #[command(about = "Diagnose setup and emit actionable fixes")]
    Doctor,

    #[command(about = "Print the resolved configuration as JSON")]
    Config,

    #[command(about = "Link shared files into every worktree of the current repo")]
    Setup(SetupArgs),

    #[command(about = "Convert a plain-clone repo into the bare-clone + worktrees layout")]
    Migrate(MigrateArgs),

    #[command(about = "Emit shell integration (eval in your rc)")]
    Init(InitArgs),

    #[command(about = "Emit shell completions")]
    Completions(CompletionsArgs),

    #[command(
        about = "Mark a path to be cd'd into on the shell's next prompt (tmux only)",
        hide = true
    )]
    MarkCd(MarkCdArgs),
}

/// Arguments for `limb list`.
#[derive(Args, Debug)]
pub struct ListArgs {
    #[arg(
        long,
        help = "Include worktrees from every repo under configured projects.roots"
    )]
    pub all: bool,

    #[arg(
        long,
        short = 'v',
        help = "Show lock and prune reasons next to the worktree tag"
    )]
    pub verbose: bool,
}

/// Arguments for `limb pick`.
#[derive(Args, Debug)]
pub struct PickArgs {
    #[arg(
        long,
        short = 'f',
        help = "Fetch from remote(s) before opening the picker"
    )]
    pub fetch: bool,
}

/// Arguments for `limb status`.
#[derive(Args, Debug)]
pub struct StatusArgs {
    #[arg(
        long,
        help = "Include worktrees from every repo under configured projects.roots"
    )]
    pub all: bool,

    #[arg(
        long,
        short = 'f',
        help = "Fetch from remote(s) before computing status"
    )]
    pub fetch: bool,
}

/// Arguments for `limb add`.
#[derive(Args, Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct AddArgs {
    #[arg(value_name = "NAME", help = "Worktree name (or template slug with -t)")]
    pub name: String,

    #[arg(
        value_name = "BASE",
        conflicts_with_all = ["from", "orphan"],
        help = "Commit-ish to branch from (shortcut for --from)"
    )]
    pub base: Option<String>,

    #[arg(
        long,
        short = 'b',
        value_name = "BRANCH",
        conflicts_with_all = ["detach", "orphan", "force_branch"],
        help = "Check out this existing branch in the new worktree"
    )]
    pub branch: Option<String>,

    #[arg(
        long,
        short = 'B',
        value_name = "BRANCH",
        requires = "from",
        conflicts_with_all = ["branch", "detach", "orphan", "template"],
        help = "Force-create a new branch (overwrites if it exists; requires --from)"
    )]
    pub force_branch: Option<String>,

    #[arg(
        long,
        value_name = "COMMIT",
        conflicts_with = "orphan",
        help = "Create a new branch starting from this commit-ish"
    )]
    pub from: Option<String>,

    #[arg(
        long,
        requires = "from",
        conflicts_with_all = ["no_track", "detach", "orphan"],
        help = "Configure upstream tracking for the new branch (requires --from)"
    )]
    pub track: bool,

    #[arg(
        long = "no-track",
        requires = "from",
        conflicts_with_all = ["track", "detach", "orphan"],
        help = "Disable upstream tracking config for the new branch (requires --from)"
    )]
    pub no_track: bool,

    #[arg(
        long,
        requires = "from",
        conflicts_with_all = ["no_guess_remote", "detach", "orphan"],
        help = "Guess the upstream remote-tracking branch from the start commit (requires --from)"
    )]
    pub guess_remote: bool,

    #[arg(
        long = "no-guess-remote",
        requires = "from",
        conflicts_with_all = ["guess_remote", "detach", "orphan"],
        help = "Disable upstream guessing for the new branch (requires --from)"
    )]
    pub no_guess_remote: bool,

    #[arg(
        long,
        conflicts_with = "orphan",
        help = "Skip checking files out into the new worktree"
    )]
    pub no_checkout: bool,

    #[arg(
        long,
        conflicts_with = "no_relative_paths",
        help = "Store admin paths relative to the repo (portable across mounts)"
    )]
    pub relative_paths: bool,

    #[arg(
        long = "no-relative-paths",
        conflicts_with = "relative_paths",
        help = "Force absolute admin paths (override config)"
    )]
    pub no_relative_paths: bool,

    #[arg(
        long,
        short = 't',
        value_name = "TEMPLATE",
        conflicts_with_all = ["detach", "orphan"],
        help = "Apply a named template from .limb.toml"
    )]
    pub template: Option<String>,

    #[arg(
        long,
        conflicts_with = "orphan",
        help = "Create a detached HEAD (no branch)"
    )]
    pub detach: bool,

    #[arg(long, help = "Create an orphan branch with no parent commits")]
    pub orphan: bool,

    #[arg(long, help = "Lock the worktree after creation")]
    pub lock: bool,

    #[arg(
        long,
        value_name = "TEXT",
        requires = "lock",
        help = "Reason for locking (requires --lock)"
    )]
    pub reason: Option<String>,

    #[arg(long, help = "Show what would be added without making changes")]
    pub dry_run: bool,
}

/// Arguments for `limb remove`.
#[derive(Args, Debug)]
pub struct RemoveArgs {
    #[arg(value_name = "NAME", help = "Worktree to remove")]
    pub name: String,

    #[arg(long, short = 'f', help = "Skip the confirm prompt")]
    pub force: bool,

    #[arg(long, help = "Show what would be removed without making changes")]
    pub dry_run: bool,
}

/// Arguments for `limb cd`.
#[derive(Args, Debug)]
pub struct CdArgs {
    #[arg(value_name = "NAME", help = "Worktree name")]
    pub name: String,
}

/// Arguments for `limb lock`.
#[derive(Args, Debug)]
pub struct LockArgs {
    #[arg(value_name = "NAME", help = "Worktree to lock")]
    pub name: String,

    #[arg(
        long,
        value_name = "TEXT",
        help = "Reason for locking (shown in `git worktree list`)"
    )]
    pub reason: Option<String>,

    #[arg(long, help = "Show what would be locked without making changes")]
    pub dry_run: bool,
}

/// Arguments for `limb unlock`.
#[derive(Args, Debug)]
pub struct UnlockArgs {
    #[arg(value_name = "NAME", help = "Worktree to unlock")]
    pub name: String,

    #[arg(long, help = "Show what would be unlocked without making changes")]
    pub dry_run: bool,
}

/// Arguments for `limb repair`.
#[derive(Args, Debug)]
pub struct RepairArgs {
    #[arg(
        value_name = "PATH",
        help = "Specific worktree path(s) to repair; defaults to every worktree"
    )]
    pub paths: Vec<PathBuf>,
}

/// Arguments for `limb prune`.
#[derive(Args, Debug)]
pub struct PruneArgs {
    #[arg(
        long,
        value_name = "WHEN",
        help = "Only prune entries older than this (e.g. '1.week.ago', '2023-01-01')"
    )]
    pub expire: Option<String>,

    #[arg(long, help = "Show what would be pruned without making changes")]
    pub dry_run: bool,

    #[arg(long, help = "Report what is pruned")]
    pub report: bool,
}

/// Arguments for `limb rename`.
#[derive(Args, Debug)]
pub struct RenameArgs {
    #[arg(value_name = "OLD", help = "Current worktree name")]
    pub old: String,

    #[arg(value_name = "NEW", help = "New worktree name")]
    pub new: String,

    #[arg(long, help = "Show what would be renamed without making changes")]
    pub dry_run: bool,
}

/// Arguments for `limb clean`.
#[derive(Args, Debug)]
pub struct CleanArgs {
    #[arg(long, help = "Show what would be removed without making changes")]
    pub dry_run: bool,
}

/// Arguments for `limb setup`.
#[derive(Args, Debug)]
pub struct SetupArgs {
    #[arg(
        long,
        help = "Recreate symlinks that point elsewhere (preserves regular files)"
    )]
    pub refresh_shared: bool,

    #[arg(long, help = "Show what would be done without making changes")]
    pub dry_run: bool,
}

/// Arguments for `limb migrate`.
#[derive(Args, Debug)]
pub struct MigrateArgs {
    #[arg(
        value_name = "REPO",
        help = "Path to a plain git clone to convert (default: current repo)"
    )]
    pub repo: Option<PathBuf>,

    #[arg(long, help = "Show what would be done without making changes")]
    pub dry_run: bool,
}

/// Arguments for `limb update`.
#[derive(Args, Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct UpdateArgs {
    #[arg(
        long,
        help = "Update every repo under configured projects.roots in parallel"
    )]
    pub all: bool,

    #[arg(
        long,
        conflicts_with = "ff_only",
        help = "Only fetch. Don't fast-forward"
    )]
    pub fetch_only: bool,

    #[arg(long, help = "Only fast-forward. Skip fetch")]
    pub ff_only: bool,

    #[arg(long, help = "Show what would be done without making changes")]
    pub dry_run: bool,
}

/// Arguments for `limb init`.
#[derive(Args, Debug)]
pub struct InitArgs {
    #[arg(value_enum, help = "Target shell")]
    pub shell: Shell,

    #[arg(
        long,
        value_name = "PREFIX",
        default_value = "gw",
        help = "Prefix for emitted wrapper functions (gw, gwa, gwp, ...)"
    )]
    pub prefix: String,
}

/// Arguments for the hidden `limb mark-cd` command.
#[derive(Args, Debug)]
pub struct MarkCdArgs {
    #[arg(value_name = "PATH", help = "Path for the shell to cd into")]
    pub path: PathBuf,
}

/// Arguments for `limb completions`.
#[derive(Args, Debug)]
pub struct CompletionsArgs {
    #[arg(value_enum, help = "Target shell")]
    pub shell: CompletionShell,
}

/// Shells supported by `limb init`.
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Shell {
    Zsh,
    Bash,
    Fish,
    Pwsh,
}

/// Shells supported by `limb completions`. Superset of [`Shell`]
/// because `clap_complete` can emit elvish completions even though limb
/// does not provide an `init` hook for elvish.
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompletionShell {
    Zsh,
    Bash,
    Fish,
    Pwsh,
    Elvish,
}

impl From<CompletionShell> for clap_complete::Shell {
    fn from(s: CompletionShell) -> Self {
        match s {
            CompletionShell::Zsh => Self::Zsh,
            CompletionShell::Bash => Self::Bash,
            CompletionShell::Fish => Self::Fish,
            CompletionShell::Pwsh => Self::PowerShell,
            CompletionShell::Elvish => Self::Elvish,
        }
    }
}
