use crate::*;
use std::path::{Path, PathBuf};
use std::{env, fs};

pub struct Suggestion {
    pub display: BString,
    pub delta: BString,
}

fn _path_completion(mut prefix: BString) -> io::Result<Vec<Suggestion>> {
    let mut partial_entry = BString::new();
    while let Some(c) = prefix.last().cloned() {
        if c == b'/' {
            break;
        }
        partial_entry.push(c);
        prefix.pop();
    }
    partial_entry.reverse();

    let mut sugs = Vec::new();

    if prefix.is_empty() {
        prefix.push(b'.');
    }

    for entry in fs::read_dir(OsStr::from_bytes(&prefix))? {
        let entry = entry?;
        let name = entry.file_name().as_bytes().to_vec();
        if name.starts_with(&partial_entry) {
            let mut delta = name[partial_entry.len()..].to_vec();

            let is_dir = entry.metadata().map(|m| m.is_dir()).unwrap_or(false);
            if is_dir {
                delta.push(b'/');
            } else {
                delta.push(b' ');
            }

            sugs.push(Suggestion {
                display: name,
                delta,
            });
        }
    }

    Ok(sugs)
}

pub fn path_completion(prefix: BString) -> Vec<Suggestion> {
    match _path_completion(prefix) {
        Ok(suggestions) => suggestions,
        Err(err) => {
            println!("path completion failed: {err:?}\r");
            Vec::new()
        }
    }
}

pub fn variable_completion(session: Arc<Mutex<Session>>, prefix: BString) -> Vec<Suggestion> {
    let se = session.lock().unwrap();
    let mut out = Vec::new();
    for var in se.vars.keys() {
        if var.starts_with(&prefix) {
            out.push(Suggestion {
                display: var.to_vec(),
                delta: var[prefix.len()..].to_vec(),
            });
        }
    }
    drop(se);
    for var in env::vars_os() {
        let var = var.0.as_bytes();
        if var.starts_with(&prefix) {
            out.push(Suggestion {
                display: var.to_vec(),
                delta: var[prefix.len()..].to_vec(),
            });
        }
    }
    out
}

#[derive(Default)]
pub struct PathCache {
    binaries: HashMap<BString, PathBuf>,
}

fn is_executable(path: &Path) -> bool {
    use std::os::unix::fs::PermissionsExt;
    fs::metadata(path)
        .map(|m| m.permissions().mode() & 0o111 != 0)
        .unwrap_or(false)
}

pub fn populate_path_cache(session: Arc<Mutex<Session>>) {
    let path_var = env::var_os("PATH").unwrap();

    let mut binaries = HashMap::new();

    for dir in env::split_paths(&path_var) {
        if let Ok(entries) = fs::read_dir(&dir) {
            for entry in entries.flatten() {
                let path = entry.path();
                if path.is_file() && is_executable(&path) {
                    binaries.insert(path.file_name().unwrap().as_bytes().to_vec(), path);
                }
            }
        }
    }

    session.lock().unwrap().path_cache = PathCache { binaries };
}

pub fn command_completion(session: Arc<Mutex<Session>>, prefix: BString) -> Vec<Suggestion> {
    let se = session.lock().unwrap();
    let mut out = Vec::new();
    for fun in se
        .funs
        .keys()
        .chain(se.builtins.keys())
        .chain(se.path_cache.binaries.keys())
    {
        if fun.starts_with(&prefix) {
            out.push(Suggestion {
                display: fun.to_vec(),
                delta: fun[prefix.len()..].to_vec(),
            })
        }
    }

    for s in out.iter_mut() {
        s.delta.push(b' ');
    }

    out
}
