use std::fs;
use std::path::{Path, PathBuf};

/// An iterator that yields paths to fasta or fastq files.
pub struct PathIter {
    dir_stack: Vec<PathBuf>,   // dossiers qu'il reste à explorer
    file_buffer: Vec<PathBuf>, // fichiers déjà listés (à distribuer)
}

impl PathIter {
    /// Creates a new `PathIter` that will yield paths to fasta or fastq files.
    ///
    /// For non-directory paths only the file extension is checked (no stat syscall).
    /// Directories are left on the stack and expanded lazily in `next()`.
    /// Invalid or missing paths are silently skipped when `open_chunks` fails later.
    pub fn new(paths: Vec<PathBuf>) -> Self {
        let mut iter = PathIter {
            dir_stack: Vec::new(),
            file_buffer: Vec::new(),
        };
        for path in paths {
            // "-" is the stdin sentinel — pass it through without any extension
            // check or directory expansion.
            if path.as_os_str() == "-" {
                iter.file_buffer.push(path);
            } else if is_fasta_or_fastq(&path) {
                // Avoid stat() at construction time on network filesystems (Lustre, NFS)
                // where metadata operations can be 100s of milliseconds each.
                iter.file_buffer.push(path);
            } else {
                iter.dir_stack.push(path);
            }
        }
        iter
    }
}

impl Iterator for PathIter {
    type Item = PathBuf;

    fn next(&mut self) -> Option<Self::Item> {
        // Tant qu'on n'a pas de fichier à donner
        while self.file_buffer.is_empty() {
            // Prendre un dossier dans la pile (s'il y en a)
            let dir = self.dir_stack.pop()?;
            // Lire le dossier
            let entries = match fs::read_dir(&dir) {
                Ok(entries) => entries,
                Err(_) => continue, // si erreur (perm, etc.), on ignore ce dossier
            };
            for entry in entries.flatten() {
                let path = entry.path();
                // Prefer file_type() over path.is_dir()/is_file() — on most filesystems
                // (including Lustre with DT_UNKNOWN fallback) this avoids an extra stat.
                let is_dir = entry.file_type().map(|t| t.is_dir()).unwrap_or(false);
                if is_dir {
                    self.dir_stack.push(path);
                } else if is_fasta_or_fastq(&path) {
                    self.file_buffer.push(path);
                }
            }
        }
        // Maintenant file_buffer n'est pas vide
        self.file_buffer.pop()
    }
}

/// Returns an iterator that yields paths to fasta or fastq files.
pub fn path_iter(paths: &[String]) -> PathIter {
    let path_bufs: Vec<PathBuf> = paths.iter().map(PathBuf::from).collect();
    PathIter::new(path_bufs)
}

/// Returns true if the path ends with a recognised sequence file extension.
fn is_fasta_or_fastq(path: &Path) -> bool {
    let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
    name.ends_with(".fasta")
        || name.ends_with(".fa")
        || name.ends_with(".fastq")
        || name.ends_with(".fq")
        || name.ends_with(".fasta.gz")
        || name.ends_with(".fa.gz")
        || name.ends_with(".fastq.gz")
        || name.ends_with(".fq.gz")
        || name.ends_with(".gbff")
        || name.ends_with(".gbk")
        || name.ends_with(".gb")
        || name.ends_with(".gbff.gz")
        || name.ends_with(".gbk.gz")
        || name.ends_with(".gb.gz")
}
