use std::collections::{HashMap, HashSet};

/// Tracks dependencies of each template:
///  - forward: template_rel -> {snippets, contents(files), content_ptrs(file+path)}
///  - reverse: snippet_rel  -> {templates}
///  - reverse: content_rel  -> {templates}
///  - reverse: content_ptr_key "<file.toml>::path=<dot.path>" -> {templates}
#[derive(Default)]
pub struct DepGraph {
    // template -> deps
    by_template_snips: HashMap<String, HashSet<String>>,
    by_template_conts: HashMap<String, HashSet<String>>,
    by_template_contptrs: HashMap<String, HashSet<String>>,

    // reverse maps
    by_snip_to_tpl: HashMap<String, HashSet<String>>,
    by_cont_to_tpl: HashMap<String, HashSet<String>>,
    by_contptr_to_tpl: HashMap<String, HashSet<String>>,
}

impl DepGraph {
    /// Record all deps for a template; each Vec is the full set for this build.
    pub fn record(
        &mut self,
        template_rel: String,
        snips: Vec<String>,
        conts: Vec<String>,
        cont_ptrs: Vec<String>,
    ) {
        // snippets
        {
            let set = self.by_template_snips.entry(template_rel.clone()).or_default();
            set.clear();
            for s in snips {
                set.insert(s.clone());
                self.by_snip_to_tpl.entry(s).or_default().insert(template_rel.clone());
            }
        }
        // content files
        {
            let set = self.by_template_conts.entry(template_rel.clone()).or_default();
            set.clear();
            for c in conts {
                set.insert(c.clone());
                self.by_cont_to_tpl.entry(c).or_default().insert(template_rel.clone());
            }
        }
        // content pointers
        {
            let set = self.by_template_contptrs.entry(template_rel.clone()).or_default();
            set.clear();
            for p in cont_ptrs {
                set.insert(p.clone());
                self.by_contptr_to_tpl.entry(p).or_default().insert(template_rel.clone());
            }
        }
    }

    /// All templates that depend on a snippet rel key ("path/to/x.htmls")
    pub fn templates_by_snippet(&self, snip_rel: &str) -> Vec<String> {
        self.by_snip_to_tpl
            .get(snip_rel)
            .map(|s| s.iter().cloned().collect())
            .unwrap_or_default()
    }

    /// All templates that depend on a content rel key ("path/to/x.toml")
    pub fn templates_by_content(&self, cont_rel: &str) -> Vec<String> {
        self.by_cont_to_tpl
            .get(cont_rel)
            .map(|s| s.iter().cloned().collect())
            .unwrap_or_default()
    }

    /// All templates that depend on a specific content pointer key ("<file>.toml::path=dot.path")
    pub fn templates_by_content_ptr(&self, cont_ptr_key: &str) -> Vec<String> {
        self.by_contptr_to_tpl
            .get(cont_ptr_key)
            .map(|s| s.iter().cloned().collect())
            .unwrap_or_default()
    }

    /// All pointer keys recorded for a content file
    pub fn pointer_keys_for_content_file(&self, cont_rel: &str) -> Vec<String> {
        // Find all pointer keys that start with "<cont_rel>::path="
        let mut out = Vec::new();
        for key in self.by_contptr_to_tpl.keys() {
            if key.starts_with(cont_rel) && key.contains("::path=") {
                out.push(key.clone());
            }
        }
        out
    }

    #[allow(dead_code)]
    pub fn all_templates(&self) -> Vec<String> {
        let mut set: HashSet<String> = HashSet::new();
        for k in self.by_template_snips.keys() { set.insert(k.clone()); }
        for k in self.by_template_conts.keys() { set.insert(k.clone()); }
        for k in self.by_template_contptrs.keys() { set.insert(k.clone()); }
        set.into_iter().collect()
    }

    #[allow(dead_code)]
    pub fn forget_template(&mut self, template_rel: &str) {
        if let Some(snips) = self.by_template_snips.remove(template_rel) {
            for s in snips {
                if let Some(tset) = self.by_snip_to_tpl.get_mut(&s) {
                    tset.remove(template_rel);
                    if tset.is_empty() { self.by_snip_to_tpl.remove(&s); }
                }
            }
        }
        if let Some(conts) = self.by_template_conts.remove(template_rel) {
            for c in conts {
                if let Some(tset) = self.by_cont_to_tpl.get_mut(&c) {
                    tset.remove(template_rel);
                    if tset.is_empty() { self.by_cont_to_tpl.remove(&c); }
                }
            }
        }
        if let Some(ptrs) = self.by_template_contptrs.remove(template_rel) {
            for p in ptrs {
                if let Some(tset) = self.by_contptr_to_tpl.get_mut(&p) {
                    tset.remove(template_rel);
                    if tset.is_empty() { self.by_contptr_to_tpl.remove(&p); }
                }
            }
        }
    }
    pub fn clear_content_file(&mut self, content_rel: &str) {
        // find templates that referenced this file directly
        if let Some(tpls) = self.by_cont_to_tpl.remove(content_rel) {
            for tpl in tpls {
                if let Some(edges) = self.by_template_conts.get_mut(&tpl) {
                    edges.remove(content_rel);
                }
            }
        }
    }
}