use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::{
    fmt,
    ops::{Deref, DerefMut},
    path::{Path, PathBuf},
};

use crate::FILESYSTEM;

#[cfg(feature = "native")]
pub mod native;

#[cfg(feature = "web")]
pub mod web;

#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Serialize, Deserialize)]
pub struct Id(String);

impl Id {
    pub fn new() -> Self {
        Self(uuid::Uuid::new_v4().to_string())
    }
}

impl AsRef<str> for Id {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

impl Id {
    pub fn as_str(&self) -> &str {
        &self.0
    }

    pub fn from_path(path: &Path) -> Self {
        Self(path.file_name().unwrap().to_str().unwrap().to_string())
    }
}

impl fmt::Display for Id {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

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

#[derive(Debug)]
pub enum FileSystemError {
    FileNotFound(Id, String),
    DirectoryNotFound(PathBuf, String),
}

impl fmt::Display for FileSystemError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FileSystemError::FileNotFound(id, message) => {
                write!(f, "File not found: {} - {}", id, message)
            }
            FileSystemError::DirectoryNotFound(id, message) => {
                write!(f, "Directory not found: {} - {}", id.display(), message)
            }
        }
    }
}

impl std::error::Error for FileSystemError {}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileTree {
    pub name: String,
    pub path: PathBuf,
    pub is_directory: bool,
    pub id: Option<Id>,
    pub children: Vec<FileTree>,
}

pub trait FileSystem {
    fn new(root: impl AsRef<Path>) -> Self;

    fn create(&self, directory: &Path, data: impl Serialize) -> Result<Id, FileSystemError>;

    fn read<T: DeserializeOwned>(&self, id: &Id) -> Result<T, FileSystemError>;

    fn write(&self, id: &Id, data: impl Serialize) -> Result<(), FileSystemError>;

    fn borrow<T: DeserializeOwned + Serialize>(
        &self,
        id: &Id,
    ) -> Result<FileBorrow<T>, FileSystemError> {
        let file = self.read(id)?;
        Ok(FileBorrow {
            id: id.clone(),
            file,
        })
    }

    fn delete(&self, id: &Id) -> Result<(), FileSystemError>;

    fn clone(&self, id: &Id, new_id: &Id) -> Result<(), FileSystemError>;

    fn exists(&self, id: &Id) -> bool;

    fn lsdir(&self, id: &Id) -> Result<Vec<String>, FileSystemError>;

    fn file_tree(&self, root_path: &Path) -> Result<FileTree, FileSystemError>;
}

pub struct FileBorrow<T: DeserializeOwned + Serialize> {
    pub id: Id,
    pub file: T,
}

impl<T: DeserializeOwned + Serialize> Deref for FileBorrow<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.file
    }
}

impl<T: DeserializeOwned + Serialize> DerefMut for FileBorrow<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.file
    }
}

impl<T: DeserializeOwned + Serialize> Drop for FileBorrow<T> {
    fn drop(&mut self) {
        FILESYSTEM.write(&self.id, &self.file).unwrap();
    }
}
