
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Callable, List, Optional, Dict, Any, TYPE_CHECKING

from textual.app import ComposeResult
from textual.containers import Vertical, VerticalScroll
from textual.widgets import Static, Input
from textual.widget import Widget
from textual.message import Message
from textual import events

@dataclass
class SlashCommand:
    name: str
    description: str = ""
    usage: str = ""
    callback: Optional[Callable] = None
    category: str = "General"
    aliases: List[str] = field(default_factory=list)
    args: List[Dict[str, Any]] = field(default_factory=list)
    hidden: bool = False
    plugin_id: str = ""

class CommandItem(Static):

    class Selected(Message):
        def __init__(self, command: SlashCommand):
            self.command = command
            super().__init__()

    def __init__(self, command: SlashCommand, is_selected: bool = False):
        super().__init__()
        self.command = command
        self._selected = is_selected

    def compose(self) -> ComposeResult:
        cmd = self.command

        name_part = f"[bold cyan]/{cmd.name}[/]"

        if cmd.usage:
            args_part = f" [dim]{cmd.usage.replace('/' + cmd.name, '').strip()}[/]"
        elif cmd.args:
            args_hints = []
            for arg in cmd.args:
                arg_name = arg.get("name", "arg")
                required = arg.get("required", False)
                if required:
                    args_hints.append(f"<{arg_name}>")
                else:
                    args_hints.append(f"[{arg_name}]")
            args_part = f" [dim]{' '.join(args_hints)}[/]"
        else:
            args_part = ""

        desc_part = f"  [dim italic]{cmd.description}[/]" if cmd.description else ""

        yield Static(f"{name_part}{args_part}{desc_part}")

    def on_click(self, event: events.Click) -> None:
        self.post_message(self.Selected(self.command))

    def set_selected(self, selected: bool) -> None:
        self._selected = selected
        if selected:
            self.add_class("selected")
        else:
            self.remove_class("selected")

