"""
Command Palette - Searchable command interface (Ctrl+P)

Provides a fuzzy-searchable command palette similar to VS Code / OpenCode.
Users can quickly access commands, switch contexts, and perform actions.

Features:
- Fuzzy search across all commands
- Category grouping (Suggested, Session, Agent, Provider, System)
- Context-aware suggestions
- Keyboard navigation
- Recent commands tracking

Examples:
    # Create palette
    palette = CommandPalette()

    # Add commands
    palette.add_command(
        name="Switch to Build Agent",
        action="agent:build",
        category=CommandCategory.AGENT,
        keywords=["build", "agent", "switch"]
    )

    # Search and execute
    results = palette.search("build")
    # Returns [Command("Switch to Build Agent", ...)]
"""

from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable, Optional
from datetime import datetime
import json
from pathlib import Path

from rapidfuzz import fuzz, process


class CommandCategory(Enum):
    """Command categories for grouping"""
    SUGGESTED = "suggested"  # Context-relevant actions
    SESSION = "session"      # Session management
    AGENT = "agent"          # Model/agent selection
    PROMPT = "prompt"        # Input operations
    PROVIDER = "provider"    # Provider config
    SYSTEM = "system"        # System settings
    MCP = "mcp"              # MCP servers
    CUSTOM = "custom"        # User-defined


@dataclass
class PaletteCommand:
    """A command in the palette"""
    name: str
    action: str  # e.g., "agent:build", "session:new", "system:theme"
    category: CommandCategory
    description: str = ""
    keywords: list[str] = field(default_factory=list)
    shortcut: Optional[str] = None
    icon: str = ""
    enabled: bool = True

    def matches(self, query: str, threshold: int = 60) -> tuple[bool, int]:
        """
        Check if command matches search query using fuzzy matching

        Args:
            query: Search query
            threshold: Minimum score to consider a match (0-100)

        Returns:
            Tuple of (matched, score)
        """
        if not query:
            return True, 100

        query = query.lower()

        # Check name
        name_score = fuzz.partial_ratio(query, self.name.lower())
        if name_score >= threshold:
            return True, name_score

        # Check description
        desc_score = fuzz.partial_ratio(query, self.description.lower())
        if desc_score >= threshold:
            return True, desc_score

        # Check keywords
        for keyword in self.keywords:
            keyword_score = fuzz.partial_ratio(query, keyword.lower())
            if keyword_score >= threshold:
                return True, keyword_score

        # Check action
        action_score = fuzz.partial_ratio(query, self.action.lower())
        if action_score >= threshold:
            return True, action_score

        return False, 0


@dataclass
class PaletteCategory:
    """A group of commands"""
    category: CommandCategory
    title: str
    commands: list[PaletteCommand] = field(default_factory=list)


@dataclass
class PaletteResult:
    """Search result from palette"""
    commands: list[PaletteCommand]
    categories: list[PaletteCategory]
    query: str = ""


