"""
Keyboard Shortcuts - Configurable key bindings with leader key support

Provides keyboard shortcut handling for the CLI:
- Leader key pattern (e.g., Ctrl+X followed by another key)
- Configurable key bindings from config
- Built-in actions (help, save, new, edit)
- Action callback system

Examples:
    # Create keybinding manager
    manager = KeybindingManager()

    # Register actions
    manager.register_action("help", lambda: print("Help"))
    manager.register_action("save", lambda: save_session())

    # Handle key sequence
    result = manager.handle_key("ctrl+x")
    if result.waiting_for_chord:
        # Leader key pressed, waiting for second key
        result = manager.handle_key("h")  # Triggers help
"""

from dataclasses import dataclass, field
from enum import Enum
from typing import Callable, Optional


class KeyAction(Enum):
    """Built-in keyboard actions"""

    HELP = "help"
    SAVE = "save"
    NEW = "new"
    EDIT = "edit"
    QUIT = "quit"
    CLEAR = "clear"
    HISTORY = "history"
    CANCEL = "cancel"


@dataclass
class KeySequence:
    """A parsed key sequence"""

    raw: str
    modifiers: set[str] = field(default_factory=set)
    key: str = ""

    @classmethod
    def parse(cls, key_string: str) -> "KeySequence":
        """
        Parse a key string like "ctrl+x" or "alt+shift+a"

        Args:
            key_string: Key string to parse

        Returns:
            KeySequence object
        """
        key_string = key_string.lower().strip()
        parts = key_string.split("+")

        modifiers = set()
        key = ""

        for part in parts:
            part = part.strip()
            if part in ("ctrl", "control", "alt", "meta", "shift", "super"):
                # Normalize modifier names
                if part == "control":
                    part = "ctrl"
                modifiers.add(part)
            else:
                key = part

        return cls(raw=key_string, modifiers=modifiers, key=key)

    def matches(self, other: "KeySequence") -> bool:
        """Check if this sequence matches another"""
        return self.modifiers == other.modifiers and self.key == other.key

    def __str__(self) -> str:
        """String representation"""
        parts = sorted(self.modifiers) + [self.key] if self.key else sorted(self.modifiers)
        return "+".join(parts)

    def __hash__(self) -> int:
        return hash((frozenset(self.modifiers), self.key))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, KeySequence):
            return False
        return self.modifiers == other.modifiers and self.key == other.key


@dataclass
class Keybinding:
    """A keyboard shortcut binding"""

    sequence: list[KeySequence]  # Multi-key sequence (e.g., [Ctrl+X, H])
    action: str
    description: str = ""

    @classmethod
    def parse(cls, key_string: str, action: str, description: str = "") -> "Keybinding":
        """
        Parse a keybinding like "ctrl+x h" (two-key chord)

        Args:
            key_string: Space-separated key sequence
            action: Action name
            description: Human-readable description

        Returns:
            Keybinding object
        """
        parts = key_string.strip().split()
        sequence = [KeySequence.parse(part) for part in parts]
        return cls(sequence=sequence, action=action, description=description)

    def matches_partial(self, keys: list[KeySequence]) -> bool:
        """Check if keys match the start of this binding"""
        if len(keys) > len(self.sequence):
            return False
        return all(k.matches(s) for k, s in zip(keys, self.sequence))

    def matches_full(self, keys: list[KeySequence]) -> bool:
        """Check if keys fully match this binding"""
        return len(keys) == len(self.sequence) and self.matches_partial(keys)


@dataclass
class KeyHandleResult:
    """Result of handling a key press"""

    action: Optional[str] = None
    executed: bool = False
    waiting_for_chord: bool = False
    partial_match: bool = False
    error: Optional[str] = None


