use std::{
    io::{self, stdout, Stdout, Write},
    mem,
    ops::{Deref, DerefMut, Range},
    str::Split,
};

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

#[derive(Debug, Clone, PartialEq)]
#[repr(transparent)]
pub struct NonEmptyVec<T>(Vec<T>);

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmptyVecError;

impl<T> NonEmptyVec<T> {
    #[inline]
    pub fn try_from_iter<I: IntoIterator<Item = T>>(iter: I) -> Result<Self, EmptyVecError> {
        Self::try_from(Vec::from_iter(iter))
    }
}

impl<T> Deref for NonEmptyVec<T> {
    type Target = [T];

    fn deref(&self) -> &Self::Target {
        &self.0[..]
    }
}

impl<T> DerefMut for NonEmptyVec<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0[..]
    }
}

impl<T> TryFrom<Vec<T>> for NonEmptyVec<T> {
    type Error = EmptyVecError;

    fn try_from(value: Vec<T>) -> Result<Self, Self::Error> {
        if value.is_empty() {
            Err(EmptyVecError)
        } else {
            Ok(Self(value))
        }
    }
}

pub trait ReadlineProvider {
    fn completions(&mut self, buffer: &Buffer) -> Option<NonEmptyVec<String>>;

    fn prompt(&mut self) -> String {
        "> ".into()
    }

    fn lookup_history(&mut self, depth: usize) -> Option<String> {
        let _ = depth;
        None
    }
    fn push_history(&mut self, entry: &str) {
        let _ = entry;
    }
}

pub struct CompletionState {
    variants: NonEmptyVec<String>,
    index: usize,
}

pub struct Readline<P: ReadlineProvider> {
    buffer: Buffer,

    provider: P,
    current_prompt: String,

    stdin: TerminalInput,
    stdout: Stdout,

    completion: Option<CompletionState>,
    history_depth: Option<usize>,
}

#[derive(Debug, thiserror::Error)]
pub enum ReadlineError {}

enum Outcome {
    Clear,
    Break,
    Output(String),
}

impl CompletionState {
    pub fn current(&self) -> &str {
        debug_assert!(!self.variants.is_empty());
        &self.variants[self.index]
    }

    pub fn cycle(&mut self) -> &str {
        debug_assert!(!self.variants.is_empty());
        self.index = (self.index + 1) % self.variants.len();
        &self.variants[self.index]
    }
}

impl From<NonEmptyVec<String>> for CompletionState {
    fn from(variants: NonEmptyVec<String>) -> Self {
        Self { variants, index: 0 }
    }
}

impl TryFrom<Vec<String>> for CompletionState {
    type Error = EmptyVecError;

    fn try_from(value: Vec<String>) -> Result<Self, Self::Error> {
        let variants = value.try_into()?;
        Ok(Self { variants, index: 0 })
    }
}

impl ReadlineProvider for () {
    fn completions(&mut self, _buffer: &Buffer) -> Option<NonEmptyVec<String>> {
        None
    }
}

impl<P: ReadlineProvider> Readline<P> {
    pub fn new(provider: P) -> io::Result<Self> {
        let stdin = TerminalInput::open()?;
        Ok(Self {
            stdin,
            provider,

            current_prompt: String::new(),
            completion: None,
            history_depth: None,
            buffer: Buffer::new(),
            stdout: stdout(),
        })
    }

    pub fn stdin_mut(&mut self) -> &mut TerminalInput {
        &mut self.stdin
    }

    fn print_char(&mut self, ch: char) {
        let mut char_buffer = [0; 4];
        let s = ch.encode_utf8(&mut char_buffer);
        self.stdout.write_all(s.as_bytes()).ok();
    }

    fn print_newline(&mut self) {
        self.print_char('\r');
        self.print_char('\n');
    }

    fn print_erase_backward(&mut self) {
        write!(self.stdout, "\x08 \x08").ok();
        self.flush();
    }

