use std::{
    fs::ReadDir,
    sync::{Arc, Mutex},
};

use zip::ZipArchive;

use crate::{VfsError, buffer::VfsBuffer};

pub enum VfsListFiles {
    Working(ReadDir),
    ZipArchive {
        index: usize,
        prefix: Box<str>,
        zip: Arc<Mutex<ZipArchive<VfsBuffer>>>,
    },
}

impl VfsListFiles {
    fn next_working(iter: &mut ReadDir) -> Option<<Self as Iterator>::Item> {
        let mut name = None;
        while name.is_none() {
            match iter.next()?.map_err(Into::into) {
                Ok(dir) => match dir.file_type().map_err(Into::into) {
                    Ok(it) => {
                        if it.is_file() {
                            name = dir.file_name().to_str().map(Into::into);
                        }
                    }
                    Err(err) => return Some(Err(err)),
                },

                Err(err) => {
                    return Some(Err(err));
                }
            }
        }

        return Some(Ok(name?));
    }

    fn next_zip(
        index: &mut usize,
        prefix: &Box<str>,
        zip: &Arc<Mutex<ZipArchive<VfsBuffer>>>,
    ) -> Option<<Self as Iterator>::Item> {
        let Ok(mut zip) = zip.lock() else {
            return Some(Err(VfsError::Other));
        };

        while *index < zip.len() {
            let file = match zip.by_index(*index).map_err(Into::into) {
                Ok(file) => file,
                Err(err) => return Some(Err(err)),
            };
            *index += 1;

            let name = file.name();
            let prefix: &str = &prefix;
            if name.starts_with(prefix) {
                let remaining = &name[prefix.len()..];

                if !remaining.contains('/') && file.is_file() {
                    return Some(Ok(remaining.to_string()));
                }
            }
        }

        return None;
    }
}

impl Iterator for VfsListFiles {
    type Item = Result<String, VfsError>;

    fn next(&mut self) -> Option<Self::Item> {
        match self {
            Self::Working(iter) => Self::next_working(iter),

            Self::ZipArchive { index, prefix, zip } => Self::next_zip(index, prefix, zip),
        }
    }
}
