use std::collections::HashMap;

use tracing::instrument;

use crate::{
    state::{FileId, Treehouse},
    tree::{attributes::Content, SemaBranchId},
    vfs::VPathBuf,
};

#[derive(Debug, Clone, Default)]
struct NavigationMapBuilder {
    stack: Vec<VPathBuf>,
    navigation_map: NavigationMap,
}

impl NavigationMapBuilder {
    fn enter_tree(&mut self, file_id: FileId, tree_path: VPathBuf) {
        self.stack.push(tree_path.clone());
        self.navigation_map
            .paths
            .insert(file_id, self.stack.clone());
    }

    fn exit_tree(&mut self) {
        self.stack.pop();
    }

    fn finish(self) -> NavigationMap {
        self.navigation_map
    }
}

#[derive(Debug, Clone, Default)]
pub struct NavigationMap {
    /// Tells you which pages need to be opened to get to the key.
    pub paths: HashMap<FileId, Vec<VPathBuf>>,
}

impl NavigationMap {
    #[instrument(name = "NavigationMap::build", skip(treehouse))]
    pub fn build(treehouse: &Treehouse, root_file_id: FileId) -> Self {
        let mut builder = NavigationMapBuilder::default();

        fn rec_branch(
            treehouse: &Treehouse,
            builder: &mut NavigationMapBuilder,
            branch_id: SemaBranchId,
        ) {
            let branch = treehouse.tree.branch(branch_id);
            if let Content::ResolvedLink(linked) = &branch.attributes.content {
                rec_tree(treehouse, builder, *linked);
            } else {
                for &child_id in &branch.children {
                    rec_branch(treehouse, builder, child_id);
                }
            }
        }

        fn rec_tree(treehouse: &Treehouse, builder: &mut NavigationMapBuilder, file_id: FileId) {
            if let Some(roots) = treehouse.roots.get(&file_id) {
                // Pages can link to each other causing infinite recursion, so we need to handle that
                // case by skipping pages that already have been analyzed.
                if !builder.navigation_map.paths.contains_key(&file_id) {
                    builder.enter_tree(
                        file_id,
                        treehouse
                            .tree_path(file_id)
                            .expect("tree files may only link to other tree files")
                            .to_owned(),
                    );
                    for &branch_id in &roots.branches {
                        rec_branch(treehouse, builder, branch_id);
                    }
                    builder.exit_tree();
                }
            }
        }

        rec_tree(treehouse, &mut builder, root_file_id);

        builder.finish()
    }
}
