#![feature(
    if_let_guard,
    iter_chain,
    anonymous_pipe,
    trait_alias,
    exitcode_exit_method,
    let_chains
)]
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
#![allow(clippy::new_without_default, clippy::should_implement_trait)]

use std::{
    fs::{self, File},
    io::{self, stdout, BufRead, BufReader, Write},
    path::Path,
    process::ExitCode,
};

use clap::Parser;
use command::env::Environment;
use cross::{
    io::{TerminalOptions, TerminalOptionsImpl},
    term::TerminalInput,
};
use error::Error;
use exec::Outcome;
use stuff::readline::{self, ReadlineProvider};

pub mod builtin;
pub mod command;
pub mod error;
pub mod exec;
pub mod syntax;

#[derive(Debug, Parser)]
pub struct ShellArgs {
    #[arg(short)]
    command: Option<String>,
    #[arg(short)]
    login: bool,
    script: Option<String>,
    args: Vec<String>,
}

pub enum ShellInput {
    File(BufReader<File>),
    Interactive(TerminalInput),
}

pub struct ReadlineProviderImpl {}

fn list_cwd_files() -> io::Result<Vec<String>> {
    let dir = fs::read_dir(".")?;
    let mut names = vec![];
    for entry in dir {
        let Ok(entry) = entry else {
            continue;
        };

        let filename = entry.file_name();
        let filename = filename.to_str().unwrap();
        if filename.starts_with(".") {
            continue;
        }

        names.push(filename.into());
    }

    Ok(names)
}

impl ReadlineProvider for ReadlineProviderImpl {
    fn prompt(&mut self, stdout: &mut io::Stdout) {
        let cwd = match std::env::current_dir() {
            Ok(cwd) => format!("{}", cwd.display()),
            Err(_) => "???".into(),
        };

        stdout.write_all(format!("{cwd} $ ").as_bytes()).ok();
    }

    fn clear_screen(&mut self, stdout: &mut io::Stdout) {
        stdout.write_all(b"\x1B[H\x1B[J").ok();
    }

    fn completions(&mut self, raw_command: &str) -> Vec<String> {
        let (is_command, last_word) = match raw_command.rsplit_once(' ') {
            Some((_, last)) => (false, last),
            None => (true, raw_command.trim()),
        };

        if is_command {
            // Lookup command completion
            vec![]
        } else {
            match list_cwd_files() {
                Ok(list) => list
                    .into_iter()
                    .filter(|e| e.starts_with(last_word))
                    .collect(),
                Err(error) => {
                    log::error!("Completion error: {error}");
                    vec![]
                }
            }
        }
    }
}

impl ShellInput {
    pub fn read_line(
        &mut self,
        readline_provider: &mut ReadlineProviderImpl,
        line: &mut String,
        _continuation: bool,
    ) -> Result<usize, Error> {
        match self {
            Self::File(file) => Ok(file.read_line(line)?),
            Self::Interactive(stdin) => {
                let mut stdout = stdout();

                readline::readline(stdin, &mut stdout, line, readline_provider).map_err(Error::from)
                // readline::readline(stdin, &mut stdout, line, |stdout| {
                //     let prompt = if !continuation {
                //         let cwd = std::env::current_dir();
                //         let cwd = match cwd {
                //             Ok(cwd) => format!("{}", cwd.display()),
                //             Err(_) => "???".into(),
                //         };
                //         format!("{cwd} $ ")
                //     } else {
                //         "> ".to_owned()
                //     };
                //     stdout.write_all(prompt.as_bytes()).ok();
                // })
                // .map_err(Error::from)
            }
        }
    }
}

fn run_single(_env: &Environment, _command: &str) -> Outcome {
    todo!()
    // let line = command.trim();

    // let command = todo!();
    // let command = match parse_interactive(line) {
    //     Ok(c) => c,
    //     Err(e) => {
    //         eprintln!("Syntax error: {e}");
    //         return Outcome::err();
    //     }
    // };
    // let command = match command.expand(env) {
    //     Ok(c) => c,
    //     Err(e) => {
    //         eprintln!("{e}");
    //         return Outcome::err();
    //     }
    // };
    // let (outcome, exit) = match exec::eval(command) {
    //     Ok(res) => res,
    //     Err(error) => {
    //         eprintln!("{error}");
    //         return Outcome::err();
    //     }
    // };

    // if let Some(exit) = exit {
    //     exit.exit_process();
    // }

    // outcome
}

