//! Implements `limb pick`. Interactive TUI worktree picker.

use std::path::PathBuf;

use anyhow::Result;
use rayon::prelude::*;

use crate::cli::PickArgs;
use crate::context::Context;
use crate::discover;
use crate::tui::app::{Entry, Mode};
use crate::tui::theme::{Theme, ThemeKind};
use crate::{tui, worktree};

/// Runs `limb pick`.
///
/// Dispatches on whether `projects.roots` is configured: empty → single
/// repo picker; non-empty → cross-repo picker scanning every root. Prints
/// the selected worktree's path to stdout (consumed by the shell wrapper
/// to `cd` the parent shell).
///
/// # Errors
///
/// Returns an error if no selectable worktrees exist, the terminal isn't
/// a TTY, or the TUI itself fails. Cancellation via `Esc` returns
/// [`crate::error::Canceled`] which exits `130`.
pub fn run(ctx: &Context, args: &PickArgs) -> Result<()> {
    let global = ctx.global()?;

    if args.fetch {
        if global.projects_roots.is_empty() {
            let repo = ctx.repo()?;
            if !ctx.quiet {
                anstream::eprintln!("{}→ fetch{:#}", crate::style::DIM, crate::style::DIM);
            }
            worktree::fetch(&repo, ctx.quiet)?;
        } else {
            fetch_all_roots(&global.projects_roots, ctx.quiet);
        }
    }

    let (entries, mode) = if global.projects_roots.is_empty() {
        single_repo(ctx)?
    } else {
        cross_repo(&global.projects_roots)
    };

    let selectable: Vec<_> = entries.into_iter().filter(|e| !e.worktree.bare).collect();
    if selectable.is_empty() {
        anyhow::bail!("no selectable worktrees; try: `limb add <name>` to create one");
    }

    let theme_kind = ThemeKind::from_name(&global.ui_theme);
    let theme = Theme::resolve(theme_kind, ctx.no_color);
    let path = tui::run_picker(selectable, mode, theme, global.refresh_thresholds)?;
    println!("{}", path.display());
    Ok(())
}

/// Parallel-fetches every repo under `roots`. Errors per-repo are
/// reported on stderr and do not abort. Used by `--fetch` and is shared
/// with `limb update --all`.
pub(crate) fn fetch_all_roots(roots: &[PathBuf], quiet: bool) {
    let repos: Vec<PathBuf> = roots
        .iter()
        .filter(|r| r.is_dir())
        .flat_map(|r| discover::repos_under(r).unwrap_or_default())
        .collect();
    if !quiet && !repos.is_empty() {
        anstream::eprintln!(
            "{}→ fetch {} repos{:#}",
            crate::style::DIM,
            repos.len(),
            crate::style::DIM
        );
    }
    repos.par_iter().for_each(|repo_dir| {
        let Some(anchor) = discover::anchor_for(repo_dir) else {
            return;
        };
        if let Err(e) = worktree::fetch(&anchor, true) {
            let warn = crate::style::WARN;
            anstream::eprintln!(
                "{warn}fetch failed{warn:#}: {} ({e:#})",
                discover::repo_name(repo_dir)
            );
        }
    });
}

fn single_repo(ctx: &Context) -> Result<(Vec<Entry>, Mode)> {
    let repo = ctx.repo()?;
    let rows = worktree::list_with_status(&repo)?;
    let entries = rows
        .into_iter()
        .map(|s| Entry {
            repo: None,
            worktree: s.worktree,
            dirty: s.dirty_files,
            upstream: s.upstream,
            fetched_at: s.fetched_at,
        })
        .collect();
    Ok((entries, Mode::SingleRepo))
}

fn cross_repo(roots: &[std::path::PathBuf]) -> (Vec<Entry>, Mode) {
    let rows = worktree::list_all_with_status(roots);
    let entries = rows
        .into_iter()
        .map(|s| Entry {
            repo: Some(s.repo),
            worktree: s.status.worktree,
            dirty: s.status.dirty_files,
            upstream: s.status.upstream,
            fetched_at: s.status.fetched_at,
        })
        .collect();
    (entries, Mode::CrossRepo)
}
