use std::io::{self, Stdout, Write};

use cross::term::{TermKey, TerminalInput};

#[derive(thiserror::Error, Debug)]
pub enum ReadlineError {
    #[error("{0}")]
    Io(#[from] io::Error),
}

pub trait ReadlineProvider {
    fn prompt(&mut self, stdout: &mut Stdout);
    fn clear_screen(&mut self, stdout: &mut Stdout);
    fn completions(&mut self, raw_command: &str) -> Vec<String>;
}

struct CompletionState {}

pub struct Readline<'a, P: ReadlineProvider = ()> {
    stdin: &'a mut TerminalInput,
    stdout: &'a mut Stdout,
    buffer: &'a mut String,
    provider: &'a mut P,

    pos: usize,

    completion: CompletionState,
}

enum Outcome {
    Interrupt,
    Clear,
    Data(usize),
}

impl ReadlineProvider for () {
    fn prompt(&mut self, stdout: &mut Stdout) {
        stdout.write_all(b"# ").ok();
    }

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

    fn completions(&mut self, _raw_command: &str) -> Vec<String> {
        vec![]
    }
}

impl CompletionState {
    fn new() -> Self {
        Self {}
    }

    fn tab<P: ReadlineProvider>(&mut self, buffer: &String, provider: &mut P) -> Option<String> {
        let completions = provider.completions(buffer.as_ref());

        if completions.len() == 1 {
            let completion = &completions[0];
            Some(completion.clone())
        } else {
            None
        }
    }
}

impl<'a, P: ReadlineProvider> Readline<'a, P> {
    fn new(
        stdin: &'a mut TerminalInput,
        stdout: &'a mut Stdout,
        buffer: &'a mut String,
        provider: &'a mut P,
    ) -> Self {
        Self {
            pos: buffer.chars().count(),
            completion: CompletionState::new(),

            stdin,
            stdout,
            buffer,
            provider,
        }
    }

    fn accept_completion(&mut self, completion: &str) {
        match self.buffer.rsplit_once(' ') {
            Some((head, tail)) => {
                let new_buffer = format!("{head} {completion}");
                for _ in 0..tail.chars().count() {
                    self.stdout.write_all(b"\x08").ok();
                }
                self.stdout.write_all(completion.as_bytes()).ok();
                self.stdout.flush().ok();
                self.pos = new_buffer.chars().count();
                *self.buffer = new_buffer;
            }
            None => {
                todo!()
            }
        }
    }

    fn handle_char(&mut self, key: TermKey) -> Result<Option<Outcome>, ReadlineError> {
        let mut ch_buffer = [0; 8];

        match key {
            TermKey::Eof => {
                if self.pos == 0 {
                    return Ok(Some(Outcome::Data(self.pos)));
                } else {
                    return Ok(None);
                }
            }
            TermKey::Char(ch) => match ch {
                // ^C
                '\x03' => return Ok(Some(Outcome::Interrupt)),
                // ^L
                '\x0C' => return Ok(Some(Outcome::Clear)),
                // Backspace
                '\x7F' => {
                    // backspace
                    if self.pos != 0 {
                        self.stdout.write_all(b"\x08 \x08").ok();
                        self.stdout.flush().ok();

                        self.buffer.pop();
                        self.pos -= 1;
                    }
                }
                // Tab
                '\t' => {
                    // TODO parse the buffer into a command struct and do ops on the command
                    //      or at least check the token/word before current to see if it's a
                    //      separator like ';', '&&' etc
                    if let Some(completion) = self.completion.tab(&self.buffer, self.provider) {
                        self.accept_completion(&completion);
                    }
                }
                ch if ch.is_whitespace() || ch.is_ascii_graphic() => {
                    let bytes = ch.encode_utf8(&mut ch_buffer);
                    self.stdout.write_all(bytes.as_bytes()).ok();
                    self.stdout.flush().ok();

                    if ch != '\r' {
                        self.buffer.push(ch);
                        self.pos += 1;
                    }

                    if ch == '\r' || ch == '\n' {
                        if ch == '\r' {
                            self.buffer.push('\n');
                            self.pos += 1;
                            self.stdout.write_all(b"\n").ok();
                            self.stdout.flush().ok();
                        }
                        return Ok(Some(Outcome::Data(self.pos)));
                    }
                }
                ch => {
                    log::warn!("TODO: handle char: {ch:?}");
                }
            },
            TermKey::Up => {
                log::warn!("TODO: shell history up");
            }
            TermKey::Down => {
                log::warn!("TODO: shell history down");
            }
            TermKey::Left | TermKey::Right => {
                log::warn!("TODO: readline cursor");
            }
            _ => (),
        }

        Ok(None)
    }

    fn try_readline(&mut self) -> Result<Outcome, ReadlineError> {
        // let mut pos = self.buffer.chars().count();

        self.pos = self.buffer.chars().count();
        self.stdout.write_all(self.buffer.as_bytes()).ok();
        self.stdout.flush().ok();

        loop {
            let key = self.stdin.read_key()?;
            match self.handle_char(key)? {
                Some(outcome) => break Ok(outcome),
                None => (),
            }
        }

        // loop {
        //     let key = self.stdin.read_key()?;

        // }

        // Ok(Outcome::Data(pos))
    }

    pub fn readline(&mut self) -> Result<usize, ReadlineError> {
        loop {
            self.provider.prompt(self.stdout);
            self.stdout.flush().ok();

            match self.try_readline()? {
                Outcome::Data(n) => break Ok(n),
                Outcome::Clear => {
                    self.provider.clear_screen(self.stdout);
                }
                Outcome::Interrupt => {
                    self.stdout.write_all(b"\r\n").ok();
                    self.buffer.clear();
                }
            }
        }
        //     loop {
        //         prompt(stdout);
        //         stdout.flush().ok();
        //
        //         match readline_inner(stdin, stdout, buffer)? {
        //             Outcome::Data(n) => break Ok(n),
        //             Outcome::Clear => {
        //                 stdout.write_all(b"\x1B[H\x1B[J").ok();
        //             }
        //             Outcome::Interrupt => {
        //                 stdout.write_all(b"\r\n").ok();
        //                 buffer.clear();
        //             }
        //         }
        //     }
    }
}

// fn readline_inner(
//     stdin: &mut TerminalInput,
//     stdout: &mut Stdout,
//     buffer: &mut String,
// ) -> Result<Outcome, ReadlineError> {
// }

pub fn readline<P: ReadlineProvider>(
    stdin: &mut TerminalInput,
    stdout: &mut Stdout,
    buffer: &mut String,
    provider: &mut P,
) -> Result<usize, ReadlineError> {
    let mut readline = Readline::new(stdin, stdout, buffer, provider);
    readline.readline()
}

// pub fn readline<P: Fn(&mut Stdout)>(
//     stdin: &mut TerminalInput,
//     stdout: &mut Stdout,
//     buffer: &mut String,
//     prompt: P,
// ) -> Result<usize, ReadlineError> {
// }