fn run(mut input: ShellInput, env: &mut Environment) -> Result<(), Error> {
    let mut command_text = String::new();
    let mut line = String::new();
    let mut readline_provider = ReadlineProviderImpl {};
    loop {
        line.clear();
        let len = input.read_line(&mut readline_provider, &mut line, !command_text.is_empty())?;
        if len == 0 {
            break Ok(());
        }

        let trimmed = line.trim();
        if trimmed.starts_with('#') || trimmed.is_empty() {
            continue;
        }

        command_text.push_str(line.trim_matches([' ', '\t']));
        let expr = match syntax::parse::parse_toplevel(&command_text) {
            Ok(("" | "\n", expr)) => expr,
            Ok((rest, _)) => {
                eprintln!("Trailing characters: {rest:?}");
                command_text.clear();
                continue;
            }
            Err(syntax::parse::Error::Incomplete) => {
                continue;
            }
            Err(syntax::parse::Error::Lex(e)) if e.is_incomplete() => {
                continue;
            }
            Err(e) => {
                eprintln!("{e}");
                command_text.clear();
                continue;
            }
        };
        let old_termios = if let ShellInput::Interactive(interactive) = &mut input {
            let new = TerminalOptionsImpl::normal();
            Some(interactive.set_options(new)?)
        } else {
            None
        };
        let (outcome, exit) = command::eval::evaluate(&expr, env);

        if let ShellInput::Interactive(interactive) = &mut input {
            interactive.set_options(old_termios.unwrap()).ok();
        }

        command_text.clear();
        if !outcome.is_success() {
            eprintln!("{outcome:?}");
        }

        if let Some(exit) = exit {
            exit.exit_process();
        }

        match outcome {
            Outcome::ExitShell(_) => unreachable!(),
            Outcome::Process(status) => {
                if !status.success() {
                    if let Some(code) = status.code() {
                        eprintln!("Exit code: {code}");
                    }
                }
            }
            Outcome::Builtin(code) => {
                if code != ExitCode::SUCCESS {
                    eprintln!("Builtin command failed");
                }
            }
        }
    }
}

fn find_script<P: AsRef<Path>>(arg: &P) -> &Path {
    #[cfg(any(target_os = "yggdrasil", rust_analyzer))]
    {
        let from_auxv = std::os::yggdrasil::real_binary_path();
        if from_auxv.exists() {
            return from_auxv;
        }
    }

    arg.as_ref()
}

fn run_wrapper(args: ShellArgs, env: &mut Environment) -> Result<(), Error> {
    let shell_name = std::env::args().next().unwrap();
    match (args.command, args.script) {
        (Some(_), Some(_)) => {
            eprintln!("{shell_name}: cannot mix '-c' and regular arguments");
            Err(Error::InvalidUsage)
        }
        (Some(command), None) => match run_single(env, &command) {
            Outcome::ExitShell(_) => unreachable!(),
            Outcome::Process(status) if status.success() => ExitCode::SUCCESS.exit_process(),
            Outcome::Process(status) if let Some(code) = status.code() => std::process::exit(code),
            Outcome::Process(_) => todo!(),
            Outcome::Builtin(code) => code.exit_process(),
        },
        (None, Some(script)) => {
            let script_path = find_script(&script);
            // let script_path_str = script_path.to_str().unwrap();
            // env.put_var("0", script_path_str.into());
            let script = BufReader::new(File::open(script_path)?);
            run(ShellInput::File(script), env)
        }
        (None, None) => {
            // env.put_var("0", shell_name.into());
            let stdin = TerminalInput::open()?;
            run(ShellInput::Interactive(stdin), env)
        }
    }
}

fn main() -> ExitCode {
    #[cfg(any(rust_analyzer, unix))]
    unsafe {
        libc::setpgid(0, 0);
        libc::signal(libc::SIGTTOU, libc::SIG_IGN);
    }

    const PROFILE_PATH: &str = "/etc/profile";

    logsink::setup_logging(false);
    let args = ShellArgs::parse();
    let mut env = Environment::default();

    // env.setup_builtin(&args.args);
    builtin::register_default();

    if args.login {
        if let Ok(profile_script) = File::open(PROFILE_PATH) {
            let profile_script = BufReader::new(profile_script);
            match run(ShellInput::File(profile_script), &mut env) {
                Ok(()) => (),
                Err(error) => {
                    eprintln!("{PROFILE_PATH}: {error}");
                }
            }
        }
    }

    match run_wrapper(args, &mut env) {
        Ok(()) => ExitCode::SUCCESS,
        Err(error) => {
            eprintln!("{error}");
            ExitCode::FAILURE
        }
    }
}