    fn reprint(&mut self) {
        write!(
            self.stdout,
            "\x1B[2K\x1B[1G{}{}\x1B[{}G",
            self.current_prompt,
            self.buffer.line,
            self.current_prompt.chars().count() + self.buffer.cursor + 1
        )
        .ok();
        self.flush();
    }

    fn flush(&mut self) {
        self.stdout.flush().ok();
    }

    fn reset_auxiliary(&mut self) {
        self.completion = None;
        self.history_depth = None;
    }

    fn handle_char(&mut self, ch: char) -> io::Result<Option<Outcome>> {
        match ch {
            '\x0C' => Ok(Some(Outcome::Clear)),
            '\t' => {
                let variant = if let Some(completion) = self.completion.as_mut() {
                    completion.cycle()
                } else {
                    let Some(completion) = self.provider.completions(&self.buffer) else {
                        return Ok(None);
                    };
                    let completion = self.completion.insert(completion.into());
                    completion.current()
                };
                self.buffer.replace_cursor_word(variant);

                self.reprint();

                Ok(None)
            }
            '\r' | '\n' => {
                self.reset_auxiliary();
                self.print_newline();
                let text = self.buffer.reset();
                self.provider.push_history(&text);
                Ok(Some(Outcome::Output(text)))
            }
            '\x7F' => {
                self.reset_auxiliary();

                if self.buffer.erase_backward() {
                    self.print_erase_backward();
                }
                Ok(None)
            }
            ch if ch.is_alphanumeric() || ch.is_whitespace() || ch.is_ascii_punctuation() => {
                self.reset_auxiliary();

                // TODO better insert?
                self.buffer.insert_at_cursor(ch);
                // Reprint everything after cursor
                let after_cursor = self.buffer.after_cursor();
                write!(self.stdout, "{ch}{after_cursor}").ok();
                // Move cursor back (TODO n times)
                for _ in 0..after_cursor.chars().count() {
                    write!(self.stdout, "\x08").ok();
                }
                self.flush();
                Ok(None)
            }
            _ => Ok(None),
        }
    }

    fn handle_key(&mut self, key: TermKey) -> io::Result<Option<Outcome>> {
        match key {
            TermKey::Char(ch) => self.handle_char(ch),
            TermKey::Left => {
                self.reset_auxiliary();

                if self.buffer.move_backward() {
                    self.print_char('\x08');
                    self.flush();
                }
                Ok(None)
            }
            TermKey::Home => {
                self.reset_auxiliary();

                self.buffer.cursor = 0;
                let pos = self.current_prompt.chars().count() + 1;
                write!(self.stdout, "\x1B[{pos}G").ok();
                self.flush();

                Ok(None)
            }
            TermKey::End => {
                self.reset_auxiliary();

                self.buffer.cursor = self.buffer.line.chars().count();
                let pos = self.current_prompt.chars().count() + self.buffer.cursor + 1;
                write!(self.stdout, "\x1B[{pos}G").ok();
                self.flush();

                Ok(None)
            }
            TermKey::Right => {
                self.reset_auxiliary();

                if self.buffer.move_forward() {
                    write!(self.stdout, "\x1B[C").ok();
                    self.flush();
                }
                Ok(None)
            }
            TermKey::Up => {
                self.completion = None;

                let depth = self.history_depth.map(|x| x + 1).unwrap_or(0);
                let entry = self.provider.lookup_history(depth);
                if let Some(entry) = entry {
                    self.buffer.set_text(&entry);
                    self.reprint();
                    self.history_depth = Some(depth);
                }

                Ok(None)
            }
            TermKey::Down => {
                self.completion = None;

                match self.history_depth {
                    Some(0) | None => {
                        self.history_depth = None;
                        self.buffer.reset();
                        self.reprint();
                    }
                    Some(n) => {
                        if let Some(entry) = self.provider.lookup_history(n - 1) {
                            self.buffer.set_text(&entry);
                            self.reprint();
                            self.history_depth = Some(n - 1);
                        }
                    }
                }

                Ok(None)
            }
            TermKey::Eof => {
                self.reset_auxiliary();

                if self.buffer.is_empty() {
                    Ok(Some(Outcome::Break))
                } else {
                    Ok(None)
                }
            }
            _ => Ok(None),
        }
    }

