use std::{borrow::Borrow, error::Error, fmt, ops::Deref, str::FromStr};

use serde::{Deserialize, Serialize};

#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct VPath {
    path: str,
}

impl VPath {
    pub const SEPARATOR_BYTE: u8 = b'/';
    pub const SEPARATOR: char = Self::SEPARATOR_BYTE as char;
    pub const ROOT: &Self = unsafe { Self::new_unchecked("") };

    pub const fn try_new(s: &str) -> Result<&Self, InvalidPathError> {
        if s.is_empty() {
            return Ok(Self::ROOT);
        }

        let b = s.as_bytes();
        if b[b.len() - 1] == Self::SEPARATOR_BYTE {
            return Err(InvalidPathError::TrailingSlash);
        }
        if b[0] == Self::SEPARATOR_BYTE {
            return Err(InvalidPathError::LeadingSlash);
        }

        Ok(unsafe { Self::new_unchecked(s) })
    }

    pub fn new(s: &str) -> &Self {
        Self::try_new(s).expect("invalid path")
    }

    /// `const` version of [`new`][Self::new]. This has worse error messages, so prefer `new` whenever possible.
    pub const fn new_const(s: &str) -> &Self {
        match Self::try_new(s) {
            Ok(p) => p,
            Err(_) => panic!("invalid path"),
        }
    }

    const unsafe fn new_unchecked(s: &str) -> &Self {
        // SAFETY: The representation of &str and &VPath is the same.
        unsafe { std::mem::transmute::<_, &Self>(s) }
    }

    pub fn is_empty(&self) -> bool {
        self.path.is_empty()
    }

    pub fn is_root(&self) -> bool {
        self.is_empty()
    }

    pub fn join(&self, sub: &VPath) -> VPathBuf {
        let mut buf = self.to_owned();
        buf.push(sub);
        buf
    }

    pub fn parent(&self) -> Option<&VPath> {
        if self.is_root() {
            None
        } else if self.depth() == 0 {
            Some(VPath::ROOT)
        } else {
            let (left, _right) = self
                .path
                .split_once(Self::SEPARATOR)
                .expect("path with depth > 0 must have separators");
            // SAFETY: We're splitting on a `/`, so there cannot be a trailing `/` in `left`.
            Some(unsafe { VPath::new_unchecked(left) })
        }
    }

    pub fn strip_prefix(&self, prefix: &VPath) -> Option<&Self> {
        if self == prefix {
            Some(VPath::ROOT)
        } else {
            self.path
                .strip_prefix(&prefix.path)
                .and_then(|p| p.strip_prefix(Self::SEPARATOR))
                // SAFETY: If `self` starts with `prefix`, `p` will end up not being prefixed by `self`
                // nor a leading slash.
                .map(|p| unsafe { VPath::new_unchecked(p) })
        }
    }

    pub fn depth(&self) -> usize {
        self.path.chars().filter(|&c| c == Self::SEPARATOR).count()
    }

    pub fn segments(&self) -> impl Iterator<Item = &Self> {
        if self.is_root() {
            None.into_iter().flatten()
        } else {
            Some(self.as_str().split(Self::SEPARATOR).map(|s| unsafe {
                // SAFETY: Since we're splitting on the separator, the path cannot start or end with it.
                Self::new_unchecked(s)
            }))
            .into_iter()
            .flatten()
        }
    }

    pub fn rsegments(&self) -> impl Iterator<Item = &Self> {
        self.as_str().rsplit(Self::SEPARATOR).map(|s| unsafe {
            // SAFETY: Since we're splitting on the separator, the path cannot start or end with it.
            Self::new_unchecked(s)
        })
    }

    pub fn file_name(&self) -> Option<&str> {
        self.rsegments().next().map(Self::as_str)
    }

    pub fn extension(&self) -> Option<&str> {
        let file_name = self.file_name()?;
        let (left, right) = file_name.rsplit_once('.')?;
        if left.is_empty() { None } else { Some(right) }
    }

    pub fn with_extension(&self, extension: &str) -> VPathBuf {
        let mut buf = self.to_owned();
        buf.set_extension(extension);
        buf
    }

    pub fn file_stem(&self) -> Option<&str> {
        let file_name = self.file_name()?;
        if let Some(extension) = self.extension() {
            Some(&file_name[..file_name.len() - extension.len() - 1])
        } else {
            Some(file_name)
        }
    }

    pub fn as_str(&self) -> &str {
        &self.path
    }
}

impl ToOwned for VPath {
    type Owned = VPathBuf;

    fn to_owned(&self) -> Self::Owned {
        VPathBuf::from(self)
    }
}

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

impl fmt::Display for VPath {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.path)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvalidPathError {
    TrailingSlash,
    LeadingSlash,
}

impl fmt::Display for InvalidPathError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            InvalidPathError::TrailingSlash => {
                f.write_str("paths must not end with a trailing `/`")
            }
            InvalidPathError::LeadingSlash => {
                f.write_str("paths are always absolute and must not start with `/`")
            }
        }
    }
}

impl Error for InvalidPathError {}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VPathBuf {
    path: String,
}

impl VPathBuf {
    pub fn new(path: impl Into<String>) -> Self {
        Self::try_new(path).expect("invalid path")
    }

    pub fn try_new(path: impl Into<String>) -> Result<Self, InvalidPathError> {
        let path = path.into();
        match VPath::try_new(&path) {
            Ok(_) => Ok(Self { path }),
            Err(e) => Err(e),
        }
    }

    unsafe fn new_unchecked(path: String) -> Self {
        Self { path }
    }

    pub fn push(&mut self, sub: &VPath) {
        if !sub.is_empty() {
            if !self.is_empty() {
                self.path.push('/');
            }
            self.path.push_str(&sub.path);
        }
    }

    pub fn set_extension(&mut self, new_extension: &str) {
        if let Some(existing) = self.extension() {
            let mut chop_len = existing.len();
            if new_extension.is_empty() {
                chop_len += 1; // also chop off the `.`
            }

            let range = self.path.len() - chop_len..;
            self.path.replace_range(range, new_extension);
        } else if !new_extension.is_empty() {
            self.path.push('.');
            self.path.push_str(new_extension);
        }
    }
}

impl Default for VPathBuf {
    fn default() -> Self {
        VPath::ROOT.to_owned()
    }
}

impl Deref for VPathBuf {
    type Target = VPath;

    fn deref(&self) -> &Self::Target {
        unsafe { VPath::new_unchecked(&self.path) }
    }
}

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

impl fmt::Display for VPathBuf {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.path)
    }
}

impl From<&VPath> for VPathBuf {
    fn from(value: &VPath) -> Self {
        unsafe { Self::new_unchecked(value.path.to_owned()) }
    }
}

impl Borrow<VPath> for VPathBuf {
    fn borrow(&self) -> &VPath {
        self
    }
}

impl<'de> Deserialize<'de> for VPathBuf {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        use serde::de;

        struct Visitor;

        impl de::Visitor<'_> for Visitor {
            type Value = VPathBuf;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("virtual path")
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                VPathBuf::try_new(v).map_err(de::Error::custom)
            }
        }

        deserializer.deserialize_str(Visitor)
    }
}

impl Serialize for VPathBuf {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(self.as_str())
    }
}

impl FromStr for VPathBuf {
    type Err = InvalidPathError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::try_new(s)
    }
}
