use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use hjkl_engine::{Input, Key, VimMode};
use std::collections::BTreeMap;

use super::App;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum MapMode {
    Normal,
    Visual,
    Insert,
    OperatorPending,
    CommandLine,
    Terminal,
}

#[derive(Debug, Clone)]
struct Mapping {
    lhs: Vec<Input>,
    rhs: Vec<Input>,
    recursive: bool,
}

#[derive(Debug, Default, Clone)]
pub(crate) struct RuntimeKeymaps {
    maps: BTreeMap<MapMode, Vec<Mapping>>,
    pending: Vec<Input>,
    pending_mode: Option<MapMode>,
}

impl RuntimeKeymaps {
    fn mappings_mut(&mut self, mode: MapMode) -> &mut Vec<Mapping> {
        self.maps.entry(mode).or_default()
    }

    fn mappings(&self, mode: MapMode) -> &[Mapping] {
        self.maps.get(&mode).map(Vec::as_slice).unwrap_or(&[])
    }

    fn clear_pending(&mut self) {
        self.pending.clear();
        self.pending_mode = None;
    }

    pub(crate) fn add(
        &mut self,
        modes: &[MapMode],
        lhs: Vec<Input>,
        rhs: Vec<Input>,
        recursive: bool,
    ) {
        for &mode in modes {
            let list = self.mappings_mut(mode);
            list.retain(|m| m.lhs != lhs);
            list.push(Mapping {
                lhs: lhs.clone(),
                rhs: rhs.clone(),
                recursive,
            });
        }
    }

    pub(crate) fn remove(&mut self, modes: &[MapMode], lhs: &[Input]) -> bool {
        let mut removed = false;
        for &mode in modes {
            if let Some(list) = self.maps.get_mut(&mode) {
                let before = list.len();
                list.retain(|m| m.lhs.as_slice() != lhs);
                removed |= list.len() != before;
            }
        }
        removed
    }

    pub(crate) fn clear(&mut self, modes: &[MapMode]) {
        for &mode in modes {
            self.maps.remove(&mode);
        }
        self.clear_pending();
    }

    pub(crate) fn list(&self, modes: &[MapMode]) -> String {
        let mut lines = Vec::new();
        for &mode in modes {
            let label = match mode {
                MapMode::Normal => "normal",
                MapMode::Visual => "visual",
                MapMode::Insert => "insert",
                MapMode::OperatorPending => "operator-pending",
                MapMode::CommandLine => "command-line",
                MapMode::Terminal => "terminal",
            };
            lines.push(format!("[{label}]"));
            let mut entries: Vec<&Mapping> = self.mappings(mode).iter().collect();
            entries.sort_by(|a, b| {
                a.lhs
                    .len()
                    .cmp(&b.lhs.len())
                    .then_with(|| a.rhs.len().cmp(&b.rhs.len()))
            });
            if entries.is_empty() {
                lines.push("  (none)".into());
                continue;
            }
            for mapping in entries {
                lines.push(format!(
                    "  {} {} {}",
                    if mapping.recursive { "map" } else { "noremap" },
                    display_keys(&mapping.lhs),
                    display_keys(&mapping.rhs)
                ));
            }
        }
        lines.join("\n")
    }

    pub(crate) fn translate(&mut self, mode: Option<MapMode>, input: Input) -> Option<Vec<Input>> {
        let Some(mode) = mode else {
            self.clear_pending();
            return Some(vec![input]);
        };

        if self.pending_mode != Some(mode) {
            self.clear_pending();
            self.pending_mode = Some(mode);
        }

        self.pending.push(input);
        let mut out = Vec::new();
        let mut guard = 0usize;

        loop {
            guard += 1;
            if guard > 1024 {
                out.append(&mut self.pending);
                break;
            }

            if self.pending.is_empty() {
                break;
            }

            if let Some(mapping) = self.find_exact(mode) {
                let lhs_len = mapping.lhs.len();
                self.pending.drain(..lhs_len);
                if mapping.recursive {
                    for rhs in mapping.rhs.into_iter().rev() {
                        self.pending.insert(0, rhs);
                    }
                } else {
                    out.extend(mapping.rhs);
                }
                continue;
            }

            if self.has_prefix(mode) {
                return None;
            }

            out.push(self.pending.remove(0));
        }

        Some(out)
    }

    fn find_exact(&self, mode: MapMode) -> Option<Mapping> {
        let mut best: Option<Mapping> = None;
        for mapping in self.mappings(mode) {
            if mapping.lhs == self.pending
                && best
                    .as_ref()
                    .is_none_or(|cur| mapping.lhs.len() >= cur.lhs.len())
            {
                best = Some(mapping.clone());
            }
        }
        best
    }

    fn has_prefix(&self, mode: MapMode) -> bool {
        self.mappings(mode)
            .iter()
            .any(|mapping| mapping.lhs.starts_with(&self.pending))
    }
}

