use std::{
    collections::HashSet,
    fs::{self, Metadata},
    path::Path,
};

#[derive(Default, Debug, Clone, Copy)]
pub struct CompletionFilter {
    pub only_executables: bool,
}

fn is_executable(meta: &Metadata) -> bool {
    #[cfg(any(rust_analyzer, unix))]
    {
        use std::os::unix::fs::MetadataExt;

        meta.mode() & 0o111 != 0
    }
    #[cfg(any(rust_analyzer, target_os = "yggdrasil"))]
    {
        use std::os::yggdrasil::fs::MetadataExt;

        u32::from(meta.mode_ext()) & 0o111 != 0
    }
}

pub fn complete_from_directory<P: AsRef<Path>>(
    entries: &mut HashSet<String>,
    directory: P,
    prefix: &str,
    filter: &CompletionFilter,
) {
    let Ok(dir) = fs::read_dir(directory) else {
        return;
    };
    for entry in dir {
        let Ok(entry) = entry else {
            continue;
        };
        let filename = entry.file_name();
        let Some(filename) = filename.to_str() else {
            continue;
        };
        if filename == "." || filename == ".." {
            continue;
        }
        if !filename.starts_with(prefix) {
            continue;
        }

        if filter.only_executables {
            let Ok(stat) = entry.metadata() else {
                continue;
            };

            if !is_executable(&stat) {
                continue;
            }
        }

        entries.insert(filename.into());
    }
}

pub fn complete_from_cwd(entries: &mut HashSet<String>, prefix: &str, filter: &CompletionFilter) {
    complete_from_directory(entries, ".", prefix, filter);
}

pub fn complete_binary<S: AsRef<Path>, I: IntoIterator<Item = S>>(
    entries: &mut HashSet<String>,
    bin_paths: I,
    prefix: &str,
) {
    let filter = CompletionFilter {
        only_executables: true,
    };
    for bin_path in bin_paths {
        let bin_path = bin_path.as_ref();
        complete_from_directory(entries, bin_path, prefix, &filter);
    }
}

pub fn complete_binary_from_env(entries: &mut HashSet<String>, prefix: &str) {
    let Ok(env_path) = std::env::var("PATH") else {
        return;
    };

    let it = env_path.split(':').map(str::trim);
    complete_binary(entries, it, prefix);
}
