pub mod config;
pub mod discord;
pub mod git;
pub mod project;
pub mod tui;
pub mod watcher;

use std::collections::HashMap;
use std::io::IsTerminal;
use std::path::{Path, PathBuf};

use anyhow::{bail, Context, Result};
use clap::{Parser, Subcommand};
use tokio::sync::mpsc;
use tracing::info;

#[derive(Parser, Debug)]
#[command(name = "donion", about = "A simple, opinionated CLI tool to automatically deploy apps.")]
pub struct Cli {
    /// Directory to operate in
    #[arg(short, long, default_value = ".")]
    pub dir: PathBuf,

    #[command(subcommand)]
    pub command: Option<Command>,
}

#[derive(Subcommand, Debug)]
pub enum Command {
    /// Generate an example donion.toml in the current directory
    Config,
}

pub async fn run(args: Cli) -> Result<()> {
    if let Some(command) = args.command {
        return match command {
            Command::Config => generate_config(),
        };
    }

    let dir = args.dir.canonicalize()?;
    let use_tui = std::io::stdout().is_terminal();

    if !use_tui {
        let _ = tracing_subscriber::fmt()
            .with_target(false)
            .with_level(true)
            .try_init();
    }

    let mut branches = config::load(&dir)?;
    info!(count = branches.len(), "loaded branch configs");

    setup_worktrees(&mut branches).await?;

    let (tx, rx) = mpsc::unbounded_channel();

    let mut handles = Vec::new();
    for (idx, branch_cfg) in branches.into_iter().enumerate() {
        info!(branch = %branch_cfg.branch, dir = %branch_cfg.directory.display(), "spawning watcher");
        handles.push(tokio::spawn(watcher::watch(branch_cfg, tx.clone(), idx)));
    }

    drop(tx);

    if use_tui {
        tui::run(rx).await?;
    } else {
        drop(rx);
        futures::future::join_all(handles).await;
    }

    Ok(())
}

async fn setup_worktrees(branches: &mut [config::BranchConfig]) -> Result<()> {
    let mut by_root: HashMap<PathBuf, Vec<usize>> = HashMap::new();
    for (idx, cfg) in branches.iter().enumerate() {
        let root = git::git_root(&cfg.directory).await?;
        by_root.entry(root).or_default().push(idx);
    }

    for (repo_root, indices) in &by_root {
        if indices.len() <= 1 {
            continue;
        }

        info!(repo = %repo_root.display(), branches = indices.len(), "shared repo, setting up worktrees");

        git::prune_worktrees(repo_root).await?;

        let worktree_base = repo_root.join(".donion").join("worktrees");

        for &idx in indices {
            let branch = &branches[idx].branch;
            let rel = branches[idx].directory.strip_prefix(repo_root)
                .unwrap_or(Path::new(""))
                .to_path_buf();

            let worktree_name = match &branches[idx].name {
                Some(name) => name.clone(),
                None => branch.to_string(),
            };
            let worktree_path = worktree_base.join(&worktree_name);

            if !worktree_path.exists() {
                git::fetch_branch(repo_root, branch).await?;
                git::add_worktree(repo_root, &worktree_path, branch).await?;
                info!(branch = %branch, path = %worktree_path.display(), "created worktree");
            }

            branches[idx].directory = worktree_path.join(&rel);
        }
    }

    Ok(())
}

fn generate_config() -> Result<()> {
    let path = PathBuf::from("donion.toml");
    if path.exists() {
        bail!("donion.toml already exists in the current directory");
    }
    std::fs::write(&path, include_str!("../example.donion.toml"))
        .context("failed to write donion.toml")?;
    info!("wrote donion.toml");
    Ok(())
}
