use std::{cell::RefCell, fmt::Write as FmtWrite, mem, path::Path, rc::Rc};

use libterm::{Clear, Color, Term, TermKey};
use lysp::vm::value::convert::AnyFunction;

use crate::{
    TopMode,
    buffer::{Buffer, Mode, SetMode},
    command::Action,
    config::EditorConfig,
    error::Error,
    highlight::Highlighter,
    keymap::{KeyMap, KeySeq, PrefixNode},
    script::Movement,
};

pub struct State {
    pub(super) term: Term,
    buffer: Buffer,
    command: String,
    message: Option<String>,
    status: Option<String>,
    key_seq: KeySeq,
    top_mode: TopMode,
    config: Rc<RefCell<EditorConfig>>,
    // config: Config,
    running: bool,
    number_width: usize,
    pub(super) highlight: Highlighter,

    // Scripting hooks
    pub(super) normal_map: KeyMap<AnyFunction>,
    pub(super) insert_map: KeyMap<AnyFunction>,
    pub(super) command_hook: Option<AnyFunction>,
    pub(super) post_render_hook: Option<AnyFunction>,
}

impl State {
    pub fn open<P: AsRef<Path>>(
        path: Option<P>,
        config: Rc<RefCell<EditorConfig>>,
    ) -> Result<Self, Error> {
        let mut buffer = match path {
            Some(path) => Buffer::open(path).unwrap(),
            None => Buffer::empty(),
        };
        let term = Term::open().map_err(Error::TerminalError)?;

        Ok(Self {
            number_width: buffer.number_width(),
            top_mode: TopMode::Normal,
            message: None,
            status: None,
            command: String::new(),
            key_seq: KeySeq::empty(),
            running: true,
            buffer,
            term,
            config,
            highlight: Highlighter::default(),

            normal_map: KeyMap::new(),
            insert_map: KeyMap::new(),
            command_hook: None,
            post_render_hook: None,
        })
    }

    pub fn buffer(&self) -> &Buffer {
        &self.buffer
    }

    pub fn buffer_mut(&mut self) -> &mut Buffer {
        &mut self.buffer
    }

    pub fn mode(&self) -> (TopMode, Mode) {
        (self.top_mode, self.buffer.mode())
    }

    pub fn buffer_terminal_cursor(&self) -> Option<(usize, usize)> {
        if self.top_mode == TopMode::Command {
            return None;
        }
        Some(self.buffer.get_terminal_cursor(&self.config.borrow()))
    }

    pub fn exit(&mut self, force: bool) {
        if self.buffer.is_modified() && !force {
            self.message = Some("Buffer has unsaved changes. Use :q! to force-exit".into());
            return;
        }
        self.running = false;
    }

    pub fn exited(&self) -> bool {
        !self.running
    }

    pub fn set_status<S: Into<String>>(&mut self, status: S) {
        self.status.replace(status.into());
    }

    pub fn set_message<S: Into<String>>(&mut self, message: S) {
        self.message.replace(message.into());
    }

    fn display_number(&mut self) -> Result<(), Error> {
        let start = self.buffer.row_offset();
        let end = self.buffer.len();

        for i in 0.. {
            self.term
                .set_cursor_position(i, 0)
                .map_err(Error::TerminalError)?;

            if i + start == self.buffer.cursor_row() {
                self.term.set_bright(true).map_err(Error::TerminalError)?;
                self.term
                    .set_foreground(Color::Yellow)
                    .map_err(Error::TerminalError)?;
            }

            if i + start < end {
                write!(self.term, " {0:1$} ", i + start + 1, self.number_width)
                    .map_err(Error::TerminalFmtError)?;
            }

            if i == self.buffer.height() {
                break;
            }

            if i + start == self.buffer.cursor_row() {
                self.term.reset_style().map_err(Error::TerminalError)?;
            }
        }

        self.term.reset_style().map_err(Error::TerminalError)?;

        Ok(())
    }

