use crate::{
    Editor,
    buffer::SetMode,
    highlight::{SyntaxRule, SyntaxStyle},
    keymap::KeySeq,
};

pub mod convert;
pub use convert::{AsValue, FromValue, Movement};
use libterm::Color;
use lysp::{
    error::MachineError,
    vm::{
        Value, prelude,
        value::{
            StringValue,
            convert::{AnyFunction, TryFromValue},
        },
    },
};

fn editor_api(editor: &mut Editor) {
    editor.defun("red/bind-normal-hook", |_, _, state, args| {
        let [seq, handler] = args else {
            return Err(MachineError::InvalidArgumentCount.into());
        };
        let seq = KeySeq::from_value(seq)?;
        let handler = AnyFunction::try_from_value(handler)?;

        let mut state = state.borrow_mut();
        state.normal_map.set(seq, handler);

        Ok(Value::Nil)
    });
    editor.defun("red/bind-insert-hook", |_, _, state, args| {
        let [seq, handler] = args else {
            return Err(MachineError::InvalidArgumentCount.into());
        };
        let seq = KeySeq::from_value(seq)?;
        let handler = AnyFunction::try_from_value(handler)?;

        let mut state = state.borrow_mut();
        state.insert_map.set(seq, handler);

        Ok(Value::Nil)
    });
    editor.defun("red/bind-command-hook", |_, _, state, args| {
        let [hook] = args else {
            return Err(MachineError::InvalidArgumentCount.into());
        };
        let hook = AnyFunction::try_from_value(hook)?;
        state.borrow_mut().command_hook = Some(hook);
        Ok(Value::Nil)
    });
    editor.defun("red/bind-post-render-hook", |_, _, state, args| {
        let [hook] = args else {
            return Err(MachineError::InvalidArgumentCount.into());
        };
        let hook = AnyFunction::try_from_value(hook)?;
        state.borrow_mut().post_render_hook = Some(hook);
        Ok(Value::Nil)
    });

    editor.defun("red/quit", |_, _, state, args| {
        let force = match args {
            [] => false,
            [arg, ..] => arg.is_trueish(),
        };
        state.borrow_mut().exit(force);
        Ok(Value::Nil)
    });
    editor.defun("red/message", |_, _, state, args| {
        let mut message = String::new();
        for (i, arg) in args.iter().enumerate() {
            if i != 0 {
                message.push(' ');
            }
            message.push_str(&format!("{arg}"));
        }
        state.borrow_mut().set_message(message);
        Ok(Value::Nil)
    });
    editor.defun("red/status", |_, _, state, args| {
        let mut message = String::new();
        for (i, arg) in args.iter().enumerate() {
            if i != 0 {
                message.push(' ');
            }
            message.push_str(&format!("{arg}"));
        }
        state.borrow_mut().set_status(message);
        Ok(Value::Nil)
    });
}

