use anyhow::{Result, anyhow};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use serde::Deserialize;
use std::sync::{Arc, LazyLock};

use super::input::KeyAction;
use crate::keymap_spec;

macro_rules! keymap_source {
    () => {
        include_str!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/keymap/default.keymap.json"
        ))
    };
}

pub(super) use keymap_source;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum KeymapContext {
    Default,
    Collection,
    Overlay,
    Popup,
    Help,
    TextInput,
    NumericInput,
    BooleanInput,
    EnumInput,
    MultiSelectInput,
    CompositeInput,
    ArrayBufferInput,
}

impl KeymapContext {
    fn from_str(raw: &str) -> Option<Self> {
        match raw {
            "default" => Some(KeymapContext::Default),
            "collection" => Some(KeymapContext::Collection),
            "overlay" => Some(KeymapContext::Overlay),
            "popup" => Some(KeymapContext::Popup),
            "help" => Some(KeymapContext::Help),
            "text" | "textInput" | "text-input" => Some(KeymapContext::TextInput),
            "numeric" | "numericInput" | "numeric-input" => Some(KeymapContext::NumericInput),
            "boolean" | "booleanInput" | "boolean-input" => Some(KeymapContext::BooleanInput),
            "enum" | "enumInput" | "enum-input" => Some(KeymapContext::EnumInput),
            "multiSelect" | "multi-select" | "multiSelectInput" | "multi-select-input" => {
                Some(KeymapContext::MultiSelectInput)
            }
            "composite" | "compositeInput" | "composite-input" => {
                Some(KeymapContext::CompositeInput)
            }
            "arrayBuffer" | "array-buffer" | "arrayBufferInput" | "array-buffer-input" => {
                Some(KeymapContext::ArrayBufferInput)
            }
            _ => None,
        }
    }
}

#[derive(Deserialize)]
struct RawEntry {
    id: String,
    description: String,
    contexts: Vec<String>,
    #[serde(default = "default_dispatch")]
    dispatch: bool,
    action: RawAction,
    combos: Vec<String>,
}

#[derive(Deserialize)]
#[serde(tag = "kind", rename_all = "camelCase")]
enum RawAction {
    Save,
    Quit,
    ResetStatus,
    TogglePopup,
    EditComposite,
    ShowHelp,
    HelpClose,
    HelpPageStep { delta: i32 },
    HelpShortcutScroll { delta: i32 },
    HelpShortcutPage { delta: i32 },
    HelpShortcutHome,
    HelpShortcutEnd,
    HelpErrorScroll { delta: i32 },
    PopupClose,
    PopupSelect { delta: i32 },
    PopupToggle,
    PopupApply,
    FieldStep { delta: i32 },
    SectionStep { delta: i32 },
    RootStep { delta: i32 },
    ListAddEntry,
    ListRemoveEntry,
    ListMove { delta: i32 },
    ListSelect { delta: i32 },
    None,
}

#[derive(Clone, Debug)]
struct KeyBinding {
    action: KeyAction,
    contexts: Vec<KeymapContext>,
    combos: Vec<KeyPattern>,
    snippet: String,
    dispatch: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct HelpEntry {
    pub(crate) keys: String,
    pub(crate) action: String,
}

impl KeyBinding {
    fn from_raw(raw: RawEntry) -> Result<Self> {
        let contexts = raw
            .contexts
            .iter()
            .filter_map(|ctx| KeymapContext::from_str(ctx))
            .collect::<Vec<_>>();
        if contexts.is_empty() {
            return Err(anyhow!("keymap entry {} must declare contexts", raw.id));
        }
        let action = raw.action.into_action();
        let mut combos = Vec::with_capacity(raw.combos.len());
        for combo in raw.combos {
            let pattern = KeyPattern::parse(&combo)
                .map_err(|err| anyhow!("failed to parse combo '{combo}' for {}: {err}", raw.id))?;
            combos.push(pattern);
        }
        if combos.is_empty() {
            return Err(anyhow!("keymap entry {} must declare combos", raw.id));
        }
        let combos_display = combos
            .iter()
            .map(|pattern| pattern.display.clone())
            .collect::<Vec<_>>()
            .join("/");
        let snippet = format!("{combos_display} -> {}", raw.description);
        Ok(Self {
            action,
            contexts,
            combos,
            snippet,
            dispatch: raw.dispatch,
        })
    }

    fn classify(&self, key: &KeyEvent) -> Option<KeyAction> {
        if !self.dispatch {
            return None;
        }
        if !self.contexts.iter().any(|context| {
            matches!(
                context,
                KeymapContext::Default | KeymapContext::Collection | KeymapContext::Overlay
            )
        }) {
            return None;
        }
        self.combos
            .iter()
            .find(|pattern| pattern.matches(key))
            .map(|_| self.action)
    }