    fn display_modeline(&mut self) -> Result<(), Error> {
        self.term
            .set_cursor_position(self.buffer.height(), 0)
            .map_err(Error::TerminalError)?;

        let bg = match (self.top_mode, self.buffer.mode()) {
            (TopMode::Normal, Mode::Normal) => Color::Yellow,
            (TopMode::Normal, Mode::Insert) => Color::Cyan,
            (TopMode::Command, _) => Color::Green,
        };

        self.term.set_background(bg).map_err(Error::TerminalError)?;
        self.term
            .set_foreground(Color::Black)
            .map_err(Error::TerminalError)?;

        match self.top_mode {
            TopMode::Normal => {
                write!(self.term, " {} ", self.buffer.mode().as_str())
                    .map_err(Error::TerminalFmtError)?;

                if self.buffer.is_modified() {
                    self.term
                        .set_background(Color::Magenta)
                        .map_err(Error::TerminalError)?;
                    self.term
                        .set_foreground(Color::Default)
                        .map_err(Error::TerminalError)?;
                } else {
                    self.term
                        .set_foreground(Color::Green)
                        .map_err(Error::TerminalError)?;
                    self.term
                        .set_background(Color::Default)
                        .map_err(Error::TerminalError)?;
                }
            }
            TopMode::Command => {
                write!(self.term, " COMMAND ").map_err(Error::TerminalFmtError)?;

                self.term
                    .set_foreground(Color::Green)
                    .map_err(Error::TerminalError)?;
                self.term
                    .set_background(Color::Default)
                    .map_err(Error::TerminalError)?;
            }
        }

        let name = self
            .buffer
            .name()
            .map(String::as_str)
            .unwrap_or("<unnamed>");
        write!(self.term, " {}", name).map_err(Error::TerminalFmtError)?;
        self.term
            .clear(Clear::LineToEnd)
            .map_err(Error::TerminalError)?;
        self.term
            .set_cursor_position(self.buffer.height(), self.buffer.width() - 10)
            .map_err(Error::TerminalError)?;
        self.term
            .set_foreground(Color::White)
            .map_err(Error::TerminalError)?;
        write!(self.term, "{} {}", self.buffer.filetype(), self.key_seq)
            .map_err(Error::TerminalFmtError)?;

        self.term.reset_style().map_err(Error::TerminalError)?;

        Ok(())
    }

    pub fn finish_display(&mut self) -> Result<(), Error> {
        let config = self.config.borrow();

        match self.top_mode {
            TopMode::Normal => {
                self.buffer.set_terminal_cursor(&config, &mut self.term)?;
            }
            TopMode::Command => {
                self.term
                    .set_cursor_position(self.buffer.height() + 1, 0)
                    .map_err(Error::TerminalError)?;
                write!(self.term, ":{}", self.command.as_str()).map_err(Error::TerminalFmtError)?;
            }
        }

        self.term.flush().map_err(Error::TerminalError)?;
        Ok(())
    }

    pub fn display(&mut self) -> Result<(usize, usize), Error> {
        let config = self.config.clone();
        let mut config = config.borrow_mut();

        if self.buffer.is_dirty() {
            self.term.clear(Clear::All).map_err(Error::TerminalError)?;
        }

        let (w, h) = self.term.size().map_err(Error::TerminalError)?;

        if config.number.clean() {
            if *config.number {
                let nw = self.buffer.number_width() + 3;
                self.buffer.resize(&config, nw, w - nw - 1, h - 2);
            } else {
                self.buffer.resize(&config, 0, w - 1, h - 2);
            }
        }

        if *config.number && self.buffer.is_dirty() {
            self.display_number()?;
        }

        self.buffer
            .display(&config, &mut self.term, &self.highlight)?;

        if self.top_mode != TopMode::Command
            && let Some(status) = &self.status
        {
            self.term
                .set_cursor_position(self.buffer().height() + 1, 0)
                .map_err(Error::TerminalError)?;
            self.term
                .write_str(status.as_str())
                .map_err(Error::TerminalFmtError)?;
        }

        if let Some(msg) = &self.message {
            self.term
                .set_cursor_position(self.buffer.height(), 0)
                .map_err(Error::TerminalError)?;
            self.term.write_str(msg).map_err(Error::TerminalFmtError)?;
            self.term.flush().map_err(Error::TerminalError)?;
            return Ok((w, h));
        }

        self.display_modeline()?;

        Ok((w, h))
    }

    pub fn set_mode(&mut self, target: SetMode) {
        self.top_mode = TopMode::Normal;
        self.message = None;
        self.key_seq.clear();
        self.buffer.set_mode(&self.config.borrow(), target);
    }

    fn key_seq(&self, mode: Mode) -> Option<&PrefixNode<KeySeq, AnyFunction>> {
        match mode {
            Mode::Normal => self.normal_map.get(&self.key_seq),
            Mode::Insert => self.insert_map.get(&self.key_seq),
        }
    }

    fn handle_mode_key(&mut self, mode: Mode, key: TermKey) -> Option<AnyFunction> {
        self.key_seq.push(key);
        match self.key_seq(mode) {
            Some(PrefixNode::Leaf(action)) => {
                let action = action.clone();
                self.key_seq.clear();
                return Some(action);
            }
            Some(PrefixNode::Prefix(_)) => {}
            None => self.key_seq.clear(),
        }
        None
    }

