"""Slash command system for the chat interface.

Provides a registry-based system for defining and executing slash commands
like /clear, /help, /copy, etc. Commands are parsed from user input and
executed with access to the chat context.
"""

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Protocol

from cli.tui.chat.store import ChatStore

if TYPE_CHECKING:
    from cli.tui.app import AlloyRuntimeApp


@dataclass
class SlashContext:
    """Context provided to slash commands during execution.

    Contains all the resources a command might need to perform its action.
    """

    store: ChatStore
    app: "AlloyRuntimeApp"

    def notify(self, message: str, severity: str = "information") -> None:
        """Notify the user with a message.

        Args:
            message: The message to display.
            severity: Notification severity (information, warning, error).
        """
        self.app.notify(message, severity=severity)  # type: ignore[arg-type]


class SlashCommand(Protocol):
    """Protocol for slash commands.

    All slash commands must implement this protocol to be registered.
    """

    @property
    def name(self) -> str:
        """Primary command name (without slash)."""
        ...

    @property
    def description(self) -> str:
        """Short description shown in /help."""
        ...

    @property
    def aliases(self) -> tuple[str, ...]:
        """Alternative names for the command."""
        ...

    def execute(self, args: str, context: SlashContext) -> None:
        """Execute the command.

        Args:
            args: Arguments passed after the command name.
            context: Execution context with store, app, and notify.
        """
        ...


@dataclass
class ParseResult:
    """Result of parsing user input for slash commands."""

    is_command: bool
    command: SlashCommand | None = None
    args: str = ""
    raw_input: str = ""


class SlashCommandRegistry:
    """Registry of available slash commands.

    Manages command registration, lookup by name/alias, and parsing.
    """

    def __init__(self) -> None:
        self._commands: dict[str, SlashCommand] = {}
        self._all_commands: list[SlashCommand] = []

    def register(self, command: SlashCommand) -> None:
        """Register a slash command.

        Args:
            command: The command to register.
        """
        self._commands[command.name] = command
        for alias in command.aliases:
            self._commands[alias] = command
        self._all_commands.append(command)

    def get(self, name: str) -> SlashCommand | None:
        """Get a command by name or alias.

        Args:
            name: Command name (without slash).

        Returns:
            The command if found, None otherwise.
        """
        return self._commands.get(name.lower())

    def list_commands(self) -> list[SlashCommand]:
        """Get all registered commands (no duplicates from aliases)."""
        return list(self._all_commands)

    def parse(self, text: str) -> ParseResult:
        """Parse user input to detect slash commands.

        Args:
            text: Raw user input.

        Returns:
            ParseResult indicating if input is a command and which one.
        """
        text = text.strip()

        if not text.startswith("/"):
            return ParseResult(is_command=False, raw_input=text)

        # Split into command and args
        parts = text[1:].split(maxsplit=1)
        name = parts[0].lower() if parts else ""
        args = parts[1] if len(parts) > 1 else ""

        command = self.get(name)
        return ParseResult(
            is_command=True,
            command=command,
            args=args,
            raw_input=text,
        )


# =============================================================================
# Built-in Commands
# =============================================================================


@dataclass
class ClearCommand:
    """Clear the message display and show welcome."""

    name: str = field(default="clear", init=False)
    description: str = field(default="Clear the message display", init=False)
    aliases: tuple[str, ...] = field(default=("c", "cls"), init=False)

    def execute(self, args: str, context: SlashContext) -> None:
        """Clear display - args are ignored."""
        del args  # Unused
        context.store.clear_session()
        context.notify("Display cleared", "information")


