use crate::fmt;
use crate::hash::Hash;
use crate::io;
use crate::path::{Path, PathBuf};
use crate::sys::time::SystemTime;
use crate::sys::{cvt_io, run_with_path_str};

use yggdrasil_rt::io::{
    FileAttr as OsFileAttr, FileMode as OsFileMode, FileType as OsFileType, RawFd as OsRawFd,
};

mod dir;
mod file;

pub use dir::{readdir, remove_dir_all, rmdir, DirBuilder, DirEntry, ReadDir};
pub use file::{File, OpenOptions};

#[derive(Clone, Copy, PartialEq, Debug)]
pub struct FileAttr(OsFileAttr);

#[derive(Clone, Copy, PartialEq, Eq)]
pub struct FilePermissions(OsFileMode);

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct FileType(OsFileType);

#[derive(Copy, Clone, Debug, Default)]
pub struct FileTimes {}

impl FileAttr {
    pub fn size(&self) -> u64 {
        self.0.size
    }

    pub fn perm(&self) -> FilePermissions {
        FilePermissions(self.0.mode)
    }

    pub fn file_type(&self) -> FileType {
        FileType(self.0.ty)
    }

    pub fn mode_ext(&self) -> OsFileMode {
        self.0.mode
    }

    pub fn modified(&self) -> io::Result<SystemTime> {
        Err(crate::io::const_io_error!(
            crate::io::ErrorKind::Unsupported,
            "feature not implemented"
        ))
    }

    pub fn accessed(&self) -> io::Result<SystemTime> {
        Err(crate::io::const_io_error!(
            crate::io::ErrorKind::Unsupported,
            "feature not implemented"
        ))
    }

    pub fn created(&self) -> io::Result<SystemTime> {
        Err(crate::io::const_io_error!(
            crate::io::ErrorKind::Unsupported,
            "feature not implemented"
        ))
    }
}

impl FilePermissions {
    pub fn readonly(&self) -> bool {
        todo!()
    }

    pub fn set_readonly(&mut self, _readonly: bool) {
        todo!()
    }
}

impl fmt::Debug for FilePermissions {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&self.0.bits(), f)
    }
}

impl FileType {
    pub fn is_dir(&self) -> bool {
        self.0 == OsFileType::Directory
    }

    pub fn is_file(&self) -> bool {
        self.0 == OsFileType::File
    }

    pub fn is_symlink(&self) -> bool {
        // TODO: symlinks NYI
        false
    }
}

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

impl FileTimes {
    pub fn set_accessed(&mut self, _t: SystemTime) {
        todo!()
    }

    pub fn set_modified(&mut self, _t: SystemTime) {
        todo!()
    }
}

pub(self) fn get_metadata_inner(
    at: Option<OsRawFd>,
    path: &str,
    follow: bool,
) -> io::Result<FileAttr> {
    let mut metadata = crate::mem::MaybeUninit::uninit();
    cvt_io(unsafe { yggdrasil_rt::sys::get_metadata(at, path, &mut metadata, follow) })?;
    Ok(unsafe { FileAttr(metadata.assume_init()) })
}

pub fn unlink(path: &Path) -> io::Result<()> {
    run_with_path_str(path, |path_str| cvt_io(unsafe { yggdrasil_rt::sys::remove(None, path_str) }))
}

pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> {
    todo!()
}

pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
    todo!()
}

pub fn readlink(_p: &Path) -> io::Result<PathBuf> {
    todo!()
}

pub fn symlink(_original: &Path, _link: &Path) -> io::Result<()> {
    todo!()
}

pub fn link(_original: &Path, _link: &Path) -> io::Result<()> {
    todo!()
}

pub fn stat(path: &Path) -> io::Result<FileAttr> {
    run_with_path_str(path, |path_str| get_metadata_inner(None, path_str, true))
}

pub fn lstat(path: &Path) -> io::Result<FileAttr> {
    run_with_path_str(path, |path_str| get_metadata_inner(None, path_str, false))
}

pub fn canonicalize(_p: &Path) -> io::Result<PathBuf> {
    todo!()
}

pub fn copy(_from: &Path, _to: &Path) -> io::Result<u64> {
    todo!()
}

pub fn try_exists(_path: &Path) -> io::Result<bool> {
    todo!()
}