pub(crate) fn map_mode_for_vim(mode: VimMode) -> Option<MapMode> {
    match mode {
        VimMode::Normal => Some(MapMode::Normal),
        VimMode::Insert => Some(MapMode::Insert),
        VimMode::Visual | VimMode::VisualLine | VimMode::VisualBlock => Some(MapMode::Visual),
    }
}

pub(crate) fn key_event_to_input(key: KeyEvent) -> Input {
    key.into()
}

pub(crate) fn input_to_key_event(input: Input) -> KeyEvent {
    let code = match input.key {
        Key::Char(c) => KeyCode::Char(c),
        Key::Backspace => KeyCode::Backspace,
        Key::Enter => KeyCode::Enter,
        Key::Left => KeyCode::Left,
        Key::Right => KeyCode::Right,
        Key::Up => KeyCode::Up,
        Key::Down => KeyCode::Down,
        Key::Tab => KeyCode::Tab,
        Key::Delete => KeyCode::Delete,
        Key::Home => KeyCode::Home,
        Key::End => KeyCode::End,
        Key::PageUp => KeyCode::PageUp,
        Key::PageDown => KeyCode::PageDown,
        Key::Esc => KeyCode::Esc,
        Key::Null => KeyCode::Null,
    };

    let mut modifiers = KeyModifiers::NONE;
    if input.ctrl {
        modifiers |= KeyModifiers::CONTROL;
    }
    if input.alt {
        modifiers |= KeyModifiers::ALT;
    }
    if input.shift {
        modifiers |= KeyModifiers::SHIFT;
    }

    KeyEvent::new(code, modifiers)
}

pub(crate) fn parse_key_sequence(text: &str, leader: char) -> Vec<Input> {
    let mut normalized = String::new();
    let mut chars = text.chars().peekable();
    while let Some(ch) = chars.next() {
        if ch != '<' {
            normalized.push(ch);
            continue;
        }

        let mut tag = String::new();
        let mut closed = false;
        for next in chars.by_ref() {
            if next == '>' {
                closed = true;
                break;
            }
            tag.push(next);
        }

        if !closed {
            normalized.push('<');
            normalized.push_str(&tag);
            break;
        }

        match tag.to_ascii_lowercase().as_str() {
            "leader" => normalized.push(leader),
            "space" => normalized.push(' '),
            "cr" => normalized.push_str("<CR>"),
            "esc" => normalized.push_str("<Esc>"),
            "tab" => normalized.push_str("<Tab>"),
            "bs" => normalized.push_str("<BS>"),
            "lt" => normalized.push_str("<lt>"),
            "del" => normalized.push_str("<Del>"),
            "home" => normalized.push_str("<Home>"),
            "end" => normalized.push_str("<End>"),
            "pageup" => normalized.push_str("<PageUp>"),
            "pagedown" => normalized.push_str("<PageDown>"),
            "up" => normalized.push_str("<Up>"),
            "down" => normalized.push_str("<Down>"),
            "left" => normalized.push_str("<Left>"),
            "right" => normalized.push_str("<Right>"),
            _ => {
                normalized.push('<');
                normalized.push_str(&tag);
                normalized.push('>');
            }
        }
    }

    hjkl_engine::decode_macro(&normalized)
}

pub(crate) fn parse_mode_groups(cmd: &str) -> Option<Vec<MapMode>> {
    match cmd {
        "map" | "noremap" | "nm" => Some(vec![
            MapMode::Normal,
            MapMode::Visual,
            MapMode::OperatorPending,
        ]),
        "nmap" | "nnoremap" | "nunmap" | "nmapclear" => Some(vec![MapMode::Normal]),
        "vmap" | "vnoremap" | "vunmap" | "vmapclear" => Some(vec![MapMode::Visual]),
        "xmap" | "xnoremap" | "xunmap" | "xmapclear" => Some(vec![MapMode::Visual]),
        "imap" | "inoremap" | "iunmap" | "imapclear" => Some(vec![MapMode::Insert]),
        "omap" | "onoremap" | "ounmap" | "omapclear" => Some(vec![MapMode::OperatorPending]),
        "cmap" | "cnoremap" | "cunmap" | "cmapclear" => Some(vec![MapMode::CommandLine]),
        "tmap" | "tnoremap" | "tunmap" | "tmapclear" => Some(vec![MapMode::Terminal]),
        "unmap" | "mapclear" => Some(vec![
            MapMode::Normal,
            MapMode::Visual,
            MapMode::OperatorPending,
            MapMode::Insert,
            MapMode::CommandLine,
            MapMode::Terminal,
        ]),
        _ => None,
    }
}