@dataclass
class HelpCommand:
    """Show available slash commands."""

    name: str = field(default="help", init=False)
    description: str = field(default="Show available commands", init=False)
    aliases: tuple[str, ...] = field(default=("h", "?"), init=False)
    registry: SlashCommandRegistry | None = None

    def execute(self, args: str, context: SlashContext) -> None:
        """Display help - args can filter to specific command."""
        del args  # Could be used for command-specific help later

        if not self.registry:
            context.notify("Help unavailable", "warning")
            return

        # Build help text
        lines = ["[bold]Available Commands:[/]", ""]
        for cmd in self.registry.list_commands():
            alias_str = ""
            if cmd.aliases:
                alias_str = (
                    f" [dim](aliases: {', '.join('/' + a for a in cmd.aliases)})[/]"
                )
            lines.append(f"  [cyan]/{cmd.name}[/] - {cmd.description}{alias_str}")

        lines.append("")
        lines.append(
            "[dim]Keyboard shortcuts also available: Ctrl+N (new), Ctrl+R (regenerate), etc.[/]"
        )

        # Show as notification - in a real impl could show in a modal
        context.notify("\n".join(lines), "information")


@dataclass
class CopyCommand:
    """Copy the last assistant response to clipboard."""

    name: str = field(default="copy", init=False)
    description: str = field(default="Copy last response to clipboard", init=False)
    aliases: tuple[str, ...] = field(default=("cp", "y"), init=False)

    def execute(self, args: str, context: SlashContext) -> None:
        """Copy last assistant message - args are ignored."""
        del args  # Unused

        from cli.tui.chat.types import extract_message_text

        messages = context.store.state.messages
        if not messages:
            context.notify("No messages to copy", "warning")
            return

        # Find last assistant message
        for msg in reversed(messages):
            if msg.role == "assistant":
                text = extract_message_text(msg)
                if text:
                    from cli.infrastructure.tui.clipboard import copy_to_clipboard

                    copy_to_clipboard(context.app, text, "Last response")
                    return

        context.notify("No assistant message to copy", "warning")


@dataclass
class NewCommand:
    """Request a new chat session."""

    name: str = field(default="new", init=False)
    description: str = field(default="Start a new chat session", init=False)
    aliases: tuple[str, ...] = field(default=("n",), init=False)

    def execute(self, args: str, context: SlashContext) -> None:
        """Request new session - signals to screen to open modal."""
        del args, context  # Command handled by screen via message


@dataclass
class SessionsCommand:
    """Focus the session sidebar for browsing."""

    name: str = field(default="sessions", init=False)
    description: str = field(default="Focus session sidebar", init=False)
    aliases: tuple[str, ...] = field(default=("s", "list"), init=False)

    def execute(self, args: str, context: SlashContext) -> None:
        """Focus sessions - signals to screen to focus sidebar."""
        del args, context  # Command handled by screen via message


@dataclass
class UndoCommand:
    """Undo the last conversation turn."""

    name: str = field(default="undo", init=False)
    description: str = field(default="Undo last conversation turn", init=False)
    aliases: tuple[str, ...] = field(default=("u", "z"), init=False)

    def execute(self, args: str, context: SlashContext) -> None:
        """Undo last turn - signals to screen to perform undo."""
        del args, context  # Command handled by screen via message


@dataclass
class RegenerateCommand:
    """Regenerate the last AI response."""

    name: str = field(default="regenerate", init=False)
    description: str = field(default="Regenerate last response", init=False)
    aliases: tuple[str, ...] = field(default=("r", "regen"), init=False)

    def execute(self, args: str, context: SlashContext) -> None:
        """Regenerate - signals to screen to perform regeneration."""
        del args, context  # Command handled by screen via message


