//! Implements `limb status`. Dirty / ahead / behind per worktree.

use std::time::SystemTime;

use anyhow::Result;

use crate::cli::StatusArgs;
use crate::context::Context;
use crate::fmt::col_width;
use crate::refresh::{self, Freshness, Thresholds};
use crate::style;
use crate::worktree::{self, RepoStatus, Status};

/// Runs `limb status`.
///
/// Dispatches on `--all` exactly like [`crate::cmd::list`]. Columns:
/// NAME, BRANCH, DIRTY (file count), UPSTREAM (tracking + ahead/behind
/// arrows). JSON output via `--json`.
///
/// # Errors
///
/// Returns an error if the repo (or global config, with `--all`) cannot
/// be resolved, or if `--all` is requested with no configured roots.
pub fn run(ctx: &Context, args: &StatusArgs) -> Result<()> {
    let global = ctx.global().ok();
    let thresholds = global
        .as_ref()
        .map_or_else(Thresholds::default, |g| g.refresh_thresholds);

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

    if args.all {
        run_all(ctx, &thresholds)
    } else {
        run_single(ctx, &thresholds)
    }
}

fn run_single(ctx: &Context, thresholds: &Thresholds) -> Result<()> {
    let repo = ctx.repo()?;
    let rows: Vec<Status> = worktree::list_with_status(&repo)?
        .into_iter()
        .filter(|s| !s.worktree.bare)
        .collect();
    if ctx.json {
        println!("{}", serde_json::to_string_pretty(&rows)?);
    } else {
        print_single(&rows, thresholds);
    }
    Ok(())
}

fn run_all(ctx: &Context, thresholds: &Thresholds) -> Result<()> {
    let global = ctx.global()?;
    if global.projects_roots.is_empty() {
        anyhow::bail!(
            "--all: no projects.roots configured; \
             try: set `projects.roots = [\"~/dev/myrepos\"]` in ~/.config/limb/config.toml"
        );
    }
    let rows: Vec<RepoStatus> = worktree::list_all_with_status(&global.projects_roots)
        .into_iter()
        .filter(|r| !r.status.worktree.bare)
        .collect();
    if ctx.json {
        println!("{}", serde_json::to_string_pretty(&rows)?);
    } else {
        print_all(&rows, thresholds);
    }
    Ok(())
}

fn print_single(rows: &[Status], thresholds: &Thresholds) {
    if rows.is_empty() {
        anstream::eprintln!("no worktrees");
        return;
    }
    let now = SystemTime::now();
    let name_w = col_width(rows, |r| r.worktree.name.len(), 4);
    let branch_w = col_width(
        rows,
        |r| r.worktree.branch.as_deref().unwrap_or("(detached)").len(),
        6,
    );
    let fetched_w = col_width(rows, |r| refresh::render_age(now, r.fetched_at).len(), 7);

    let h = style::DIM;
    anstream::println!(
        "{h}{:<name_w$}  {:<branch_w$}  DIRTY  {:<fetched_w$}  UPSTREAM{h:#}",
        "NAME",
        "BRANCH",
        "FETCHED"
    );
    for r in rows {
        let branch = r.worktree.branch.as_deref().unwrap_or("(detached)");
        anstream::println!(
            "{:<name_w$}  {:<branch_w$}  {}  {}  {}",
            r.worktree.name,
            branch,
            dirty_cell(r.dirty_files),
            fetched_cell(now, r.fetched_at, thresholds, fetched_w),
            upstream_cell(r.upstream.as_ref()),
        );
    }
}

fn print_all(rows: &[RepoStatus], thresholds: &Thresholds) {
    if rows.is_empty() {
        anstream::eprintln!("no worktrees");
        return;
    }
    let now = SystemTime::now();
    let repo_w = col_width(rows, |r| r.repo.len(), 4);
    let name_w = col_width(rows, |r| r.status.worktree.name.len(), 4);
    let branch_w = col_width(
        rows,
        |r| {
            r.status
                .worktree
                .branch
                .as_deref()
                .unwrap_or("(detached)")
                .len()
        },
        6,
    );
    let fetched_w = col_width(
        rows,
        |r| refresh::render_age(now, r.status.fetched_at).len(),
        7,
    );

    let h = style::DIM;
    anstream::println!(
        "{h}{:<repo_w$}  {:<name_w$}  {:<branch_w$}  DIRTY  {:<fetched_w$}  UPSTREAM{h:#}",
        "REPO",
        "NAME",
        "BRANCH",
        "FETCHED"
    );
    let a = style::ACCENT;
    for r in rows {
        anstream::println!(
            "{a}{:<repo_w$}{a:#}  {:<name_w$}  {:<branch_w$}  {}  {}  {}",
            r.repo,
            r.status.worktree.name,
            r.status.worktree.branch.as_deref().unwrap_or("(detached)"),
            dirty_cell(r.status.dirty_files),
            fetched_cell(now, r.status.fetched_at, thresholds, fetched_w),
            upstream_cell(r.status.upstream.as_ref()),
        );
    }
}

fn fetched_cell(
    now: SystemTime,
    fetched_at: Option<SystemTime>,
    thresholds: &Thresholds,
    width: usize,
) -> String {
    let age = fetched_at.and_then(|t| now.duration_since(t).ok());
    let freshness = refresh::classify(age, thresholds);
    let s = refresh::render_age(now, fetched_at);
    let style = freshness_style(freshness);
    format!("{style}{s:<width$}{style:#}")
}

fn freshness_style(f: Freshness) -> anstyle::Style {
    match f {
        Freshness::Fresh => style::FRESH,
        Freshness::Warn => style::WARN_AGE,
        Freshness::Stale => style::STALE_AGE,
        Freshness::Ancient | Freshness::Unknown => style::ANCIENT_AGE,
    }
}

fn dirty_cell(n: usize) -> String {
    if n == 0 {
        "     ".to_string()
    } else {
        let s = style::DIRTY;
        format!("{s}{n:>5}{s:#}")
    }
}

fn upstream_cell(u: Option<&worktree::Upstream>) -> String {
    match u {
        Some(u) if u.ahead == 0 && u.behind == 0 => u.name.clone(),
        Some(u) => {
            let ahead = style::AHEAD;
            let behind = style::BEHIND;
            format!(
                "{} {behind}↓{}{behind:#} {ahead}↑{}{ahead:#}",
                u.name, u.behind, u.ahead
            )
        }
        None => {
            let d = style::DIM;
            format!("{d}-{d:#}")
        }
    }
}
