use {
    crate::*,
    lazy_regex::regex_captures,
    std::{
        fmt,
        path::{
            Path,
            PathBuf,
        },
        str::FromStr,
    },
};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PagePath {
    pub dir: Vec<String>,
    pub stem: String,
}

impl PagePath {
    pub fn depth(&self) -> usize {
        if self.is_root_index() {
            0
        } else {
            1 + self.dir.len()
        }
    }
    pub fn to_path_buf(
        &self,
        parent: &Path,
        ext: Option<&str>,
    ) -> PathBuf {
        let mut path = parent.to_owned();
        for d in &self.dir {
            path.push(d);
        }
        let file_name = match ext {
            Some(e) => format!("{}.{}", self.stem, e),
            None => self.stem.clone(),
        };
        path.push(file_name);
        path
    }
    pub fn md_path_buf(
        &self,
        parent: &Path,
    ) -> PathBuf {
        let mut path = parent.to_owned();
        for d in &self.dir {
            path.push(d);
        }
        path.push(format!("{}.md", self.stem));
        path
    }
    pub fn html_path_buf(
        &self,
        parent: &Path,
    ) -> PathBuf {
        let mut path = parent.to_owned();
        if !self.is_root_index() {
            for d in &self.dir {
                path.push(d);
            }
            path.push(&self.stem);
        }
        path.push("index.html");
        path
    }
    pub fn is_root_index(&self) -> bool {
        self.dir.is_empty() && self.stem == "index"
    }
    /// Returns a relative link from this PagePath to another PagePath
    pub fn link_to(
        &self,
        other: &PagePath,
    ) -> String {
        let mut path = String::new();
        for _ in 0..self.depth() {
            path.push_str("../");
        }
        if !other.is_root_index() {
            for d in &other.dir {
                path.push_str(d);
                path.push('/');
            }
            path.push_str(&other.stem);
            path.push('/');
        }
        path
    }
}

impl FromStr for PagePath {
    type Err = DdError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let Some((_, dir, stem)) = regex_captures!(r"^([\w-]+/)*([\w-]+)(?:\.md)?$", s,) else {
            return Err(DdError::InvalidPagePath { path: s.to_owned() });
        };
        let dir = dir
            .trim_end_matches('/')
            .split('/')
            .filter(|part| !part.is_empty())
            .map(|s| s.to_owned())
            .collect();
        let stem = stem.to_owned();
        Ok(Self { dir, stem })
    }
}

impl fmt::Display for PagePath {
    fn fmt(
        &self,
        f: &mut fmt::Formatter<'_>,
    ) -> fmt::Result {
        for d in &self.dir {
            write!(f, "{}/", d)?;
        }
        write!(f, "{}", self.stem)
    }
}

impl serde::Serialize for PagePath {
    fn serialize<S: serde::Serializer>(
        &self,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(&self.to_string())
    }
}
impl<'de> serde::Deserialize<'de> for PagePath {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let s = String::deserialize(deserializer)?;
        s.parse().map_err(serde::de::Error::custom)
    }
}
