# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""Dashboard adapter for the ConnectMixin wizard.

Bridges the async wizard protocol to Flask's synchronous request/response
model by queuing wizard output as JSON-serialisable dicts and draining them
after each user action.
"""

from __future__ import annotations

import asyncio
import logging
import threading
from collections import deque

from .connect_wizard import ConnectMixin
from .connect_wizard._helpers import TAB_CATEGORIES, TAB_ORDER
from .formatting import FormatMode

logger = logging.getLogger(__name__)


class DashboardWizardHost(ConnectMixin):
    """Wizard adapter for the localhost Flask dashboard.

    All ``wizard_send*`` methods push JSON dicts onto a per-session deque.
    The sync helper methods (``start_wizard``, ``select_menu``, etc.) run
    the corresponding async method in a throwaway event loop and return
    the accumulated messages via ``drain_messages()``.
    """

    format_mode = FormatMode.HTML
    supports_buttons = True
    supports_message_deletion = False

    def __init__(self, agent):
        self.agent = agent
        self._messages: deque[dict] = deque()
        self._lock = threading.Lock()
        self._wizard_state: dict[str, dict] = {}

    # ── Message queue ───────────────────────────────────────────────

    def _push(self, msg: dict) -> None:
        with self._lock:
            self._messages.append(msg)

    def drain_messages(self) -> list[dict]:
        with self._lock:
            msgs = list(self._messages)
            self._messages.clear()
        return msgs

    # ── WizardHost protocol implementation ──────────────────────────

    async def wizard_send(self, recipient_id: str, text: str) -> None:
        self._push({"type": "text", "text": text})

    async def wizard_send_menu(
        self,
        recipient_id: str,
        text: str,
        options: list[tuple[str, str]],
    ) -> None:
        self._push(
            {
                "type": "menu",
                "text": text,
                "options": [{"key": k, "label": lbl} for k, lbl in options],
            }
        )

    async def wizard_delete_message(self, recipient_id: str, message_ref: object) -> bool:
        return False

    async def wizard_send_card_menu(
        self,
        recipient_id: str,
        header: str,
        tab_content_text: str,
        tab_options: list[tuple[str, str]],
        active_tab: str,
    ) -> None:
        """Emit structured card data for the dashboard frontend."""
        tabs = []
        for tid in TAB_ORDER:
            cat = TAB_CATEGORIES[tid]
            tabs.append(
                {
                    "id": tid,
                    "label": cat["label"],
                    "icon": cat["icon"],
                    "active": tid == active_tab,
                }
            )

        self._push(
            {
                "type": "card_menu",
                "header": header,
                "tabs": tabs,
                "tab_content": tab_content_text,
                "tab_options": [{"key": k, "label": lbl} for k, lbl in tab_options],
                "active_tab": active_tab,
            }
        )

    def _get_current_provider_name(self) -> str:
        provider = getattr(self.agent, "provider", None)
        return provider.name if provider else "unknown"

    # ── Sync wrappers for Flask routes ──────────────────────────────

    def _run_async(self, coro):
        """Run an async coroutine in a throwaway event loop."""
        loop = asyncio.new_event_loop()
        try:
            loop.run_until_complete(coro)
        finally:
            loop.close()

    def start_wizard(self, args: list[str] | None = None) -> list[dict]:
        self._run_async(self.handle_connect_command("dashboard", args or []))
        return self.drain_messages()

    def select_menu(self, key: str) -> list[dict]:
        self._run_async(self.handle_connect_menu_selection("dashboard", key))
        return self.drain_messages()

    def send_text(self, text: str) -> list[dict]:
        self._run_async(self.handle_wizard_message("dashboard", text))
        return self.drain_messages()

    def switch_tab(self, tab_id: str) -> list[dict]:
        self._run_async(self._show_tab("dashboard", tab_id))
        return self.drain_messages()

    def is_active(self) -> bool:
        self._ensure_wizard_state()
        return bool(self._wizard_state.get("dashboard"))

    def cancel(self) -> None:
        """Clear any active wizard state so chat messages route to the agent."""
        self._ensure_wizard_state()
        self._wizard_state.pop("dashboard", None)

    def start(self) -> list[dict]:
        self._run_async(self.handle_start_command("dashboard"))
        return self.drain_messages()

    def get_current_context(self) -> dict:
        """Return context about the currently active wizard step.

        Used by the Helper Hat handoff to tell the agent what the user
        was trying to set up when they asked for help.
        """
        self._ensure_wizard_state()
        state = self._wizard_state.get("dashboard") or {}
        if not state:
            return {"active": False}

        from .connect_wizard._helpers import SERVICE_DISPLAY

        service_key = state.get("type", "")
        step = state.get("step", "")
        display_name = SERVICE_DISPLAY.get(service_key, service_key)

        return {
            "active": True,
            "service_key": service_key,
            "service_name": display_name,
            "step": step,
        }

    def browse(self) -> list[dict]:
        self._run_async(self.handle_browse_command("dashboard"))
        return self.drain_messages()

    def search(self, service: str, query: str) -> list[dict]:
        self._run_async(self.handle_search_command("dashboard", [service] + query.split()))
        return self.drain_messages()
