#[allow(warnings)]
///
///## useage of fs module
///```ignore
///fn main() {
///    use doe::*;
///    use doe::DebugPrint;
///    use doe::Str;
///    use std::ops::Deref;
///    // append data to file
///    append_data_to_file("demo.txt", "demo".as_bytes().to_vec()).unwrap();
///
///    // get all files and folders
///    walk_dir(".".to_path().deref()).unwrap().dprintln();
///
///    // get all folders
///    walk_dir_get_files(".".to_path().deref()).unwrap().dprintln();
///
///    // get all folders
///    walk_dir_get_folders(".".to_path().deref())
///        .unwrap()
///        .dprintln();
///
///    //move file the directory to a new directory
///    move_file(
///        "/Users/ryanandrew/code/test/t1/demo.zip".to_path().deref(),
///        "/Users/ryanandrew/code/test/t2/d2.zip".to_path().deref(),
///    )
///    .unwrap();
///
///    //copy file the directory to a new directory
///    copy_file(
///        "/Users/ryanandrew/code/test/t1/demo.zip".to_path().deref(),
///        "/Users/ryanandrew/code/test/t2/d2.zip".to_path().deref(),
///    )
///    .unwrap();
///    //move all files in the directory to a new directory
///    move_folder(
///        "/Users/ryanandrew/code/test/t1".to_path().deref(),
///        "/Users/ryanandrew/code/test/t2/t1".to_path().deref(),
///    )
///    .unwrap();
///
///    //copy all files in the directory to a new directory
///    copy_folder(
///        "/Users/ryanandrew/code/test/d1".to_path().deref(),
///        "/Users/ryanandrew/code/test/d2".to_path().deref(),
///    )
///    .unwrap();
///}
///```
pub mod fs {
    use std::ffi::OsStr;
    use std::fs::{self, File, Metadata, OpenOptions};
    use std::io::{self, Read, Seek, Write};
    use std::path::{Path, PathBuf};


    ///append data to file
    pub fn append_data<T: AsRef<[u8]>, P: AsRef<Path>>(path: P, data: T) -> io::Result<()> {
        let mut file = OpenOptions::new().create(true).append(true).open(path)?;
        file.write_all(data.as_ref())?;
        Ok(())
    }

    ///append data to file
    pub fn append_data_to_file<T: AsRef<[u8]>, P: AsRef<Path>>(path: P, data: T) -> io::Result<()> {
        let mut file = OpenOptions::new().create(true).append(true).open(path)?;
        file.write_all(data.as_ref())?;
        Ok(())
    }

    /// get all files
    pub fn walk_dir_get_files(path: impl ToString) -> io::Result<Vec<PathBuf>> {
        let path = path.to_string();
        let mut files = walk_dir(path)?;
        Ok(files.into_iter().filter(|s| s.is_file()).collect())
    }

    /// get all folders
    pub fn walk_dir_get_folders(path: impl ToString) -> io::Result<Vec<PathBuf>> {
        let path = path.to_string();
        let mut files = walk_dir(path)?;
        Ok(files.into_iter().filter(|s| s.is_dir()).collect())
    }