    fn classify_for_contexts(
        &self,
        key: &KeyEvent,
        contexts: &[KeymapContext],
    ) -> Option<KeyAction> {
        if !self.dispatch
            || !self
                .contexts
                .iter()
                .any(|context| contexts.contains(context))
        {
            return None;
        }
        self.combos
            .iter()
            .find(|pattern| pattern.matches(key))
            .map(|_| self.action)
    }
}

#[derive(Clone, Debug)]
struct KeyPattern {
    matcher: CodeMatcher,
    required: KeyModifiers,
    allow_shift: bool,
    display: String,
}

impl KeyPattern {
    fn parse(spec: &str) -> Result<Self, String> {
        let display = spec.trim().to_string();
        if display.is_empty() {
            return Err("combo cannot be empty".into());
        }
        let mut tokens = display
            .split('+')
            .map(|t| t.trim())
            .filter(|t| !t.is_empty())
            .collect::<Vec<_>>();
        if tokens.is_empty() {
            return Err("combo must contain key".into());
        }
        let key_token = tokens.pop().unwrap();
        let matcher = CodeMatcher::from_token(key_token)?;
        let mut required = KeyModifiers::empty();
        for token in tokens {
            match token.to_lowercase().as_str() {
                "ctrl" | "control" => required |= KeyModifiers::CONTROL,
                "shift" => required |= KeyModifiers::SHIFT,
                "alt" => required |= KeyModifiers::ALT,
                other => {
                    return Err(format!("unsupported modifier '{other}'"));
                }
            }
        }
        let allow_shift = matcher.allows_extra_shift() && !required.contains(KeyModifiers::SHIFT);
        Ok(Self {
            matcher,
            required,
            allow_shift,
            display,
        })
    }

    fn matches(&self, key: &KeyEvent) -> bool {
        if !self.matcher.matches(&key.code) {
            return false;
        }
        if !modifiers_include(key.modifiers, self.required) {
            return false;
        }
        let extra = remove_modifiers(key.modifiers, self.required);
        if self.allow_shift {
            let tolerated = extra & !KeyModifiers::SHIFT;
            tolerated.is_empty()
        } else {
            extra.is_empty()
        }
    }
}

#[derive(Clone, Debug)]
enum CodeMatcher {
    Literal(KeyCode),
    Alpha(char),
}

impl CodeMatcher {
    fn from_token(token: &str) -> Result<Self, String> {
        let normalized = token.to_lowercase();
        let matcher = match normalized.as_str() {
            "tab" => CodeMatcher::Literal(KeyCode::Tab),
            "backtab" | "shift+tab" => CodeMatcher::Literal(KeyCode::BackTab),
            "space" => CodeMatcher::Literal(KeyCode::Char(' ')),
            "enter" => CodeMatcher::Literal(KeyCode::Enter),
            "esc" | "escape" => CodeMatcher::Literal(KeyCode::Esc),
            "left" => CodeMatcher::Literal(KeyCode::Left),
            "right" => CodeMatcher::Literal(KeyCode::Right),
            "up" => CodeMatcher::Literal(KeyCode::Up),
            "down" => CodeMatcher::Literal(KeyCode::Down),
            "home" => CodeMatcher::Literal(KeyCode::Home),
            "end" => CodeMatcher::Literal(KeyCode::End),
            "pageup" => CodeMatcher::Literal(KeyCode::PageUp),
            "pagedown" => CodeMatcher::Literal(KeyCode::PageDown),
            "delete" | "del" => CodeMatcher::Literal(KeyCode::Delete),
            "backspace" => CodeMatcher::Literal(KeyCode::Backspace),
            other => {
                if other.len() == 1 {
                    CodeMatcher::Alpha(other.chars().next().unwrap())
                } else {
                    return Err(format!("unsupported key '{token}'"));
                }
            }
        };
        Ok(matcher)
    }

    fn matches(&self, code: &KeyCode) -> bool {
        match (self, code) {
            (CodeMatcher::Literal(expected), actual) => actual == expected,
            (CodeMatcher::Alpha(expected), KeyCode::Char(actual)) => {
                actual.to_ascii_lowercase() == *expected
            }
            _ => false,
        }
    }

