use crate::config::Config;
use crate::elements::Element;
use crate::meta::{MetaCache, MetaLocal};
use crate::store::Store;
use anyhow::Result;
use chrono::NaiveDate;
use colored::Colorize;
use std::collections::HashMap;

pub fn run(
    config: &Config,
    dates: Vec<NaiveDate>,
    all: bool,
    type_filter: Option<String>,
    status_filter: Option<String>,
    name_filter: Option<String>,
) -> Result<()> {
    let store = Store::new(&config.storage_dir);

    // For unfiltered `--all`, try serving from cache before scanning every file.
    let no_filters = type_filter.is_none() && status_filter.is_none() && name_filter.is_none();
    if all && no_filters {
        let snapshot = MetaCache::build_snapshot(&config.storage_dir);
        let local = MetaLocal::load(&config.storage_dir);
        if local.cache.is_valid(&snapshot) && !local.cache.tag_counts.is_empty() {
            let counts: HashMap<String, usize> = local
                .cache
                .tag_counts
                .iter()
                .map(|(k, &v)| (k.clone(), v as usize))
                .collect();
            return print_counts(counts);
        }
    }

    let effective_dates: Vec<NaiveDate> = if all { store.all_file_dates()? } else { dates };

    let mut counts: HashMap<String, usize> = HashMap::new();

    for d in &effective_dates {
        let elements = store.parse_date(*d)?;
        for (_, el) in elements {
            if el.is_mps_group() || el.is_unknown() {
                continue;
            }

            // Type filter
            if let Some(ref tf) = type_filter {
                if el.sign() != tf {
                    continue;
                }
            }
            // Status filter (only applies to tasks; other types are excluded)
            if let Some(ref sf) = status_filter {
                match &el {
                    Element::Task { data, .. } => {
                        if data.status_str() != sf {
                            continue;
                        }
                    }
                    _ => continue,
                }
            }
            // Name filter (only applies to characters; other types are excluded)
            if let Some(ref nf) = name_filter {
                match &el {
                    Element::Character { data, .. } => {
                        if data.name.as_deref().map(|n| n.to_lowercase()) != Some(nf.to_lowercase())
                        {
                            continue;
                        }
                    }
                    _ => continue,
                }
            }

            for tag in el.tags() {
                *counts.entry(tag.clone()).or_insert(0) += 1;
            }
        }
    }

    // Persist computed counts into cache when scanning all files with no filters.
    if all && no_filters && !counts.is_empty() {
        let snapshot = MetaCache::build_snapshot(&config.storage_dir);
        let mut local = MetaLocal::load(&config.storage_dir);
        local.cache.files_snapshot = snapshot;
        local.cache.tag_counts = counts
            .iter()
            .map(|(k, &v)| (k.clone(), v as u32))
            .collect();
        let _ = local.save(&config.storage_dir);
    }

    print_counts(counts)
}

fn print_counts(counts: HashMap<String, usize>) -> Result<()> {
    if counts.is_empty() {
        println!("{}", "(no tags found)".yellow());
        return Ok(());
    }

    let mut sorted: Vec<(String, usize)> = counts.into_iter().collect();
    sorted.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));

    let max_cnt = sorted[0].1;
    let max_tag_len = sorted
        .iter()
        .map(|(t, _)| t.len())
        .max()
        .unwrap_or(3)
        .max(3);
    let bar_max = 24usize;

    println!(
        "{}",
        format!("  {:<width$}  Count  Frequency", "Tag", width = max_tag_len).white()
    );
    println!(
        "{}",
        format!(
            "  {:-<width$}  -----  {:-<bar$}",
            "",
            "",
            width = max_tag_len,
            bar = bar_max
        )
        .white()
    );

    for (tag, n) in &sorted {
        let bar_len = ((*n as f64 / max_cnt as f64) * bar_max as f64).ceil() as usize;
        let bar = "█".repeat(bar_len).cyan().to_string();
        println!(
            "  {}  {:>5}  {}",
            format!("{:<width$}", tag, width = max_tag_len).white(),
            n,
            bar
        );
    }

    let total: usize = sorted.iter().map(|(_, n)| n).sum();
    let tag_count = sorted.len();
    println!();
    println!(
        "{}",
        format!(
            "  {} tag{}, {} total uses",
            tag_count,
            if tag_count == 1 { "" } else { "s" },
            total
        )
        .white()
    );

    Ok(())
}
