use alloc::{string::ToString, sync::Arc};
use core::{
    fmt,
    str::{self, FromStr},
};
use crate::{
    ast::Ident, diagnostics::Diagnostic, ByteReader, ByteWriter, Deserializable,
    DeserializationError, LibraryPath, Serializable, Span,
};
#[derive(Debug, thiserror::Error, Diagnostic, PartialEq, Eq)]
pub enum LibraryNamespaceError {
    #[error("invalid library namespace name: cannot be a empty")]
    #[diagnostic()]
    Empty,
    #[error("invalid library namespace name: too many characters")]
    #[diagnostic()]
    Length,
    #[error("invalid character in library namespace: expected lowercase ascii-alphanumeric character or '_'")]
    #[diagnostic()]
    InvalidChars,
    #[error(
        "invalid library namespace name: must start with lowercase ascii-alphabetic character"
    )]
    #[diagnostic()]
    InvalidStart,
}
#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum LibraryNamespace {
    Kernel = 0,
    Exec,
    #[default]
    Anon,
    User(Arc<str>),
}
impl LibraryNamespace {
    pub const MAX_LENGTH: usize = u8::MAX as usize;
    pub const KERNEL_PATH: &'static str = "#sys";
    pub const EXEC_PATH: &'static str = "#exec";
    pub const ANON_PATH: &'static str = "#anon";
}
impl LibraryNamespace {
    pub fn new<S>(source: S) -> Result<Self, LibraryNamespaceError>
    where
        S: AsRef<str>,
    {
        source.as_ref().parse()
    }
    pub fn from_ident_unchecked(name: Ident) -> Self {
        match name.as_str() {
            Self::KERNEL_PATH => Self::Kernel,
            Self::EXEC_PATH => Self::Exec,
            Self::ANON_PATH => Self::Anon,
            _ => Self::User(name.into_inner()),
        }
    }
    pub fn strip_path_prefix(path: &str) -> Result<(Self, &str), LibraryNamespaceError> {
        match path.split_once("::") {
            Some((ns, rest)) => ns.parse().map(|ns| (ns, rest)),
            None => path.parse().map(|ns| (ns, "")),
        }
    }
}
impl LibraryNamespace {
    pub fn is_reserved(&self) -> bool {
        !matches!(self, Self::User(_))
    }
    pub fn validate(source: impl AsRef<str>) -> Result<(), LibraryNamespaceError> {
        let source = source.as_ref();
        if source.is_empty() {
            return Err(LibraryNamespaceError::Empty);
        }
        if matches!(source, Self::KERNEL_PATH | Self::EXEC_PATH | Self::ANON_PATH) {
            return Ok(());
        }
        if source.as_bytes().len() > Self::MAX_LENGTH {
            return Err(LibraryNamespaceError::Length);
        }
        if !source.starts_with(|c: char| c.is_ascii_lowercase() && c.is_ascii_alphabetic()) {
            return Err(LibraryNamespaceError::InvalidStart);
        }
        if !source.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '_')) {
            return Err(LibraryNamespaceError::InvalidChars);
        }
        Ok(())
    }
}
impl LibraryNamespace {
    pub fn as_str(&self) -> &str {
        match self {
            Self::Kernel => Self::KERNEL_PATH,
            Self::Exec => Self::EXEC_PATH,
            Self::Anon => Self::ANON_PATH,
            Self::User(ref path) => path,
        }
    }
    pub fn as_refcounted_str(&self) -> Arc<str> {
        match self {
            Self::User(ref path) => path.clone(),
            other => Arc::from(other.as_str().to_string().into_boxed_str()),
        }
    }
    pub fn to_path(&self) -> LibraryPath {
        LibraryPath::from(self.clone())
    }
    pub fn to_ident(&self) -> Ident {
        Ident::new_unchecked(Span::unknown(self.as_refcounted_str()))
    }
}
impl core::ops::Deref for LibraryNamespace {
    type Target = str;
    fn deref(&self) -> &Self::Target {
        self.as_str()
    }
}
impl AsRef<str> for LibraryNamespace {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}
impl fmt::Display for LibraryNamespace {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(self.as_str())
    }
}
impl FromStr for LibraryNamespace {
    type Err = LibraryNamespaceError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            Self::KERNEL_PATH => Ok(Self::Kernel),
            Self::EXEC_PATH => Ok(Self::Exec),
            Self::ANON_PATH => Ok(Self::Anon),
            other => {
                Self::validate(other)?;
                Ok(Self::User(Arc::from(other.to_string().into_boxed_str())))
            },
        }
    }
}
impl TryFrom<Ident> for LibraryNamespace {
    type Error = LibraryNamespaceError;
    fn try_from(ident: Ident) -> Result<Self, Self::Error> {
        match ident.as_str() {
            Self::KERNEL_PATH => Ok(Self::Kernel),
            Self::EXEC_PATH => Ok(Self::Exec),
            Self::ANON_PATH => Ok(Self::Anon),
            other => Self::new(other),
        }
    }
}
impl Serializable for LibraryNamespace {
    fn write_into<W: ByteWriter>(&self, target: &mut W) {
        let bytes = self.as_bytes();
        assert!(bytes.len() <= u8::MAX as usize, "namespace too long");
        target.write_u8(bytes.len() as u8);
        target.write_bytes(bytes);
    }
}
impl Deserializable for LibraryNamespace {
    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
        let num_bytes = source.read_u8()? as usize;
        let name = source.read_slice(num_bytes)?;
        let name =
            str::from_utf8(name).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
        Self::new(name).map_err(|e| DeserializationError::InvalidValue(e.to_string()))
    }
}