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

use super::{entries, Dir, DynDir, Entries, Query, VPath, VPathBuf};

pub struct MemDir {
    mount_points: HashMap<String, DynDir>,
}

enum Resolved<'fs, 'path> {
    Root,
    MountPoint {
        fs: &'fs dyn Dir,
        fs_path: &'path VPath,
        subpath: &'path VPath,
    },
    None,
}

impl MemDir {
    pub fn new() -> Self {
        Self {
            mount_points: HashMap::new(),
        }
    }

    pub fn add(&mut self, path: &VPath, dir: DynDir) {
        assert_eq!(
            path.depth(), 0,
            "path must be situated at root. MountPoints does not support nested paths, but you can nest MountPoints within other MountPoints"
        );

        assert!(
            self.mount_points
                .insert(path.as_str().to_owned(), dir)
                .is_none(),
            "duplicate mount point at {path:?}"
        );
    }

    fn resolve<'fs, 'path>(&'fs self, path: &'path VPath) -> Resolved<'fs, 'path> {
        if path == VPath::ROOT {
            return Resolved::Root;
        } else {
            let mount_point_name = path.as_str().split(VPath::SEPARATOR).next().unwrap();
            if let Some(mount_point) = self.mount_points.get(mount_point_name) {
                return Resolved::MountPoint {
                    fs: &**mount_point,
                    fs_path: VPath::new(mount_point_name),
                    subpath: path
                        .strip_prefix(VPath::new(mount_point_name))
                        .expect("path should have `mount_point_name` as its prefix"),
                };
            }
        }

        Resolved::None
    }

    fn dir(&self, path: &VPath) -> Vec<VPathBuf> {
        match self.resolve(path) {
            Resolved::Root => self.mount_points.keys().map(VPathBuf::new).collect(),

            Resolved::MountPoint {
                fs,
                fs_path,
                subpath,
            } => entries(fs, subpath)
                .into_iter()
                .map(|path| fs_path.join(&path))
                .collect(),

            Resolved::None => vec![],
        }
    }
}

impl Default for MemDir {
    fn default() -> Self {
        Self::new()
    }
}

impl Dir for MemDir {
    fn query(&self, path: &VPath, query: &mut Query) {
        query.provide(|| Entries(self.dir(path)));

        match self.resolve(path) {
            Resolved::Root | Resolved::None => (),
            Resolved::MountPoint {
                fs,
                fs_path: _,
                subpath,
            } => fs.query(subpath, query),
        }
    }
}

impl fmt::Debug for MemDir {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("MountPoints")
    }
}