@dataclass
class AutoNameCommand:
    """Toggle automatic session naming."""

    name: str = field(default="autoname", init=False)
    description: str = field(default="Toggle automatic session naming", init=False)
    aliases: tuple[str, ...] = field(default=("an",), init=False)

    # Storage key for the preference
    _PREF_KEY: str = field(default="auto_name_sessions", init=False)

    def execute(self, args: str, context: SlashContext) -> None:
        """Toggle auto-naming or set to specific value.

        Args can be:
        - (empty): Toggle the current setting
        - "on" / "true" / "1": Enable auto-naming
        - "off" / "false" / "0": Disable auto-naming
        """
        from cli.infrastructure.local_storage import get_storage

        storage = get_storage()
        current = storage.get("preferences", self._PREF_KEY, True)

        # Parse args to determine new value
        args_lower = args.strip().lower()
        if args_lower in ("on", "true", "1", "enable", "yes"):
            new_value = True
        elif args_lower in ("off", "false", "0", "disable", "no"):
            new_value = False
        else:
            # Toggle
            new_value = not current

        storage.set("preferences", self._PREF_KEY, new_value)

        status = "enabled" if new_value else "disabled"
        context.notify(f"Automatic session naming {status}", "information")


@dataclass
class SplitCommand:
    """Toggle split-pane view for comparing models."""

    name: str = field(default="split", init=False)
    description: str = field(default="Toggle split-pane view", init=False)
    aliases: tuple[str, ...] = field(default=(), init=False)

    def execute(self, args: str, context: SlashContext) -> None:
        """Toggle split view - handled by screen via message."""
        del args, context  # Command handled by screen via message


@dataclass
class SyncCommand:
    """Toggle synchronized input mode in split view."""

    name: str = field(default="sync", init=False)
    description: str = field(
        default="Toggle sync mode (send to both panes)", init=False
    )
    aliases: tuple[str, ...] = field(default=(), init=False)

    def execute(self, args: str, context: SlashContext) -> None:
        """Toggle sync mode - handled by screen via message."""
        del args, context  # Command handled by screen via message


@dataclass
class SwitchPaneCommand:
    """Switch focus between panes in split view."""

    name: str = field(default="switch", init=False)
    description: str = field(default="Switch between panes in split view", init=False)
    aliases: tuple[str, ...] = field(default=("sw",), init=False)

    def execute(self, args: str, context: SlashContext) -> None:
        """Switch pane focus - handled by screen via message."""
        del args, context  # Command handled by screen via message


@dataclass
class MarkdownToggleCommand:
    """Toggle markdown rendering on/off."""

    name: str = field(default="markdown", init=False)
    description: str = field(default="Toggle markdown rendering", init=False)
    aliases: tuple[str, ...] = field(default=("md", "plain"), init=False)

    def execute(self, args: str, context: SlashContext) -> None:
        """Toggle render mode between markdown and plain text."""
        del args  # Unused

        from cli.tui.chat.types import RenderMode

        old_mode = context.store.state.render_mode
        context.store.toggle_render_mode()
        new_mode = context.store.state.render_mode
        mode_name = "Markdown" if new_mode == RenderMode.MARKDOWN else "Plain text"
        context.notify(f"Render mode: {mode_name} (was {old_mode.name})", "information")


def create_default_registry() -> SlashCommandRegistry:
    """Create a registry with all built-in commands.

    Returns:
        SlashCommandRegistry populated with default commands.
    """
    registry = SlashCommandRegistry()

    # Create help command with registry reference
    help_cmd = HelpCommand()
    help_cmd.registry = registry

    # Register all commands
    registry.register(ClearCommand())
    registry.register(help_cmd)
    registry.register(CopyCommand())
    registry.register(NewCommand())
    registry.register(SessionsCommand())
    registry.register(UndoCommand())
    registry.register(RegenerateCommand())
    registry.register(AutoNameCommand())
    # Split view commands
    registry.register(SplitCommand())
    registry.register(SyncCommand())
    registry.register(SwitchPaneCommand())
    # Rendering commands
    registry.register(MarkdownToggleCommand())

    return registry


# Default global registry
_default_registry: SlashCommandRegistry | None = None


def get_default_registry() -> SlashCommandRegistry:
    """Get the default slash command registry (lazy-initialized singleton)."""
    global _default_registry
    if _default_registry is None:
        _default_registry = create_default_registry()
    return _default_registry