    fn try_readline(&mut self) -> io::Result<Outcome> {
        loop {
            let key = self.stdin.read_key()?;

            if let Some(output) = self.handle_key(key)? {
                break Ok(output);
            }
        }
    }

    pub fn readline(&mut self) -> io::Result<Option<String>> {
        loop {
            self.current_prompt = self.provider.prompt();
            self.stdout.write_all(self.current_prompt.as_bytes()).ok();
            self.stdout.flush().ok();

            let outcome = self.try_readline()?;

            match outcome {
                Outcome::Clear => {
                    write!(self.stdout, "\x1B[2J\x1B[H").ok();
                    self.buffer.reset();
                }
                Outcome::Break => return Ok(None),
                Outcome::Output(output) => return Ok(Some(output)),
            }
        }
    }
}

pub struct Buffer {
    line: String,
    cursor: usize,
}

impl Buffer {
    fn new() -> Self {
        Self {
            line: String::new(),
            cursor: 0,
        }
    }

    pub fn is_empty(&self) -> bool {
        self.line.is_empty()
    }

    fn set_text(&mut self, text: &str) {
        self.line = text.into();
        self.cursor = text.chars().count();
    }

    fn reset(&mut self) -> String {
        let text = mem::take(&mut self.line);
        self.cursor = 0;
        text
    }

    fn move_backward(&mut self) -> bool {
        if self.cursor != 0 {
            self.cursor -= 1;
            true
        } else {
            false
        }
    }

    fn erase_backward(&mut self) -> bool {
        if self.cursor != 0 {
            self.cursor -= 1;
            let cursor_byte_pos = self.cursor_byte_pos();
            self.line.remove(cursor_byte_pos);
            true
        } else {
            false
        }
    }

    fn move_forward(&mut self) -> bool {
        let len = self.line.chars().count();
        if self.cursor < len {
            self.cursor += 1;
            true
        } else {
            false
        }
    }

    fn insert_at_cursor(&mut self, ch: char) {
        let cursor_byte_pos = self.cursor_byte_pos();
        self.line.insert(cursor_byte_pos, ch);
        self.cursor += 1;
    }

    pub fn byte_pos(&self, char_index: usize) -> usize {
        self.line
            .char_indices()
            .nth(char_index)
            .map(|(i, _)| i)
            .unwrap_or_else(|| self.line.len())
    }

    pub fn cursor_byte_pos(&self) -> usize {
        self.byte_pos(self.cursor)
    }

    pub fn after_cursor(&self) -> &str {
        &self.line[self.cursor_byte_pos()..]
    }

    pub fn cursor_word_byte_boundary(&self) -> Range<usize> {
        let cursor_byte_pos = self.cursor_byte_pos();
        let line_bytes = self.line.as_bytes();
        let word_start = cursor_byte_pos
            - line_bytes[..cursor_byte_pos]
                .iter()
                .rev()
                .position(u8::is_ascii_whitespace)
                .unwrap_or(cursor_byte_pos);
        let word_end = line_bytes[cursor_byte_pos..]
            .iter()
            .position(u8::is_ascii_whitespace)
            .unwrap_or(line_bytes.len() - cursor_byte_pos)
            + cursor_byte_pos;

        word_start..word_end
    }

    fn replace_cursor_word(&mut self, replace: &str) {
        let word = self.cursor_word_byte_boundary();
        self.line.replace_range(word.clone(), replace);
        self.cursor = self.line[..word.start].chars().count() + replace.chars().count();
    }