    fn handle_normal_key(&mut self, key: TermKey) -> Option<AnyFunction> {
        match key {
            TermKey::Escape => {
                self.key_seq.clear();
                self.buffer.set_mode(&self.config.borrow(), SetMode::Normal);
                None
            }
            TermKey::Char(':') => {
                self.key_seq.clear();
                self.command.clear();
                self.status = None;
                self.top_mode = TopMode::Command;
                None
            }
            _ => self.handle_mode_key(Mode::Normal, key),
        }
    }

    fn handle_insert_key(&mut self, key: TermKey) -> Option<AnyFunction> {
        match key {
            TermKey::Escape => {
                self.buffer.set_mode(&self.config.borrow(), SetMode::Normal);
                None
            }
            TermKey::Char(key)
                if !key.is_ascii() || key == ' ' || key == '\t' || key.is_ascii_graphic() =>
            {
                self.buffer.insert(&self.config.borrow(), key);
                None
            }
            _ => self.handle_mode_key(Mode::Insert, key),
        }
    }

    fn handle_command_key(&mut self, key: TermKey) -> Action {
        match key {
            TermKey::Char('\n') | TermKey::Char('\x0D') => {
                self.top_mode = TopMode::Normal;
                let command = mem::take(&mut self.command);
                return Action::Command(command);
            }
            TermKey::Char('\x7F') => {
                if self.command.is_empty() {
                    self.top_mode = TopMode::Normal;
                } else {
                    self.command.pop();
                }
            }
            TermKey::Escape => {
                self.top_mode = TopMode::Normal;
            }
            TermKey::Char(c) if c.is_ascii_graphic() || c == ' ' => self.command.push(c),
            _ => (),
        }
        Action::None
    }

    pub fn handle_key(&mut self, key: TermKey) -> Action {
        if self.message.is_some() {
            self.message = None;
            if key != TermKey::Char(':') {
                return Action::None;
            }
        }

        match (self.top_mode, self.buffer.mode()) {
            (TopMode::Normal, Mode::Normal) => self.handle_normal_key(key).into(),
            (TopMode::Normal, Mode::Insert) => self.handle_insert_key(key).into(),
            (TopMode::Command, _) => self.handle_command_key(key),
        }
    }

    pub fn move_cursor(&mut self, movement: Movement) {
        let config = self.config.borrow();
        match movement {
            Movement::LineStart => self.buffer.set_column(&config, 0),
            Movement::LineEnd => self.buffer.to_line_end(&config),
            Movement::NextLine => self.buffer.move_cursor(&config, 0, 1),
            Movement::PrevLine => self.buffer.move_cursor(&config, 0, -1),
            Movement::NextPage => self.buffer.move_cursor(&config, 0, 25),
            Movement::PrevPage => self.buffer.move_cursor(&config, 0, -25),
            Movement::FirstLine => self.buffer.to_first_line(&config),
            Movement::LastLine => self.buffer.to_last_line(&config),
            Movement::NextChar => self.buffer.move_cursor(&config, 1, 0),
            Movement::PrevChar => self.buffer.move_cursor(&config, -1, 0),
        }
    }

    pub fn kill_current_line(&mut self) {
        let config = self.config.borrow();
        self.buffer.kill_line(&config);
    }

    pub fn erase_backward(&mut self) {
        let config = self.config.borrow();
        self.buffer.erase_backward(&config);
    }

    pub fn open_buffer<P: AsRef<Path>>(&mut self, path: P, force: bool) -> Result<(), Error> {
        if self.buffer.is_modified() && !force {
            return Err(Error::UnsavedBuffer("Use :e! [FILE] to open another file"));
        }

        self.buffer.reopen(path).map_err(Error::OpenError)
    }

    pub fn write_buffer<P: AsRef<Path>>(&mut self, path: Option<P>) -> Result<(), Error> {
        if path.is_some() && self.buffer.is_modified() && self.buffer.path().is_some() {
            return Err(Error::UnsavedBuffer(
                "Use :w! FILE to force write to another file",
            ));
        }

        if let Some(path) = path {
            self.buffer.set_path(path);
        }
        self.buffer.save()?;

        if let Some(name) = self.buffer.name() {
            let status = format!("{name:?} written");
            self.set_status(status);
        }

        Ok(())
    }

    pub fn wait_for_events(&mut self) -> Result<TermKey, Error> {
        self.term.read_key().map_err(Error::TerminalError)
    }

    pub fn cleanup(&mut self) {
        self.term.clear(Clear::All).ok();
    }
}