fn syntax_api(editor: &mut Editor) {
    editor.defun("red/syntax/define-regex-rule", |_, _, state, args| {
        // ( <SYNTAX> <STATE> <PATTERN> <CATEGORY> &optional <NEXT-STATE> )
        let (filetype, entry_state, pattern, category, next_state) = match args {
            [a, b, c, d] => (a, b, c, d, &Value::Nil),
            [a, b, c, d, e] => (a, b, c, d, e),
            _ => return Err(MachineError::InvalidArgumentCount.into()),
        };

        let filetype = StringValue::try_from_value(filetype)?;
        let entry_state = usize::try_from_value(entry_state)? as u32;
        let pattern = StringValue::try_from_value(pattern)?;
        let category = StringValue::try_from_value(category)?;
        let next_state = if next_state.is_nil() {
            None
        } else {
            Some(usize::try_from_value(next_state)? as u32)
        };

        let rule = match SyntaxRule::regex(&pattern, next_state, category.as_rc_str().clone()) {
            Some(rule) => rule,
            None => return Err(MachineError::InstructionFetch.into()),
        };

        let mut state = state.borrow_mut();
        state
            .highlight
            .define_rule(filetype.as_rc_str().clone(), entry_state, rule);

        Ok(Value::Nil)
    });
    editor.defun("red/syntax/define-keyword-rule", |_, _, state, args| {
        // ( <SYNTAX> <STATE> <PATTERN> <CATEGORY> &optional <NEXT-STATE> )
        let (filetype, entry_state, pattern, category, next_state) = match args {
            [a, b, c, d] => (a, b, c, d, &Value::Nil),
            [a, b, c, d, e] => (a, b, c, d, e),
            _ => return Err(MachineError::InvalidArgumentCount.into()),
        };

        let filetype = StringValue::try_from_value(filetype)?;
        let entry_state = usize::try_from_value(entry_state)? as u32;
        let pattern = StringValue::try_from_value(pattern)?;
        let category = StringValue::try_from_value(category)?;
        let next_state = if next_state.is_nil() {
            None
        } else {
            Some(usize::try_from_value(next_state)? as u32)
        };

        let rule = SyntaxRule::keyword(
            pattern.as_rc_str().clone(),
            next_state,
            category.as_rc_str().clone(),
        );

        let mut state = state.borrow_mut();
        state
            .highlight
            .define_rule(filetype.as_rc_str().clone(), entry_state, rule);

        Ok(Value::Nil)
    });
    editor.defun("red/syntax/define-category-style", |_, _, state, args| {
        // ( <SYNTAX> <CATEGORY> <FOREGROUND> <BACKGROUND> <BOLD> )
        let (filetype, category, foreground, background, bold) = match args {
            [a, b, c] => (a, b, c, &Value::Nil, &Value::Nil),
            [a, b, c, d] => (a, b, c, d, &Value::Nil),
            [a, b, c, d, e] => (a, b, c, d, e),
            _ => return Err(MachineError::InvalidArgumentCount.into()),
        };

        let filetype = StringValue::try_from_value(filetype)?;
        let category = StringValue::try_from_value(category)?;
        let foreground = if foreground.is_nil() {
            None
        } else {
            Some(Color::from_value(foreground)?)
        };
        let background = if background.is_nil() {
            None
        } else {
            Some(Color::from_value(background)?)
        };
        let bold = if bold.is_nil() {
            None
        } else {
            Some(bold.is_trueish())
        };

        let style = SyntaxStyle {
            foreground,
            background,
            bold,
        };

        let mut state = state.borrow_mut();
        state.highlight.define_category(
            filetype.as_rc_str().clone(),
            category.as_rc_str().clone(),
            style,
        );

        Ok(Value::Nil)
    });
    editor.defun("red/syntax/reset", |_, _, state, _| {
        let mut state = state.borrow_mut();
        state.buffer_mut().reset_highlight();
        Ok(Value::Nil)
    });
}

