"""Wake-phrase registry.

Maps wake phrases (e.g. "hey sanad") → skill / callback IDs. Phrases are
persisted in data/wake_phrases.json so dashboard edits survive restart.

This module is deliberately lightweight — it only *stores* the mapping.
Matching a transcript against the registered phrases is done in
`voice/text_utils.match_phrase`, and the actual trigger is orchestrated
by `core/brain.py`'s skill registry.
"""

from __future__ import annotations

import json
import threading
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Any

from Project.Sanad.core.logger import get_logger
from Project.Sanad.config import DATA_DIR

log = get_logger("wake_phrase_manager")

STATE_PATH = DATA_DIR / "wake_phrases.json"


@dataclass
class WakePhrase:
    """A single wake phrase → action mapping."""
    phrase: str
    action_id: str
    enabled: bool = True

    def normalized(self) -> str:
        return self.phrase.strip().lower()


class WakePhraseManager:
    """Thread-safe in-memory store for wake phrases, persisted to disk."""

    def __init__(self, path: Path = STATE_PATH):
        self.path = Path(path)
        self._lock = threading.Lock()
        self._phrases: list[WakePhrase] = []
        self._load()

    # ── persistence ──────────────────────────────────────────────────
    def _load(self) -> None:
        if not self.path.exists():
            return
        try:
            data = json.loads(self.path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError) as exc:
            log.warning("wake_phrases.json unreadable: %s", exc)
            return
        with self._lock:
            self._phrases = [
                WakePhrase(**{k: v for k, v in d.items() if k in WakePhrase.__annotations__})
                for d in data if isinstance(d, dict) and "phrase" in d
            ]
        log.info("Loaded %d wake phrase(s)", len(self._phrases))

    def _save(self) -> None:
        self.path.parent.mkdir(parents=True, exist_ok=True)
        tmp = self.path.with_suffix(self.path.suffix + ".tmp")
        tmp.write_text(
            json.dumps([asdict(p) for p in self._phrases], indent=2, ensure_ascii=False),
            encoding="utf-8",
        )
        tmp.replace(self.path)

    # ── CRUD ─────────────────────────────────────────────────────────
    def list(self) -> list[dict[str, Any]]:
        with self._lock:
            return [asdict(p) for p in self._phrases]

    def add(self, phrase: str, action_id: str) -> dict[str, Any]:
        norm = phrase.strip().lower()
        if not norm:
            raise ValueError("phrase cannot be empty")
        with self._lock:
            for p in self._phrases:
                if p.normalized() == norm and p.action_id == action_id:
                    return asdict(p)
            wp = WakePhrase(phrase=phrase.strip(), action_id=action_id)
            self._phrases.append(wp)
            self._save()
            return asdict(wp)

    def remove(self, phrase: str, action_id: str | None = None) -> bool:
        norm = phrase.strip().lower()
        with self._lock:
            before = len(self._phrases)
            self._phrases = [
                p for p in self._phrases
                if not (p.normalized() == norm and
                        (action_id is None or p.action_id == action_id))
            ]
            removed = before != len(self._phrases)
            if removed:
                self._save()
        return removed

    def set_enabled(self, phrase: str, action_id: str, enabled: bool) -> bool:
        norm = phrase.strip().lower()
        with self._lock:
            for p in self._phrases:
                if p.normalized() == norm and p.action_id == action_id:
                    p.enabled = enabled
                    self._save()
                    return True
        return False

    def for_action(self, action_id: str) -> set[str]:
        """Return all enabled phrases registered for an action."""
        with self._lock:
            return {
                p.normalized() for p in self._phrases
                if p.action_id == action_id and p.enabled
            }

    def action_phrase_map(self) -> dict[str, set[str]]:
        """Return {action_id: {phrases}} for all enabled entries."""
        result: dict[str, set[str]] = {}
        with self._lock:
            for p in self._phrases:
                if p.enabled:
                    result.setdefault(p.action_id, set()).add(p.normalized())
        return result

    # ── status ───────────────────────────────────────────────────────
    def status(self) -> dict[str, Any]:
        with self._lock:
            enabled = sum(1 for p in self._phrases if p.enabled)
            return {
                "total": len(self._phrases),
                "enabled": enabled,
                "actions": sorted({p.action_id for p in self._phrases}),
            }