class CommandPalette(Widget):

    DEFAULT_CSS = """
    CommandPalette {
        display: none;
        layer: dialog;
        width: 55;
        max-width: 70%;
        height: auto;
        max-height: 14;
        background: $surface;
        border: solid $primary;
        border-title-color: $accent;
        padding: 0;
        margin: 0;
        /* Position: bottom-left above composer */
        dock: bottom;
        offset: 2 -4;
    }

    CommandPalette.visible {
        display: block;
    }

    CommandPalette .palette-header {
        height: 1;
        padding: 0 1;
        background: $primary-darken-1;
        color: $text;
        text-style: bold;
    }

    CommandPalette .palette-list {
        height: auto;
        max-height: 11;
        padding: 0;
        scrollbar-gutter: stable;
    }

    CommandPalette CommandItem {
        height: auto;
        min-height: 1;
        padding: 0 1;
        background: transparent;
    }

    CommandPalette CommandItem:hover {
        background: $surface-lighten-1;
    }

    CommandPalette CommandItem.selected {
        background: $primary-darken-1;
    }

    CommandPalette .no-commands {
        padding: 1;
        color: $text-muted;
        text-align: center;
    }

    CommandPalette .category-header {
        height: 1;
        padding: 0 1;
        color: $text-muted;
        text-style: bold;
        background: $surface-darken-1;
    }
    """

    class CommandSelected(Message):
        def __init__(self, command: SlashCommand, insert_only: bool = False):
            self.command = command
            self.insert_only = insert_only
            super().__init__()

    class Dismissed(Message):
        pass

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._commands: List[SlashCommand] = []
        self._filtered: List[SlashCommand] = []
        self._filter_text: str = ""
        self._selected_index: int = 0
        self._visible: bool = False

    def compose(self) -> ComposeResult:
        yield Static("Commands", classes="palette-header")
        with VerticalScroll(classes="palette-list"):
            pass

    def set_commands(self, commands: List[SlashCommand]) -> None:
        self._commands = [c for c in commands if not c.hidden]
        self._commands.sort(key=lambda c: (c.category, c.name))

    def add_command(self, command: SlashCommand) -> None:
        if not command.hidden:
            self._commands.append(command)
            self._commands.sort(key=lambda c: (c.category, c.name))

    def show(self, filter_text: str = "") -> None:
        self._filter_text = filter_text.lstrip("/")
        self._filter_commands()
        self._selected_index = 0
        self._visible = True
        self.add_class("visible")
        self._render_commands()

    def hide(self) -> None:
        self._visible = False
        self.remove_class("visible")

    def toggle(self, filter_text: str = "") -> None:
        if self._visible:
            self.hide()
        else:
            self.show(filter_text)

    @property
    def is_visible(self) -> bool:
        return self._visible

    def update_filter(self, text: str) -> None:
        self._filter_text = text.lstrip("/")
        self._filter_commands()
        self._selected_index = 0
        self._render_commands()

    def _filter_commands(self) -> None:
        if not self._filter_text:
            self._filtered = self._commands.copy()
        else:
            filter_lower = self._filter_text.lower()
            self._filtered = [
                c for c in self._commands
                if (filter_lower in c.name.lower() or
                    filter_lower in c.description.lower() or
                    any(filter_lower in a.lower() for a in c.aliases))
            ]

    def _render_commands(self) -> None:
        container = self.query_one(".palette-list", VerticalScroll)
        container.remove_children()

        if not self._filtered:
            container.mount(Static("[dim]No matching commands[/]", classes="no-commands"))
            return

        by_category: Dict[str, List[SlashCommand]] = {}
        for cmd in self._filtered:
            cat = cmd.category or "General"
            if cat not in by_category:
                by_category[cat] = []
            by_category[cat].append(cmd)

        idx = 0
        for category, cmds in sorted(by_category.items()):
            if len(by_category) > 1:
                container.mount(Static(f"[bold]{category}[/]", classes="category-header"))

            for cmd in cmds:
                item = CommandItem(cmd, is_selected=(idx == self._selected_index))
                container.mount(item)
                idx += 1

    def select_next(self) -> None:
        if self._filtered:
            self._selected_index = (self._selected_index + 1) % len(self._filtered)
            self._update_selection()

    def select_prev(self) -> None:
        if self._filtered:
            self._selected_index = (self._selected_index - 1) % len(self._filtered)
            self._update_selection()

    def _update_selection(self) -> None:
        items = list(self.query(CommandItem))
        for i, item in enumerate(items):
            item.set_selected(i == self._selected_index)

        if items and self._selected_index < len(items):
            try:
                container = self.query_one(".palette-list", VerticalScroll)
                container.scroll_to_widget(items[self._selected_index])
            except Exception:
                pass

    def confirm_selection(self, insert_only: bool = False) -> bool:
        if self._filtered and 0 <= self._selected_index < len(self._filtered):
            cmd = self._filtered[self._selected_index]
            self.post_message(self.CommandSelected(cmd, insert_only))
            self.hide()
            return True
        return False

    def on_command_item_selected(self, event: CommandItem.Selected) -> None:
        self.post_message(self.CommandSelected(event.command, insert_only=False))
        self.hide()

    def get_selected_command(self) -> Optional[SlashCommand]:
        if self._filtered and 0 <= self._selected_index < len(self._filtered):
            return self._filtered[self._selected_index]
        return None