fn buffer_api(editor: &mut Editor) {
    editor.defun("red/buffer/path", |_, _, state, _| {
        let state = state.borrow();
        let path = state.buffer().path();
        if let Some(path) = path {
            Ok(Value::String(format!("{}", path.display()).into()))
        } else {
            Ok(Value::Nil)
        }
    });
    editor.defun("red/buffer/mode", |_, _, state, _| {
        let state = state.borrow();
        let (top_mode, buffer_mode) = state.mode();
        let top_mode = top_mode.as_value();
        let buffer_mode = buffer_mode.as_value();
        Ok(Value::list_or_nil([top_mode.into(), buffer_mode.into()]))
    });
    editor.defun("red/buffer/set-mode", |_, _, state, args| {
        let [mode] = args else {
            return Err(MachineError::InvalidArgumentCount.into());
        };
        let target_mode = SetMode::from_value(mode)?;
        let mut state = state.borrow_mut();
        state.set_mode(target_mode);
        Ok(Value::Nil)
    });
    editor.defun("red/buffer/write", |_, _, state, args| {
        let path = match args {
            [] => None,
            [path] => Some(StringValue::try_from_value(path)?),
            _ => return Err(MachineError::InvalidArgumentCount.into()),
        };
        let mut state = state.borrow_mut();
        state.write_buffer(path.as_deref())?;
        Ok(Value::Nil)
    });
    editor.defun("red/buffer/open", |_, _, state, args| {
        let (path, force) = match args {
            [] => return Ok(Value::Nil),
            [path] => (StringValue::try_from_value(path)?, false),
            [path, force] => (StringValue::try_from_value(path)?, force.is_trueish()),
            _ => return Err(MachineError::InvalidArgumentCount.into()),
        };

        let mut state = state.borrow_mut();
        state.open_buffer(&*path, force)?;
        Ok(Value::Nil)
    });
    editor.defun("red/buffer/move", |_, _, state, args| {
        let [movement] = args else {
            return Err(MachineError::InvalidArgumentCount.into());
        };
        let movement = Movement::from_value(movement)?;
        let mut state = state.borrow_mut();
        state.move_cursor(movement);
        Ok(Value::Nil)
    });
    editor.defun("red/buffer/insert-line-before", |_, _, state, _| {
        let mut state = state.borrow_mut();
        state.buffer_mut().newline_before();
        Ok(Value::Nil)
    });
    editor.defun("red/buffer/insert-line-after", |_, _, state, args| {
        let break_line = match args {
            [] => false,
            [arg, ..] => arg.is_trueish(),
        };
        let mut state = state.borrow_mut();
        state.buffer_mut().newline_after(break_line);
        Ok(Value::Nil)
    });
    editor.defun("red/buffer/kill-line", |_, _, state, _| {
        let mut state = state.borrow_mut();
        state.kill_current_line();
        Ok(Value::Nil)
    });
    editor.defun("red/buffer/erase-backward", |_, _, state, _| {
        let mut state = state.borrow_mut();
        state.erase_backward();
        Ok(Value::Nil)
    });
    editor.defun("red/buffer/term-cursor", |_, _, state, _| {
        let state = state.borrow();
        match state.buffer_terminal_cursor() {
            Some((x, y)) => Ok(Value::list_or_nil([x.into(), y.into()])),
            None => Ok(Value::Nil),
        }
    });
}

fn term_api(editor: &mut Editor) {
    editor.defun("term/fg-color", |_, _, state, args| {
        let [color] = args else {
            return Err(MachineError::InvalidArgumentCount.into());
        };
        let mut state = state.borrow_mut();
        let color = Color::from_value(color)?;
        state.term.set_foreground(color).ok();
        Ok(Value::Nil)
    });
    editor.defun("term/bg-color", |_, _, state, args| {
        let [color] = args else {
            return Err(MachineError::InvalidArgumentCount.into());
        };
        let mut state = state.borrow_mut();
        let color = Color::from_value(color)?;
        state.term.set_background(color).ok();
        Ok(Value::Nil)
    });
    editor.defun("term/write", |_, _, state, args| {
        use std::fmt::Write;
        let [message] = args else {
            return Err(MachineError::InvalidArgumentCount.into());
        };
        let mut state = state.borrow_mut();
        write!(state.term, "{message}").ok();
        Ok(Value::Nil)
    });
    editor.defun("term/set-cursor", |_, _, state, args| {
        let [row, column] = args else {
            return Err(MachineError::InvalidArgumentCount.into());
        };
        let row = usize::try_from_value(row)?;
        let column = usize::try_from_value(column)?;
        let mut state = state.borrow_mut();
        state.term.set_cursor_position(row, column).ok();
        Ok(Value::Nil)
    });
}

pub fn setup_env(editor: &mut Editor) {
    prelude::load(&editor.env);

    editor_api(editor);
    buffer_api(editor);
    term_api(editor);
    syntax_api(editor);
}