pub(crate) enum RuntimeMapCommand {
    Add {
        modes: Vec<MapMode>,
        recursive: bool,
        lhs: Vec<Input>,
        rhs: Vec<Input>,
    },
    Remove {
        modes: Vec<MapMode>,
        lhs: Vec<Input>,
    },
    Clear {
        modes: Vec<MapMode>,
    },
    List {
        modes: Vec<MapMode>,
    },
}

pub(crate) fn parse_runtime_map_command(cmd: &str, leader: char) -> Option<RuntimeMapCommand> {
    let cmd = cmd.trim();
    let split = cmd
        .char_indices()
        .find(|(_, c)| c.is_whitespace())
        .map(|(i, _)| i)
        .unwrap_or(cmd.len());
    let (name, rest) = cmd.split_at(split);
    let rest = rest.trim();
    let modes = parse_mode_groups(name)?;

    if name.ends_with("clear") {
        return Some(RuntimeMapCommand::Clear { modes });
    }

    let is_remove = name.ends_with("unmap");
    if rest.is_empty() {
        return Some(RuntimeMapCommand::List { modes });
    }

    let split = rest
        .char_indices()
        .find(|(_, c)| c.is_whitespace())
        .map(|(i, _)| i)
        .unwrap_or(rest.len());
    let (lhs, rhs) = rest.split_at(split);
    let lhs = lhs.trim();
    let rhs = rhs.trim();
    let lhs = parse_key_sequence(lhs, leader);
    if is_remove {
        return Some(RuntimeMapCommand::Remove { modes, lhs });
    }
    let recursive = !matches!(
        name,
        "noremap"
            | "nnoremap"
            | "vnoremap"
            | "xnoremap"
            | "inoremap"
            | "onoremap"
            | "cnoremap"
            | "tnoremap"
            | "nm"
    );
    let rhs = parse_key_sequence(rhs, leader);
    Some(RuntimeMapCommand::Add {
        modes,
        recursive,
        lhs,
        rhs,
    })
}

fn display_keys(keys: &[Input]) -> String {
    let mut out = String::new();
    for input in keys {
        match input.key {
            Key::Char(c) if input.ctrl => {
                out.push_str("<C-");
                out.push(c);
                out.push('>');
            }
            Key::Char(c) if input.alt => {
                out.push_str("<M-");
                out.push(c);
                out.push('>');
            }
            Key::Char('<') => out.push_str("<lt>"),
            Key::Char(c) => out.push(c),
            Key::Esc => out.push_str("<Esc>"),
            Key::Enter => out.push_str("<CR>"),
            Key::Backspace => out.push_str("<BS>"),
            Key::Tab => out.push_str("<Tab>"),
            Key::Up => out.push_str("<Up>"),
            Key::Down => out.push_str("<Down>"),
            Key::Left => out.push_str("<Left>"),
            Key::Right => out.push_str("<Right>"),
            Key::Delete => out.push_str("<Del>"),
            Key::Home => out.push_str("<Home>"),
            Key::End => out.push_str("<End>"),
            Key::PageUp => out.push_str("<PageUp>"),
            Key::PageDown => out.push_str("<PageDown>"),
            Key::Null => {}
        }
    }
    out
}

impl App {
    pub(crate) fn runtime_map_mode(&self) -> Option<MapMode> {
        map_mode_for_vim(self.active().editor.vim_mode())
    }

    pub(crate) fn apply_runtime_map(&mut self, key: KeyEvent) -> Option<Vec<KeyEvent>> {
        let input = key_event_to_input(key);
        let mode = self.runtime_map_mode();
        let expanded = self.runtime_keymaps.translate(mode, input)?;
        Some(expanded.into_iter().map(input_to_key_event).collect())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn ch(c: char) -> Input {
        Input {
            key: Key::Char(c),
            ..Input::default()
        }
    }

    #[test]
    fn parse_leader_and_cr_tags() {
        let keys = parse_key_sequence("<leader>w<CR>", '\\');
        assert_eq!(
            keys,
            vec![
                ch('\\'),
                ch('w'),
                Input {
                    key: Key::Enter,
                    ..Input::default()
                }
            ]
        );
    }

    #[test]
    fn recursive_mapping_expands_rhs_again() {
        let mut km = RuntimeKeymaps::default();
        km.add(&[MapMode::Normal], vec![ch('a')], vec![ch('b')], true);
        km.add(&[MapMode::Normal], vec![ch('b')], vec![ch('c')], true);
        let out = km.translate(Some(MapMode::Normal), ch('a')).unwrap();
        assert_eq!(out, vec![ch('c')]);
    }

    #[test]
    fn noremap_keeps_rhs_literal() {
        let mut km = RuntimeKeymaps::default();
        km.add(&[MapMode::Normal], vec![ch('a')], vec![ch('b')], false);
        km.add(&[MapMode::Normal], vec![ch('b')], vec![ch('c')], true);
        let out = km.translate(Some(MapMode::Normal), ch('a')).unwrap();
        assert_eq!(out, vec![ch('b')]);
    }
}