def get_builtin_commands() -> List[SlashCommand]:
    return [
        SlashCommand(
            name="help",
            description="Show available commands",
            usage="/help [command]",
            category="General",
            args=[{"name": "command", "required": False, "description": "Command to get help for"}]
        ),
        SlashCommand(
            name="me",
            description="Send an action message",
            usage="/me <action>",
            category="Chat",
            args=[{"name": "action", "required": True, "description": "Action text"}]
        ),
        SlashCommand(
            name="shrug",
            description="Append ¯\\_(ツ)_/¯ to message",
            usage="/shrug [message]",
            category="Chat",
            args=[{"name": "message", "required": False}]
        ),
        SlashCommand(
            name="tableflip",
            description="Append (╯°□°)╯︵ ┻━┻",
            usage="/tableflip [message]",
            category="Chat",
        ),
        SlashCommand(
            name="unflip",
            description="Append ┬─┬ノ( º _ ºノ)",
            usage="/unflip [message]",
            category="Chat",
        ),
        SlashCommand(
            name="nick",
            description="Set display name for a peer",
            usage="/nick <alias> <name>",
            category="Users",
            args=[
                {"name": "alias", "required": True, "description": "Peer alias"},
                {"name": "name", "required": True, "description": "Display name"}
            ]
        ),
        SlashCommand(
            name="block",
            description="Block a user",
            usage="/block <alias>",
            category="Users",
            args=[{"name": "alias", "required": True, "description": "User to block"}]
        ),
        SlashCommand(
            name="unblock",
            description="Unblock a user",
            usage="/unblock <alias>",
            category="Users",
            args=[{"name": "alias", "required": True, "description": "User to unblock"}]
        ),
        SlashCommand(
            name="clear",
            description="Clear chat history",
            usage="/clear",
            category="Chat",
        ),
        SlashCommand(
            name="ping",
            description="Check connection latency",
            usage="/ping",
            category="Network",
        ),
        SlashCommand(
            name="status",
            description="Set your status",
            usage="/status <online|away|busy|offline>",
            category="General",
            args=[{
                "name": "status",
                "required": True,
                "description": "Status to set",
                "choices": ["online", "away", "busy", "offline"]
            }]
        ),
        SlashCommand(
            name="join",
            description="Join a group chat",
            usage="/join <group_id>",
            category="Groups",
            args=[{"name": "group_id", "required": True, "description": "Group ID to join"}]
        ),
        SlashCommand(
            name="leave",
            description="Leave current group",
            usage="/leave",
            category="Groups",
        ),
        SlashCommand(
            name="invite",
            description="Invite user to group",
            usage="/invite <alias>",
            category="Groups",
            args=[{"name": "alias", "required": True, "description": "User to invite"}]
        ),
        SlashCommand(
            name="kick",
            description="Kick user from group",
            usage="/kick <alias>",
            category="Groups",
            args=[{"name": "alias", "required": True, "description": "User to kick"}]
        ),
        SlashCommand(
            name="whisper",
            description="Send a private message",
            usage="/whisper <alias> <message>",
            aliases=["w", "msg", "dm"],
            category="Chat",
            args=[
                {"name": "alias", "required": True, "description": "User alias"},
                {"name": "message", "required": True, "description": "Message text"}
            ]
        ),
        SlashCommand(
            name="spoiler",
            description="Send a spoiler message",
            usage="/spoiler <text>",
            category="Chat",
            args=[{"name": "text", "required": True, "description": "Spoiler content"}]
        ),
        SlashCommand(
            name="find",
            description="Search messages in current chat",
            usage="/find <pattern>",
            category="Chat",
            args=[{"name": "pattern", "required": True, "description": "Search pattern (regex)"}]
        ),
        SlashCommand(
            name="giphy",
            description="Search and send a GIF",
            usage="/giphy <search>",
            aliases=["gif"],
            category="Media",
            args=[{"name": "search", "required": True, "description": "GIF search term"}]
        ),
    ]

def merge_plugin_commands(builtin: List[SlashCommand], plugin_commands: Dict) -> List[SlashCommand]:
    result = builtin.copy()

    for name, cmd in plugin_commands.items():
        result.append(SlashCommand(
            name=name,
            description=cmd.description,
            usage=cmd.usage,
            callback=cmd.callback,
            aliases=cmd.aliases if hasattr(cmd, 'aliases') else [],
            category="Plugins",
            plugin_id=getattr(cmd, 'plugin_id', '')
        ))

    return result