class KeybindingManager:
    """
    Manage keyboard shortcuts with leader key support

    Provides configurable key bindings with support for multi-key
    sequences like Ctrl+X followed by H.

    Example:
        manager = KeybindingManager()

        # Register callback
        manager.register_action("help", show_help)

        # Handle key presses
        result = manager.handle_key("ctrl+x")  # waiting_for_chord=True
        result = manager.handle_key("h")  # Executes help
    """

    # Default keybindings with Ctrl+X leader
    DEFAULT_KEYBINDINGS: dict[str, str] = {
        "ctrl+x h": "help",
        "ctrl+x e": "edit",
        "ctrl+x s": "save",
        "ctrl+x n": "new",
        "ctrl+x q": "quit",
        "ctrl+c": "cancel",
        "ctrl+l": "clear",
    }

    DEFAULT_DESCRIPTIONS: dict[str, str] = {
        "help": "Show help information",
        "edit": "Open in external editor",
        "save": "Save current session",
        "new": "Start new session",
        "quit": "Quit the application",
        "cancel": "Cancel current operation",
        "clear": "Clear the screen",
    }

    def __init__(
        self,
        keybindings: Optional[dict[str, str]] = None,
        leader_timeout: float = 2.0,
    ):
        """
        Initialize keybinding manager

        Args:
            keybindings: Custom keybindings (key_sequence -> action)
            leader_timeout: Timeout for multi-key sequences
        """
        self.leader_timeout = leader_timeout
        self._bindings: list[Keybinding] = []
        self._actions: dict[str, Callable[[], None]] = {}
        self._current_sequence: list[KeySequence] = []

        # Load default or custom keybindings
        bindings = keybindings or self.DEFAULT_KEYBINDINGS.copy()
        for key_str, action in bindings.items():
            desc = self.DEFAULT_DESCRIPTIONS.get(action, "")
            self.add_binding(key_str, action, desc)

    def add_binding(self, key_string: str, action: str, description: str = "") -> None:
        """
        Add a keybinding

        Args:
            key_string: Key sequence (e.g., "ctrl+x h")
            action: Action name
            description: Human-readable description
        """
        binding = Keybinding.parse(key_string, action, description)
        # Remove existing binding for same sequence
        self._bindings = [b for b in self._bindings if not b.matches_full(binding.sequence)]
        self._bindings.append(binding)

    def remove_binding(self, key_string: str) -> bool:
        """
        Remove a keybinding

        Args:
            key_string: Key sequence to remove

        Returns:
            True if binding was removed
        """
        binding = Keybinding.parse(key_string, "")
        before = len(self._bindings)
        self._bindings = [b for b in self._bindings if not b.matches_full(binding.sequence)]
        return len(self._bindings) < before

    def register_action(self, action: str, callback: Callable[[], None]) -> None:
        """
        Register an action callback

        Args:
            action: Action name
            callback: Function to call when action is triggered
        """
        self._actions[action] = callback

    def unregister_action(self, action: str) -> bool:
        """
        Unregister an action callback

        Args:
            action: Action name

        Returns:
            True if action was unregistered
        """
        if action in self._actions:
            del self._actions[action]
            return True
        return False

    def handle_key(self, key_string: str) -> KeyHandleResult:
        """
        Handle a key press

        Args:
            key_string: Key that was pressed

        Returns:
            KeyHandleResult with action status
        """
        key = KeySequence.parse(key_string)
        self._current_sequence.append(key)

        # Check for exact matches
        for binding in self._bindings:
            if binding.matches_full(self._current_sequence):
                action = binding.action
                self._current_sequence = []

                # Execute action if registered
                if action in self._actions:
                    try:
                        self._actions[action]()
                        return KeyHandleResult(action=action, executed=True)
                    except Exception as e:
                        return KeyHandleResult(action=action, executed=False, error=str(e))
                else:
                    return KeyHandleResult(action=action, executed=False)

        # Check for partial matches (leader key pressed)
        partial_matches = [b for b in self._bindings if b.matches_partial(self._current_sequence)]

        if partial_matches:
            return KeyHandleResult(
                waiting_for_chord=True,
                partial_match=True,
            )

        # No match - reset sequence
        self._current_sequence = []
        return KeyHandleResult()

    def reset_sequence(self) -> None:
        """Reset the current key sequence"""
        self._current_sequence = []

    def get_current_sequence(self) -> list[KeySequence]:
        """Get the current partial key sequence"""
        return list(self._current_sequence)

    def is_waiting_for_chord(self) -> bool:
        """Check if waiting for chord completion"""
        return len(self._current_sequence) > 0

    def get_bindings(self) -> list[Keybinding]:
        """Get all registered bindings"""
        return list(self._bindings)

    def get_binding_for_action(self, action: str) -> Optional[Keybinding]:
        """Get the keybinding for an action"""
        for binding in self._bindings:
            if binding.action == action:
                return binding
        return None

    def get_help_text(self) -> str:
        """
        Get formatted help text for all bindings

        Returns:
            Formatted string with all keybindings
        """
        lines = ["Keyboard Shortcuts:", ""]

        # Group by leader key
        leader_bindings: dict[str, list[Keybinding]] = {}
        single_bindings: list[Keybinding] = []

        for binding in sorted(self._bindings, key=lambda b: b.action):
            if len(binding.sequence) > 1:
                leader = str(binding.sequence[0])
                if leader not in leader_bindings:
                    leader_bindings[leader] = []
                leader_bindings[leader].append(binding)
            else:
                single_bindings.append(binding)

        # Format leader key bindings
        for leader, bindings in sorted(leader_bindings.items()):
            lines.append(f"  {leader.upper()} (Leader key):")
            for binding in bindings:
                key_str = " ".join(str(k) for k in binding.sequence)
                desc = binding.description or binding.action
                lines.append(f"    {key_str:<20} {desc}")
            lines.append("")

        # Format single key bindings
        if single_bindings:
            lines.append("  Single key shortcuts:")
            for binding in single_bindings:
                key_str = " ".join(str(k) for k in binding.sequence)
                desc = binding.description or binding.action
                lines.append(f"    {key_str:<20} {desc}")

        return "\n".join(lines)

    def load_from_config(self, config: dict) -> None:
        """
        Load keybindings from config dictionary

        Args:
            config: Config with "keybind" section

        Example config:
            {
                "keybind": {
                    "help": "ctrl+x h",
                    "save": "ctrl+x s"
                }
            }
        """
        keybind_config = config.get("keybind", {})

        for action, key_string in keybind_config.items():
            desc = self.DEFAULT_DESCRIPTIONS.get(action, "")
            self.add_binding(key_string, action, desc)


# Convenience functions


def create_keybinding_manager(
    config: Optional[dict] = None,
) -> KeybindingManager:
    """
    Create a keybinding manager with optional config

    Args:
        config: Optional config dictionary

    Returns:
        Configured KeybindingManager
    """
    manager = KeybindingManager()
    if config:
        manager.load_from_config(config)
    return manager


def parse_key_sequence(key_string: str) -> list[KeySequence]:
    """
    Parse a key sequence string (convenience function)

    Args:
        key_string: Space-separated key sequence

    Returns:
        List of KeySequence objects
    """
    parts = key_string.strip().split()
    return [KeySequence.parse(part) for part in parts]