    /// get all files and folders
    pub fn walk_dir(path: impl ToString) -> io::Result<Vec<PathBuf>> {
        let path = path.to_string();
        let dir = Path::new(&path);
        let mut files = Vec::new();
        for entry in fs::read_dir(dir)? {
            let entry = entry?;
            let newpath = entry.path();
            if newpath.is_dir() {
                files.extend(walk_dir(&newpath.display())?);
                files.push(newpath);
            } else {
                files.push(newpath);
            }
        }
        Ok(files)
    }
    /// move all file in the directory to a new directory
    pub fn move_file(from: &Path, to: &Path) -> io::Result<()> {
        fs::rename(from, to)?;
        Ok(())
    }
    /// copy all file in the directory to a new directory
    pub fn copy_file(from: &Path, to: &Path) -> io::Result<()> {
        fs::copy(from, to)?;
        Ok(())
    }
    /// move all files in the directory to a new directory
    pub fn move_folder(from: &Path, to: &Path) -> io::Result<()> {
        fs::create_dir_all(to)?;
        let entries = fs::read_dir(from)?;
        for entry in entries {
            let entry = entry?;
            let source_path = entry.path();
            let file_name = entry.file_name();
            let target_path = to.join(file_name);
            // move
            fs::rename(source_path, target_path)?;
        }
        std::fs::remove_dir_all(from)?;

        Ok(())
    }
    /// Copy all files and directories in the directory to a new directory
    pub fn copy_folder(from: &Path, to: &Path) -> io::Result<()> {
        // Create the destination directory if it doesn't exist
        fs::create_dir_all(to)?;

        // Iterate over the entries in the source directory
        for entry in fs::read_dir(from)? {
            let entry = entry?;
            let entry_path = entry.path();

            // Get the destination path by joining the entry's file name with the destination directory
            let dest_path = to.join(entry.file_name());

            // Check if the entry is a file or a directory
            if entry_path.is_file() {
                // Copy the file to the destination directory
                fs::copy(&entry_path, &dest_path)?;
            } else if entry_path.is_dir() {
                // Recursively copy the directory to the destination directory
                copy_folder(&entry_path, &dest_path)?;
            }
        }
        Ok(())
    }
    ///get Home directory
    /// ```rust
    ///     use doe::home_dir;
    ///     use doe::DebugPrint;
    ///      home_dir().dprintln();
    /// ```
    pub fn home_dir() -> Option<PathBuf> {
        if let Some(home_dir) = std::env::var("HOME").ok().map(PathBuf::from) {
            Some(home_dir)
        } else {
            None
        }
    }

    /// Returns metadata for a file (follows symlinks).
    ///
    /// Equivalent to [`std::fs::metadata`] with better error messages.
    pub fn metadata<P: AsRef<Path>>(path: P) -> Result<Metadata, String> {
        let path = path.as_ref();
        std::fs::metadata(path).map_err(|r| format!("failed to get metadata for"))
    }

    /// Equivalent to [`write()`], but does not write anything if the file contents
    /// are identical to the given contents.
    pub fn write_if_changed<P: AsRef<Path>, C: AsRef<[u8]>>(
        path: P,
        contents: C,
    ) -> Result<(), String> {
        let contents = contents.as_ref();
        let mut f = OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open(&path)
            .unwrap();
        let mut orig = Vec::new();
        f.read_to_end(&mut orig).unwrap();
        if orig != contents {
            f.set_len(0).unwrap();
            f.seek(io::SeekFrom::Start(0)).unwrap();
            f.write_all(contents).unwrap();
        }
        Ok(())
    }

    /// Converts a path to UTF-8 bytes.
    pub fn path_to_bytes(path: &Path) -> Result<&[u8], String> {
        #[cfg(unix)]
        {
            use std::os::unix::prelude::*;
            Ok(path.as_os_str().as_bytes())
        }
        #[cfg(windows)]
        {
            match path.as_os_str().to_str() {
                Some(s) => Ok(s.as_bytes()),
                None => Err(format!("invalid non-unicode path: {}", path.display())),
            }
        }
    }

    /// Converts UTF-8 bytes to a path.
    pub fn bytes_to_path(bytes: &[u8]) -> Result<PathBuf, String> {
        #[cfg(unix)]
        {
            use std::os::unix::prelude::*;
            Ok(PathBuf::from(OsStr::from_bytes(bytes)))
        }
        #[cfg(windows)]
        {
            use std::str;
            match str::from_utf8(bytes) {
                Ok(s) => Ok(PathBuf::from(s)),
                Err(..) => Err(format!("invalid non-unicode path")),
            }
        }
    }

    pub fn set_not_readonly(p: &Path) -> io::Result<bool> {
        let mut perms = p.metadata()?.permissions();
        if !perms.readonly() {
            return Ok(false);
        }
        perms.set_readonly(false);
        fs::set_permissions(p, perms)?;
        Ok(true)
    }
}
pub use fs::*;
