#![allow(non_camel_case_types)]

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

use super::Builtin;
use crate::*;

pub struct cd;
impl Builtin for cd {
    fn name(&self) -> &str {
        "cd"
    }
    fn mod_session(&self, se: &mut Session, args: &[BString]) {
        let mut dir = match current_dir() {
            Ok(path) => path.as_os_str().as_bytes().to_vec(),
            Err(_) => vec![b'.'],
        };

        std::mem::swap(&mut dir, &mut se.prev_path);

        match args.get(0).map(|v| &v[..]) {
            Some(b"-") => {
                let _ = set_current_dir(OsStr::from_bytes(&dir));
            }
            Some(path) => {
                let _ = set_current_dir(OsStr::from_bytes(path));
            }
            None => {
                if let Some(home) = std::env::var_os("HOME") {
                    let _ = set_current_dir(home);
                }
            }
        }

        // TODO: let mod_session builtins return nonzero exit code
    }
}

pub struct clear;
impl Builtin for clear {
    fn name(&self) -> &str {
        "clear"
    }
    fn mod_session(&self, _: &mut Session, _: &[BString]) {
        print!("\x1B[2J\x1B[1;1H");
    }
}

/// restart shell
pub struct re;
impl Builtin for re {
    fn name(&self) -> &str {
        "re"
    }

    fn mod_session(&self, session: &mut Session, _args: &[BString]) {
        session.raw.disable();
        crate::reload::begin_reload();
        session.raw.enable(); // something went wrong, let's restore raw mode
    }
}

pub struct Sink {
    name: &'static str,
    append: bool,
}

impl Builtin for Sink {
    fn name(&self) -> &str {
        self.name
    }

    fn io(
        &self,
        args: &[BString],
        stdin: &mut dyn Read,
        _stdout: &mut dyn Write,
    ) -> std::io::Result<()> {
        let Some(path) = args.get(0) else {
            // TODO exit code
            return Ok(());
        };
        let path = PathBuf::from(OsStr::from_bytes(path));
        let mut file = OpenOptions::new()
            .write(true)
            .create(true)
            .append(self.append)
            .open(path)?;
        std::io::copy(stdin, &mut file)?;
        Ok(())
    }
}

pub const fn sink(name: &'static str, append: bool) -> Sink {
    Sink { name, append }
}

// TODO
// from" => todo!("read from file"),

pub struct from;
impl Builtin for from {
    fn name(&self) -> &str {
        "from"
    }

    fn io(
        &self,
        args: &[BString],
        _stdin: &mut dyn Read,
        stdout: &mut dyn Write,
    ) -> std::io::Result<()> {
        let Some(path) = args.get(0) else {
            // TODO exit code
            return Ok(());
        };
        let path = PathBuf::from(OsStr::from_bytes(path));
        let mut file = OpenOptions::new().read(true).open(path)?;
        std::io::copy(&mut file, stdout)?;
        Ok(())
    }
}

pub struct _type;
impl Builtin for _type {
    fn name(&self) -> &str {
        "type"
    }

    fn io(
        &self,
        args: &[BString],
        _stdin: &mut dyn Read,
        stdout: &mut dyn Write,
    ) -> std::io::Result<()> {
        for arg in args {
            let kind = "todo";
            writeln!(stdout, "{} is {}", String::from_utf8_lossy(arg), kind)?;
        }

        Ok(())
    }
}

pub struct builtins;
impl Builtin for builtins {
    fn name(&self) -> &str {
        "builtins"
    }

    fn io(
        &self,
        _args: &[BString],
        _stdin: &mut dyn Read,
        stdout: &mut dyn Write,
    ) -> std::io::Result<()> {
        for b in super::BUILTINS {
            write!(stdout, "{} ", b.name())?;
        }
        writeln!(stdout)
    }
}

pub struct history;
impl Builtin for history {
    fn name(&self) -> &str {
        "history"
    }

    fn io(
        &self,
        _args: &[BString],
        _stdin: &mut dyn Read,
        _stdout: &mut dyn Write,
    ) -> std::io::Result<()> {
        // TODO: add location & time to history to allow better querying
        todo!("need session")
    }
}
