"""sentinel/articulation.py — CoArticulator: vocabulary evolution over time.

Builds and improves failure-mode vocabulary by accumulating phrases from
user descriptions, context snippets, and AI-generated labels.  Over sessions
the per-taxonomy-ID phrasebook helps users express tacit knowledge (FM-11 bridge).

Design constraints:
  KV₃: Fire-and-forget — extraction never modifies incident data.
  KV₄: acceptance_rate < 1.0.
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Tuple

from sentinel.taxonomy import PICKER_OPTIONS
from sentinel.types import CoSuggestion

logger = logging.getLogger(__name__)

# Minimum acceptance rate before a suggestion enhances the picker
_MIN_ACCEPTANCE_RATE = 0.3

# Maximum phrases stored per taxonomy ID
_MAX_PHRASES_PER_TAX = 20

# Words too generic to be distinctive
_STOP_PHRASES = frozenset({
    "the", "is", "it", "a", "an", "this", "that", "and", "or", "but",
    "for", "in", "on", "to", "of", "was", "were", "be", "been", "has",
    "have", "had", "do", "does", "did", "not", "no", "yes",
})


class CoArticulator:
    """Builds and improves failure-mode vocabulary over time.

    Uses incident descriptions and context to gradually develop a
    per-taxonomy-ID phrasebook that enriches picker options.
    """

    def __init__(self) -> None:
        self._suggestions: List[CoSuggestion] = []

    # ── Public API ───────────────────────────────────────────────────

    @property
    def suggestions(self) -> List[CoSuggestion]:
        return list(self._suggestions)

    def update_from_incident(self, incident: "Incident") -> None:  # type: ignore[name-defined]
        """Extract vocabulary from a confirmed incident.

        Sources (priority order):
          1. user_description — user's own words
          2. context_snippet — the triggering context
        """
        try:
            tax_id = incident.taxonomy_id
            phrases: List[str] = []

            if incident.user_description:
                phrases.append(incident.user_description.strip())
            if incident.context_snippet:
                candidate = self._extract_distinctive_phrase(incident.context_snippet)
                if candidate:
                    phrases.append(candidate)

            if not phrases:
                return

            existing = self._get_suggestion(tax_id)
            if existing:
                for p in phrases:
                    if p not in existing.suggested_phrases:
                        existing.suggested_phrases.append(p)
                        # Trim if too many
                        if len(existing.suggested_phrases) > _MAX_PHRASES_PER_TAX:
                            existing.suggested_phrases.pop(0)
                existing.last_updated = datetime.now(timezone.utc).isoformat()
            else:
                self._suggestions.append(CoSuggestion(
                    taxonomy_id=tax_id,
                    suggested_phrases=phrases,
                    last_updated=datetime.now(timezone.utc).isoformat(),
                ))
        except Exception:
            logger.debug("CoArticulator.update_from_incident swallowed", exc_info=True)

    def get_enhanced_picker_options(self) -> List[Tuple[str, str]]:
        """Return picker options enriched with co-articulated vocabulary.

        Base options from PICKER_OPTIONS are enhanced with the most-accepted
        user phrase for each taxonomy ID (if available and acceptance > 30%).
        """
        enhanced: List[Tuple[str, str]] = []
        for tax_id, base_text in PICKER_OPTIONS:
            suggestion = self._get_suggestion(tax_id)
            if suggestion and suggestion.acceptance_rate() > _MIN_ACCEPTANCE_RATE:
                best_phrase = suggestion.suggested_phrases[-1]
                # Truncate long phrases
                if len(best_phrase) > 60:
                    best_phrase = best_phrase[:57] + "..."
                enhanced.append((tax_id, f"{base_text} — e.g., '{best_phrase}'"))
            else:
                enhanced.append((tax_id, base_text))
        return enhanced

    def record_acceptance(self, taxonomy_id: str) -> None:
        """Record that a user accepted a suggestion for *taxonomy_id*."""
        s = self._get_suggestion(taxonomy_id)
        if s:
            s.acceptance_count += 1

    def record_shown(self, taxonomy_id: str) -> None:
        """Record that the enhanced option for *taxonomy_id* was shown."""
        s = self._get_suggestion(taxonomy_id)
        if s:
            s.usage_count += 1

    # ── State ────────────────────────────────────────────────────────

    def export_state(self) -> List[Dict[str, Any]]:
        """Export co-articulation state for persistence."""
        return [s.to_dict() for s in self._suggestions]

    def import_state(self, data: List[Dict[str, Any]]) -> None:
        """Restore from persisted state."""
        self._suggestions = []
        for raw in data:
            try:
                self._suggestions.append(CoSuggestion.from_dict(raw))
            except Exception:
                continue

    # ── Internal ─────────────────────────────────────────────────────

    def _get_suggestion(self, taxonomy_id: str) -> Optional[CoSuggestion]:
        for s in self._suggestions:
            if s.taxonomy_id == taxonomy_id:
                return s
        return None

    def _extract_distinctive_phrase(self, text: str) -> Optional[str]:
        """Extract the most distinctive phrase (>3 meaningful words) from *text*.

        Returns ``None`` if no distinctive content is found.
        """
        if not text:
            return None

        words = text.strip().split()
        meaningful = [w for w in words if w.lower() not in _STOP_PHRASES and len(w) > 2]

        if len(meaningful) < 3:
            return None

        # Take up to 10 meaningful words, join
        phrase = " ".join(meaningful[:10])
        return phrase if phrase else None
