use anyhow::bail;
use gix::prelude::ObjectIdExt;
use unicode_width::UnicodeWidthStr;

use crate::OutputFormat;

pub fn list(repo: gix::Repository, out: &mut dyn std::io::Write, format: OutputFormat) -> anyhow::Result<()> {
    if format != OutputFormat::Human {
        bail!("JSON output isn't implemented yet");
    }
    let main_repo = repo.main_repo()?;
    let mut worktrees = Vec::new();

    if let Some(worktree) = main_repo.worktree() {
        worktrees.push(create_worktree_info(&main_repo, gix::path::realpath(worktree.base())?)?);
    }

    for proxy in main_repo.worktrees()? {
        let base = gix::path::realpath(proxy.base()?)?;

        match proxy.into_repo() {
            Ok(worktree_repo) => {
                worktrees.push(create_worktree_info(&worktree_repo, base)?);
            }
            Err(_) => {
                worktrees.push(create_inaccessible_worktree_info(&repo, base));
            }
        }
    }

    let path_width = worktrees
        .iter()
        .map(|worktree| UnicodeWidthStr::width(worktree.base.as_str()))
        .max()
        .unwrap_or(0);

    for worktree in worktrees {
        worktree.write(out, path_width)?;
    }

    Ok(())
}

struct WorktreeInfo {
    base: String,
    head: String,
    branch: String,
}

impl WorktreeInfo {
    fn write(&self, out: &mut dyn std::io::Write, path_width: usize) -> std::io::Result<()> {
        writeln!(
            out,
            "{}{} {} [{}]",
            self.base,
            " ".repeat(path_width.saturating_sub(UnicodeWidthStr::width(self.base.as_str()))),
            self.head,
            self.branch,
        )
    }
}

fn create_worktree_info(repo: &gix::Repository, base: std::path::PathBuf) -> anyhow::Result<WorktreeInfo> {
    let head = repo
        .head_id()
        .map_or_else(
            |_| repo.object_hash().null().attach(repo).shorten_or_id(),
            |id| id.shorten_or_id(),
        )
        .to_string();

    let branch = repo.head_name()?.map_or_else(
        || "<detached>".to_string(),
        |name| name.shorten().to_owned().to_string(),
    );

    Ok(WorktreeInfo {
        base: base.display().to_string(),
        head,
        branch,
    })
}

fn create_inaccessible_worktree_info(repo: &gix::Repository, base: std::path::PathBuf) -> WorktreeInfo {
    WorktreeInfo {
        base: base.display().to_string(),
        head: repo.object_hash().null().attach(repo).shorten_or_id().to_string(),
        branch: "<unknown>".to_string(),
    }
}