    fn allows_extra_shift(&self) -> bool {
        matches!(
            self,
            CodeMatcher::Alpha(_) | CodeMatcher::Literal(KeyCode::BackTab)
        )
    }
}

impl RawAction {
    fn into_action(self) -> KeyAction {
        match self {
            RawAction::Save => KeyAction::Save,
            RawAction::Quit => KeyAction::Quit,
            RawAction::ResetStatus => KeyAction::ResetStatus,
            RawAction::TogglePopup => KeyAction::TogglePopup,
            RawAction::EditComposite => KeyAction::EditComposite,
            RawAction::ShowHelp => KeyAction::ShowHelp,
            RawAction::HelpClose => KeyAction::HelpClose,
            RawAction::HelpPageStep { delta } => KeyAction::HelpPageStep(delta),
            RawAction::HelpShortcutScroll { delta } => KeyAction::HelpShortcutScroll(delta),
            RawAction::HelpShortcutPage { delta } => KeyAction::HelpShortcutPage(delta),
            RawAction::HelpShortcutHome => KeyAction::HelpShortcutHome,
            RawAction::HelpShortcutEnd => KeyAction::HelpShortcutEnd,
            RawAction::HelpErrorScroll { delta } => KeyAction::HelpErrorScroll(delta),
            RawAction::PopupClose => KeyAction::PopupClose,
            RawAction::PopupSelect { delta } => KeyAction::PopupSelect(delta),
            RawAction::PopupToggle => KeyAction::PopupToggle,
            RawAction::PopupApply => KeyAction::PopupApply,
            RawAction::FieldStep { delta } => KeyAction::FieldStep(delta),
            RawAction::SectionStep { delta } => KeyAction::SectionStep(delta),
            RawAction::RootStep { delta } => KeyAction::RootStep(delta),
            RawAction::ListAddEntry => KeyAction::ListAddEntry,
            RawAction::ListRemoveEntry => KeyAction::ListRemoveEntry,
            RawAction::ListMove { delta } => KeyAction::ListMove(delta),
            RawAction::ListSelect { delta } => KeyAction::ListSelect(delta),
            RawAction::None => KeyAction::None,
        }
    }
}

#[derive(Debug, Clone)]
pub(crate) struct KeymapStore {
    bindings: Arc<Vec<KeyBinding>>,
}

impl KeymapStore {
    pub fn from_json(raw: &str) -> Result<Self> {
        let entries: Vec<RawEntry> = serde_json::from_str(raw)?;
        Self::from_entries(entries)
    }

    pub fn builtin() -> Self {
        Self::from_json(keymap_source!()).expect("invalid keymap/default.keymap.json")
    }

    fn from_entries(entries: Vec<RawEntry>) -> Result<Self> {
        keymap_spec::validate_unique_ids(entries.iter().map(|entry| entry.id.as_str()))
            .map_err(|err| anyhow!(err))?;
        let mut bindings = Vec::with_capacity(entries.len());
        for entry in entries {
            bindings.push(KeyBinding::from_raw(entry)?);
        }
        Ok(Self {
            bindings: Arc::new(bindings),
        })
    }

    pub fn classify(&self, key: &KeyEvent) -> Option<KeyAction> {
        self.bindings
            .iter()
            .find_map(|binding| binding.classify(key))
    }

    pub fn classify_for_contexts(
        &self,
        key: &KeyEvent,
        contexts: &[KeymapContext],
    ) -> Option<KeyAction> {
        self.bindings
            .iter()
            .find_map(|binding| binding.classify_for_contexts(key, contexts))
    }

    pub fn help_text(&self, context: KeymapContext) -> Option<String> {
        self.help_text_for_contexts(&[context])
    }

    pub fn help_text_for_contexts(&self, contexts: &[KeymapContext]) -> Option<String> {
        let snippets = self
            .bindings
            .iter()
            .filter(|binding| {
                contexts
                    .iter()
                    .any(|context| binding.contexts.contains(context))
            })
            .map(|binding| binding.snippet.clone())
            .collect::<Vec<_>>();
        if snippets.is_empty() {
            None
        } else {
            Some(snippets.join(" • "))
        }
    }

    pub(crate) fn help_entries(&self, context: KeymapContext) -> Vec<HelpEntry> {
        self.help_entries_for_contexts(&[context])
    }

    pub(crate) fn help_entries_for_contexts(&self, contexts: &[KeymapContext]) -> Vec<HelpEntry> {
        self.bindings
            .iter()
            .filter(|binding| {
                contexts
                    .iter()
                    .any(|context| binding.contexts.contains(context))
            })
            .map(|binding| {
                let (keys, action) = match binding.snippet.split_once("->") {
                    Some((keys, action)) => (keys.trim().to_string(), action.trim().to_string()),
                    None => (binding.snippet.clone(), String::new()),
                };
                HelpEntry { keys, action }
            })
            .collect()
    }
}

static DEFAULT_STORE: LazyLock<Arc<KeymapStore>> =
    LazyLock::new(|| Arc::new(KeymapStore::builtin()));

pub(crate) fn default_store() -> Arc<KeymapStore> {
    DEFAULT_STORE.clone()
}

fn modifiers_include(actual: KeyModifiers, required: KeyModifiers) -> bool {
    actual.contains(required)
}

fn remove_modifiers(actual: KeyModifiers, required: KeyModifiers) -> KeyModifiers {
    KeyModifiers::from_bits_truncate(actual.bits() & !required.bits())
}

fn default_dispatch() -> bool {
    true
}