    pub fn word_before_cursor(&self) -> Option<&str> {
        let cursor_byte_pos = self.cursor_byte_pos();

        let line_bytes = self.line.as_bytes();
        let word_start = cursor_byte_pos
            - line_bytes[..cursor_byte_pos]
                .iter()
                .rev()
                .position(u8::is_ascii_whitespace)
                .unwrap_or(cursor_byte_pos);

        if word_start == cursor_byte_pos {
            return None;
        }

        let word_text = &self.line[word_start..cursor_byte_pos];
        Some(word_text)
    }

    pub fn full_words_before_cursor(&self) -> Split<'_, char> {
        let cursor_byte_pos = self.cursor_byte_pos();
        self.line[..cursor_byte_pos].split(' ')
    }

    pub fn is_first_word(&self) -> bool {
        let cursor_byte_pos = self.cursor_byte_pos();
        // Find first non-space character
        let line_bytes = self.line.as_bytes();
        let word_start = cursor_byte_pos
            - line_bytes[..cursor_byte_pos]
                .iter()
                .rev()
                .position(u8::is_ascii_whitespace)
                .unwrap_or(cursor_byte_pos);

        line_bytes[..word_start]
            .iter()
            .rfind(|&&ch| !ch.is_ascii_whitespace())
            .is_none()
    }
}

impl From<String> for Buffer {
    fn from(line: String) -> Self {
        let cursor = line.chars().count();
        Self { line, cursor }
    }
}

#[cfg(test)]
mod tests {
    use crate::readline::Buffer;

    #[test]
    fn test_word_before_cursor() {
        let b = Buffer {
            line: "".into(),
            cursor: 0,
        };
        assert_eq!(b.word_before_cursor(), None);

        let b = Buffer {
            line: " ".into(),
            cursor: 0,
        };
        assert_eq!(b.word_before_cursor(), None);

        let b = Buffer {
            line: " ".into(),
            cursor: 1,
        };
        assert_eq!(b.word_before_cursor(), None);

        let b = Buffer {
            line: "abc ".into(),
            cursor: 4,
        };
        assert_eq!(b.word_before_cursor(), None);

        let b = Buffer {
            line: "abc ".into(),
            cursor: 2,
        };
        assert_eq!(b.word_before_cursor(), Some("ab"));

        let b = Buffer {
            line: "123 abc ".into(),
            cursor: 6,
        };
        assert_eq!(b.word_before_cursor(), Some("ab"));

        let b = Buffer {
            line: "123 abc ".into(),
            cursor: 3,
        };
        assert_eq!(b.word_before_cursor(), Some("123"));

        let b = Buffer {
            line: "123 abc ".into(),
            cursor: 4,
        };
        assert_eq!(b.word_before_cursor(), None);
    }

    #[test]
    fn test_replace_under_cursor() {
        // |
        let mut b = Buffer {
            line: "".into(),
            cursor: 0,
        };
        b.replace_cursor_word("test");
        assert_eq!(b.cursor, 4);
        assert_eq!(b.line, "test");

        // abc|
        let mut b = Buffer {
            line: "abc".into(),
            cursor: 3,
        };
        b.replace_cursor_word("test");
        assert_eq!(b.cursor, 4);
        assert_eq!(b.line, "test");

        // a|bc def
        let mut b = Buffer {
            line: "abc def".into(),
            cursor: 1,
        };
        b.replace_cursor_word("test");
        assert_eq!(b.cursor, 4);
        assert_eq!(b.line, "test def");

        // 123 |abc def
        let mut b = Buffer {
            line: "123 abc def".into(),
            cursor: 4,
        };
        b.replace_cursor_word("test");
        assert_eq!(b.cursor, 8);
        assert_eq!(b.line, "123 test def");

        // "123 abc def |"
        let mut b = Buffer {
            line: "123 abc def ".into(),
            cursor: 12,
        };
        b.replace_cursor_word("ghi");
        assert_eq!(b.cursor, 15);
        assert_eq!(b.line, "123 abc def ghi");
    }
}
