use crate::cache::Cache;
use crate::error::{Result, OilError};
use crate::install::InstallState;
use console::style;
use std::collections::{HashMap, HashSet};

pub async fn deps(cache: &Cache, formula: &str, tree: bool, installed: bool) -> Result<()> {
    let formulae = cache.load_all_formulae().await?;
    let formula_index: HashMap<_, _> = formulae
        .iter()
        .map(|f| (f.name.as_str(), f))
        .chain(formulae.iter().map(|f| (f.full_name.as_str(), f)))
        .collect();

    let target = formula_index
        .get(formula)
        .ok_or_else(|| OilError::FormulaNotFound(formula.to_string()))?;

    let installed_names: HashSet<String> = if installed {
        let state = InstallState::new()?;
        state.sync_from_cellar().await.ok();
        state.load().await?.into_keys().collect()
    } else {
        HashSet::new()
    };

    let deps = target.dependencies.as_deref().unwrap_or_default();
    let filtered: Vec<&str> = if installed {
        deps.iter()
            .filter(|d| installed_names.contains(*d))
            .map(|d| d.as_str())
            .collect()
    } else {
        deps.iter().map(|d| d.as_str()).collect()
    };

    if filtered.is_empty() {
        println!("{} has no dependencies", style(formula).magenta());
        return Ok(());
    }

    if tree {
        println!("{}", style(formula).magenta().bold());
        print_dep_tree(&filtered, &formula_index, &mut HashSet::new(), "", true);
    } else {
        for dep in &filtered {
            println!("{}", style(dep).cyan());
        }
    }

    Ok(())
}

fn print_dep_tree(
    deps: &[&str],
    formula_index: &HashMap<&str, &crate::api::Formula>,
    seen: &mut HashSet<String>,
    prefix: &str,
    last_group: bool,
) {
    let _ = last_group;
    for (i, dep) in deps.iter().enumerate() {
        let is_last = i == deps.len() - 1;
        let connector = if is_last { "└─ " } else { "├─ " };
        let already_seen = seen.contains(*dep);

        print!("{}{}", prefix, connector);

        if already_seen {
            println!("{} {}", style(dep).cyan(), style("(already shown)").dim());
            continue;
        }

        println!("{}", style(dep).cyan());
        seen.insert(dep.to_string());

        if let Some(formula) = formula_index.get(*dep) {
            let child_deps: Vec<&str> = formula
                .dependencies
                .as_deref()
                .unwrap_or_default()
                .iter()
                .map(|d| d.as_str())
                .collect();

            if !child_deps.is_empty() {
                let extension = if is_last { "   " } else { "│  " };
                let new_prefix = format!("{}{}", prefix, extension);
                print_dep_tree(&child_deps, formula_index, seen, &new_prefix, is_last);
            }
        }
    }
}