class CommandPalette:
    """
    Command palette with fuzzy search

    Provides quick access to all commands via Ctrl+P.
    Supports categorization, context-aware suggestions, and frecency tracking.

    Example:
        palette = CommandPalette()

        # Add commands
        palette.add_command(
            name="New Session",
            action="session:new",
            category=CommandCategory.SESSION,
            shortcut="Ctrl+X N"
        )

        # Search
        results = palette.search("new")

        # Execute
        palette.execute(results.commands[0])
    """

    def __init__(self, history_file: Optional[Path] = None):
        """
        Initialize command palette

        Args:
            history_file: Optional path to command history file
        """
        self.commands: list[PaletteCommand] = []
        self.command_handlers: dict[str, Callable] = {}
        self.history_file = history_file or Path.home() / ".groknroll" / "command_history.json"
        self.command_history: dict[str, int] = {}  # action -> frequency
        self.load_history()

        # Register built-in commands
        self._register_builtin_commands()

    def _register_builtin_commands(self):
        """Register all built-in commands"""

        # Session commands
        session_commands = [
            PaletteCommand(
                name="New Session",
                action="session:new",
                category=CommandCategory.SESSION,
                description="Start a new conversation session",
                keywords=["new", "session", "start", "create"],
                shortcut="Ctrl+X N",
                icon="📝"
            ),
            PaletteCommand(
                name="Save Session",
                action="session:save",
                category=CommandCategory.SESSION,
                description="Save current session",
                keywords=["save", "session", "persist"],
                shortcut="Ctrl+X S",
                icon="💾"
            ),
            PaletteCommand(
                name="Switch Session",
                action="session:switch",
                category=CommandCategory.SESSION,
                description="Switch to a different session",
                keywords=["switch", "session", "change", "load"],
                icon="🔄"
            ),
            PaletteCommand(
                name="Export Session",
                action="session:export",
                category=CommandCategory.SESSION,
                description="Export session to Markdown",
                keywords=["export", "markdown", "save"],
                shortcut="Ctrl+X X",
                icon="📤"
            ),
            PaletteCommand(
                name="Share Session",
                action="session:share",
                category=CommandCategory.SESSION,
                description="Share current session",
                keywords=["share", "session", "link"],
                icon="🔗"
            ),
        ]

        # Agent commands
        agent_commands = [
            PaletteCommand(
                name="Switch to Build Agent",
                action="agent:build",
                category=CommandCategory.AGENT,
                description="Full coding agent with write access",
                keywords=["build", "agent", "code", "write"],
                icon="🛠️"
            ),
            PaletteCommand(
                name="Switch to Plan Agent",
                action="agent:plan",
                category=CommandCategory.AGENT,
                description="Read-only planning agent",
                keywords=["plan", "agent", "read", "analyze"],
                icon="📋"
            ),
            PaletteCommand(
                name="Switch to Oracle Agent",
                action="agent:oracle",
                category=CommandCategory.AGENT,
                description="Codebase knowledge agent",
                keywords=["oracle", "agent", "knowledge", "qa"],
                icon="🔮"
            ),
            PaletteCommand(
                name="Cycle Agents",
                action="agent:cycle",
                category=CommandCategory.AGENT,
                description="Cycle through available agents",
                keywords=["cycle", "agent", "next"],
                shortcut="Tab",
                icon="🔄"
            ),
        ]

        # Provider commands
        provider_commands = [
            PaletteCommand(
                name="Configure Provider",
                action="provider:connect",
                category=CommandCategory.PROVIDER,
                description="Set up API keys for LLM providers",
                keywords=["connect", "provider", "api", "key", "config"],
                shortcut="Ctrl+X C",
                icon="🔌"
            ),
            PaletteCommand(
                name="List Providers",
                action="provider:list",
                category=CommandCategory.PROVIDER,
                description="Show configured providers",
                keywords=["providers", "list", "show"],
                icon="📋"
            ),
            PaletteCommand(
                name="List Models",
                action="provider:models",
                category=CommandCategory.PROVIDER,
                description="Show available models and costs",
                keywords=["models", "list", "costs"],
                shortcut="Ctrl+X M",
                icon="🤖"
            ),
        ]

        # System commands
        system_commands = [
            PaletteCommand(
                name="Help",
                action="system:help",
                category=CommandCategory.SYSTEM,
                description="Show help information",
                keywords=["help", "docs", "documentation"],
                shortcut="Ctrl+X H",
                icon="❓"
            ),
            PaletteCommand(
                name="Change Theme",
                action="system:theme",
                category=CommandCategory.SYSTEM,
                description="Switch color theme",
                keywords=["theme", "color", "appearance"],
                icon="🎨"
            ),
            PaletteCommand(
                name="Open in Editor",
                action="system:editor",
                category=CommandCategory.SYSTEM,
                description="Open prompt in external editor",
                keywords=["editor", "vim", "code", "edit"],
                shortcut="Ctrl+X E",
                icon="✏️"
            ),
            PaletteCommand(
                name="Clear Screen",
                action="system:clear",
                category=CommandCategory.SYSTEM,
                description="Clear the terminal screen",
                keywords=["clear", "clean", "cls"],
                icon="🧹"
            ),
            PaletteCommand(
                name="Initialize Project",
                action="system:init",
                category=CommandCategory.SYSTEM,
                description="Create .groknroll directory",
                keywords=["init", "initialize", "setup"],
                icon="🚀"
            ),
        ]

        # Prompt commands
        prompt_commands = [
            PaletteCommand(
                name="Undo",
                action="prompt:undo",
                category=CommandCategory.PROMPT,
                description="Undo last action",
                keywords=["undo", "revert", "back"],
                shortcut="Ctrl+X U",
                icon="↩️"
            ),
            PaletteCommand(
                name="Redo",
                action="prompt:redo",
                category=CommandCategory.PROMPT,
                description="Redo undone action",
                keywords=["redo", "forward"],
                shortcut="Ctrl+X R",
                icon="↪️"
            ),
        ]

        # MCP commands
        mcp_commands = [
            PaletteCommand(
                name="MCP Server Status",
                action="mcp:status",
                category=CommandCategory.MCP,
                description="Show MCP server connections",
                keywords=["mcp", "status", "servers"],
                icon="🔌"
            ),
        ]

        # Add all commands
        all_commands = (
            session_commands +
            agent_commands +
            provider_commands +
            system_commands +
            prompt_commands +
            mcp_commands
        )

        for cmd in all_commands:
            self.add_command_obj(cmd)

    def add_command(
        self,
        name: str,
        action: str,
        category: CommandCategory,
        description: str = "",
        keywords: Optional[list[str]] = None,
        shortcut: Optional[str] = None,
        icon: str = "",
        enabled: bool = True,
    ):
        """Add a command to the palette"""
        cmd = PaletteCommand(
            name=name,
            action=action,
            category=category,
            description=description,
            keywords=keywords or [],
            shortcut=shortcut,
            icon=icon,
            enabled=enabled,
        )
        self.add_command_obj(cmd)

    def add_command_obj(self, command: PaletteCommand):
        """Add a command object to the palette"""
        self.commands.append(command)

    def register_handler(self, action: str, handler: Callable):
        """
        Register a handler for a command action

        Args:
            action: Command action string (e.g., "session:new")
            handler: Callable to execute when command is triggered
        """
        self.command_handlers[action] = handler

    def search(
        self,
        query: str = "",
        threshold: int = 60,
        max_results: int = 50
    ) -> PaletteResult:
        """
        Search commands with fuzzy matching

        Args:
            query: Search query
            threshold: Minimum match score (0-100)
            max_results: Maximum number of results

        Returns:
            PaletteResult with matched commands
        """
        if not query:
            # No query - return all commands grouped by category
            return self._get_all_commands()

        # Find matching commands
        matches: list[tuple[PaletteCommand, int]] = []
        for cmd in self.commands:
            if not cmd.enabled:
                continue

            matched, score = cmd.matches(query, threshold)
            if matched:
                # Boost score based on frecency
                freq_boost = self.command_history.get(cmd.action, 0) * 2
                adjusted_score = min(100, score + freq_boost)
                matches.append((cmd, adjusted_score))

        # Sort by score (descending)
        matches.sort(key=lambda x: x[1], reverse=True)

        # Take top results
        top_matches = [cmd for cmd, score in matches[:max_results]]

        # Group by category
        categories = self._group_by_category(top_matches)

        return PaletteResult(
            commands=top_matches,
            categories=categories,
            query=query
        )

    def _get_all_commands(self) -> PaletteResult:
        """Get all commands grouped by category"""
        enabled_commands = [cmd for cmd in self.commands if cmd.enabled]

        # Get suggested commands based on frecency
        suggested = self._get_suggested_commands(enabled_commands, limit=5)

        # Group remaining by category
        categories = self._group_by_category(enabled_commands)

        # Add suggested category at the top
        if suggested:
            categories.insert(0, PaletteCategory(
                category=CommandCategory.SUGGESTED,
                title="Suggested",
                commands=suggested
            ))

        return PaletteResult(
            commands=enabled_commands,
            categories=categories,
            query=""
        )

    def _group_by_category(self, commands: list[PaletteCommand]) -> list[PaletteCategory]:
        """Group commands by category"""
        category_map: dict[CommandCategory, list[PaletteCommand]] = {}

        for cmd in commands:
            if cmd.category not in category_map:
                category_map[cmd.category] = []
            category_map[cmd.category].append(cmd)

        # Convert to list of categories
        category_titles = {
            CommandCategory.SUGGESTED: "Suggested",
            CommandCategory.SESSION: "Session",
            CommandCategory.AGENT: "Agent",
            CommandCategory.PROMPT: "Prompt",
            CommandCategory.PROVIDER: "Provider",
            CommandCategory.SYSTEM: "System",
            CommandCategory.MCP: "MCP",
            CommandCategory.CUSTOM: "Custom",
        }

        categories = []
        for cat, cmds in sorted(category_map.items(), key=lambda x: x[0].value):
            categories.append(PaletteCategory(
                category=cat,
                title=category_titles.get(cat, cat.value.title()),
                commands=cmds
            ))

        return categories

    def _get_suggested_commands(
        self,
        commands: list[PaletteCommand],
        limit: int = 5
    ) -> list[PaletteCommand]:
        """Get suggested commands based on frecency"""
        # Sort by frequency
        sorted_cmds = sorted(
            commands,
            key=lambda c: self.command_history.get(c.action, 0),
            reverse=True
        )
        return sorted_cmds[:limit]

    def execute(self, command: PaletteCommand) -> Any:
        """
        Execute a command

        Args:
            command: Command to execute

        Returns:
            Result from command handler

        Raises:
            ValueError: If no handler registered for command
        """
        # Update history
        self.command_history[command.action] = self.command_history.get(command.action, 0) + 1
        self.save_history()

        # Execute handler
        handler = self.command_handlers.get(command.action)
        if not handler:
            raise ValueError(f"No handler registered for action: {command.action}")

        return handler()

    def load_history(self):
        """Load command history from file"""
        if not self.history_file.exists():
            return

        try:
            with open(self.history_file) as f:
                data = json.load(f)
                self.command_history = data.get("command_history", {})
        except Exception:
            pass

    def save_history(self):
        """Save command history to file"""
        self.history_file.parent.mkdir(parents=True, exist_ok=True)

        try:
            with open(self.history_file, "w") as f:
                json.dump({
                    "command_history": self.command_history,
                    "last_updated": datetime.now().isoformat()
                }, f, indent=2)
        except Exception:
            pass


# Convenience functions

def create_palette() -> CommandPalette:
    """Create a command palette with default configuration"""
    return CommandPalette()


def search_commands(query: str, palette: Optional[CommandPalette] = None) -> list[PaletteCommand]:
    """
    Search commands

    Args:
        query: Search query
        palette: Optional palette instance

    Returns:
        List of matching commands
    """
    if palette is None:
        palette = create_palette()

    result = palette.search(query)
    return result.commands
