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

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

use crate::{
    TopMode,
    buffer::{Buffer, Mode, SetMode},
    config::EditorConfig,
    error::Error,
    highlight::Highlighter,
    script::Movement,
};

pub struct State {
    pub(super) term: Term,
    buffer: Buffer,
    top_mode: TopMode,
    config: Rc<RefCell<EditorConfig>>,
    running: bool,
    number_width: usize,
    pub(super) highlight: Highlighter,

    // Scripting hooks
    pub(super) key_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,
            running: true,
            buffer,
            term,
            config,
            highlight: Highlighter::default(),

            key_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) -> Result<(), Error> {
        if self.buffer.is_modified() && !force {
            Err(Error::UnsavedBuffer("Use :q! to force-exit"))
        } else {
            self.running = false;
            Ok(())
        }
    }

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

    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(())
    }

    pub fn cursor_to_buffer(&mut self) -> Result<(), Error> {
        let config = self.config.borrow();
        self.buffer.set_terminal_cursor(&config, &mut self.term)
    }

    pub fn finish_display(&mut self) -> Result<(), Error> {
        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() || config.bottom_margin.clean() {
            if *config.number {
                let nw = self.buffer.number_width() + 3;
                self.buffer
                    .resize(&config, nw, w - nw - 1, h - (*config.bottom_margin + 1));
            } else {
                self.buffer
                    .resize(&config, 0, w - 1, h - (*config.bottom_margin + 1));
            }
        }

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

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

        Ok((w, h))
    }

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

    pub fn set_top_mode(&mut self, target: TopMode) {
        if self.top_mode == target {
            return;
        }

        self.top_mode = target;
        self.buffer.set_mode(&self.config.borrow(), SetMode::Normal);
    }

    pub fn write_text(&mut self, text: &str) {
        let config = self.config.borrow();
        for char in text.chars() {
            self.buffer.insert(&config, char);
        }
    }

    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<Option<String>, 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");
            Ok(Some(status))
        } else {
            Ok(None)
        }
    }

    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();
    }
}
