"""Tag picker widget for selecting multiple tags.

A multi-select search picker that uses the ApiClient to fetch tags
and allows selecting multiple tags by their paths. Supports creating
new tags on-the-fly when the searched tag doesn't exist.
"""

from typing import Any

from textual.message import Message
from textual.widgets import OptionList
from textual.widgets.option_list import Option

from alloy_runtime_sdk.api_client.client import ApiClient

from cli.infrastructure.forms.base_picker import MultiSelectPicker
from cli.infrastructure.validation.tag_validation import validate_tag_path


class TagPicker(MultiSelectPicker[dict[str, Any]]):
    """Multi-select tag picker with live API search and on-the-fly tag creation.

    Uses the ApiClient to fetch tags on each keystroke. Results appear in a
    list below the search input. User can select multiple tags.

    When searching for a tag that doesn't exist, the picker shows an option
    to create the new tag. The tag will be auto-created on the server when
    the content is saved.

    Usage:
        yield TagPicker(
            client=server_client,
            placeholder="Search tags...",
            id="tags",
        )

        # Get selected tag paths
        picker = self.query_one("#tags", TagPicker)
        tag_paths = picker.selected_tags  # list[str]
    """

    class TagsChanged(Message):
        """Posted when selected tags change."""

        def __init__(self, selected_tags: list[str]) -> None:
            self.selected_tags = selected_tags
            super().__init__()

    def __init__(
        self,
        client: ApiClient,
        placeholder: str = "Search tags...",
        empty_message: str = "No tags selected (required)",
        **kwargs: Any,
    ) -> None:
        """Initialize the tag picker.

        Args:
            client: ApiClient instance for API calls
            placeholder: Placeholder text for search input
            empty_message: Message shown when nothing is selected
        """
        super().__init__(
            client=client,
            placeholder=placeholder,
            empty_message=empty_message,
            **kwargs,
        )
        self._last_search_query: str = ""

    @property
    def selected_tags(self) -> list[str]:
        """Get the list of selected tag paths."""
        return list(self._selected_items.keys())

    @property
    def selected_tag_items(self) -> list[dict[str, Any]]:
        """Get the full selected tag dicts."""
        return list(self._selected_items.values())

    async def _fetch_items(self, query: str) -> list[dict[str, Any]]:
        """Fetch tags matching query from the API."""
        self._last_search_query = query
        response = await self._client.list_tags(search=query, limit=50)
        results = [tag.model_dump() for tag in response.tags]

        # Check if query exactly matches any existing tag
        exact_match_exists = any(tag.get("tag_path", "") == query for tag in results)

        # If no exact match, check if we can offer to create the tag
        if not exact_match_exists:
            is_valid, _ = validate_tag_path(query)
            if is_valid:
                # Add synthetic "create new tag" option at the top
                create_option = {
                    "tag_path": query,
                    "display_name": query,
                    "_is_new": True,
                }
                results.insert(0, create_option)

        return results

    def _format_item(self, item: dict[str, Any]) -> str:
        """Format tag for display in option list."""
        tag_path = item.get("tag_path", "")
        is_new = item.get("_is_new", False)

        if is_new:
            return f"[cyan][+] Create:[/] {tag_path}"
        else:
            display_name = item.get("display_name", tag_path)
            if display_name and display_name != tag_path:
                return f"{tag_path} ({display_name})"
            return tag_path

    def _get_item_id(self, item: dict[str, Any]) -> str:
        """Get unique identifier for a tag (its path)."""
        return item.get("tag_path", "")

    def _get_result_count_message(self, count: int) -> str:
        """Get status message for result count."""
        # Count existing vs new tags
        existing_count = sum(
            1 for t in self._search_results if not t.get("_is_new", False)
        )
        has_create_option = any(t.get("_is_new", False) for t in self._search_results)

        if existing_count == 0 and not has_create_option:
            # No results and query is invalid
            is_valid, error = validate_tag_path(self._last_search_query)
            if not is_valid:
                return f"[yellow]{error}[/]"
            return "No tags found"
        elif has_create_option and existing_count == 0:
            return "No existing tags - Enter to create new tag"
        elif has_create_option:
            return f"{existing_count} tag{'s' if existing_count != 1 else ''} found - or create new"
        else:
            return f"{existing_count} tag{'s' if existing_count != 1 else ''} - Enter to toggle"

    def _update_options(self) -> None:
        """Update the option list with current search results (custom styling for tags)."""
        options = self.query_one("#search-options", OptionList)
        options.clear_options()

        for i, tag in enumerate(self._search_results):
            tag_path = tag.get("tag_path", "")
            is_new = tag.get("_is_new", False)

            if is_new:
                # Style "create new tag" option distinctly
                if tag_path in self._selected_items:
                    display_text = f"[green]✓[/] [cyan][+] Create:[/] {tag_path}"
                else:
                    display_text = f"  [cyan][+] Create:[/] {tag_path}"
            else:
                # Existing tag with display name
                display_name = tag.get("display_name", tag_path)
                if display_name and display_name != tag_path:
                    display_text = f"{tag_path} ({display_name})"
                else:
                    display_text = tag_path

                # Mark selected tags with checkmark
                if tag_path in self._selected_items:
                    display_text = f"[green]✓[/] {display_text}"
                else:
                    display_text = f"  {display_text}"

            options.add_option(Option(display_text, id=f"opt-{i}"))

        if self._search_results:
            options.highlighted = 0

    def _format_selection_display(self) -> str | None:
        """Format selected tags for display."""
        if not self._selected_items:
            return None

        # Format tags, marking new ones with (new)
        formatted_tags: list[str] = []
        for tag_path, tag_data in self._selected_items.items():
            if tag_data.get("_is_new", False):
                formatted_tags.append(f"[cyan]{tag_path}[/] [dim](new)[/]")
            else:
                formatted_tags.append(tag_path)

        paths = ", ".join(formatted_tags)
        return f"[bold green]Selected ({len(self._selected_items)}):[/] {paths}"

    def _post_selection_changed(self) -> None:
        """Post TagsChanged message with tag paths."""
        self.post_message(self.TagsChanged(self.selected_tags))

    def set_initial_selection(self, tags: list[dict[str, Any]]) -> None:
        """Pre-populate the picker with existing tag selections.

        Args:
            tags: List of tag dicts with at least 'tag_path' key
        """
        self._selected_items.clear()
        for tag in tags:
            tag_path = tag.get("tag_path", "")
            if tag_path:
                self._selected_items[tag_path] = tag
        self._update_selected_display()
