"""Goal Router — classifies user goals and selects orchestrators (no execution).

Architecture layer:
  User → Goal → Goal Router → Best Orchestrator → Planner → Policy Engine → Execution Runtime

Does NOT execute commands or evaluate permissions — routing only.
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from enum import Enum
from typing import Any


class GoalCategory(str, Enum):
    CHAT_GENERAL = "chat_general"
    RESEARCH = "research"
    DOCUMENT_GENERATION = "document_generation"
    CODING = "coding"
    BROWSER_AUTOMATION = "browser_automation"
    DESKTOP_AUTOMATION = "desktop_automation"
    FILE_MANAGEMENT = "file_management"
    VISION_SCREEN = "vision_screen"
    WEBSITE_WIDGET = "website_widget"
    MARKETPLACE_APP = "marketplace_app"
    MIXED = "mixed"
    UNKNOWN_CLARIFICATION = "unknown_clarification"


class OrchestratorType(str, Enum):
    CHAT = "chat"
    RESEARCH = "research"
    DOCUMENT = "document"
    DEVELOPER = "developer"
    BROWSER = "browser"
    DESKTOP = "desktop"
    FILE = "file"
    VISION = "vision"
    MARKETPLACE = "marketplace"
    WIDGET = "widget"


# Goal category → primary orchestrator (deterministic routing table).
_CATEGORY_ORCHESTRATOR: dict[GoalCategory, OrchestratorType] = {
    GoalCategory.CHAT_GENERAL: OrchestratorType.CHAT,
    GoalCategory.RESEARCH: OrchestratorType.RESEARCH,
    GoalCategory.DOCUMENT_GENERATION: OrchestratorType.DOCUMENT,
    GoalCategory.CODING: OrchestratorType.DEVELOPER,
    GoalCategory.BROWSER_AUTOMATION: OrchestratorType.BROWSER,
    GoalCategory.DESKTOP_AUTOMATION: OrchestratorType.DESKTOP,
    GoalCategory.FILE_MANAGEMENT: OrchestratorType.FILE,
    GoalCategory.VISION_SCREEN: OrchestratorType.VISION,
    GoalCategory.WEBSITE_WIDGET: OrchestratorType.WIDGET,
    GoalCategory.MARKETPLACE_APP: OrchestratorType.MARKETPLACE,
    GoalCategory.MIXED: OrchestratorType.BROWSER,
    GoalCategory.UNKNOWN_CLARIFICATION: OrchestratorType.CHAT,
}

# Orchestrator → cloud planner_type hint (existing orchestra planners).
_ORCHESTRATOR_PLANNER_TYPE: dict[OrchestratorType, str | None] = {
    OrchestratorType.CHAT: None,
    OrchestratorType.RESEARCH: None,
    OrchestratorType.DOCUMENT: "document",
    OrchestratorType.DEVELOPER: "coding",
    OrchestratorType.BROWSER: "web",
    OrchestratorType.DESKTOP: "device",
    OrchestratorType.FILE: "document",
    OrchestratorType.VISION: "device",
    OrchestratorType.MARKETPLACE: "artifact",
    OrchestratorType.WIDGET: "web",
}

# Orchestrator → suggested action-session preset for desktop grants.
_ORCHESTRATOR_SESSION_PRESET: dict[OrchestratorType, str | None] = {
    OrchestratorType.CHAT: None,
    OrchestratorType.RESEARCH: "web",
    OrchestratorType.DOCUMENT: "files",
    OrchestratorType.DEVELOPER: "coding",
    OrchestratorType.BROWSER: "web",
    OrchestratorType.DESKTOP: "desktop",
    OrchestratorType.FILE: "files",
    OrchestratorType.VISION: "web",
    OrchestratorType.MARKETPLACE: None,
    OrchestratorType.WIDGET: "web",
}

_COMPOUND_MARKERS = (" и ", " затем ", " потом ", " then ", " and then ", ",", " — ")
_MIXED_CONNECTORS = re.compile(
    r"\b(?:and then|then|потом|затем|и)\b",
    re.IGNORECASE,
)

_CATEGORY_TOKENS: dict[GoalCategory, tuple[str, ...]] = {
    GoalCategory.RESEARCH: (
        "изуч",
        "исслед",
        "research",
        "find sources",
        "узнай",
        "прочит",
        "найди в интернет",
        "search web",
        "google",
        "поиск в сети",
        "сайт",
        "site",
    ),
    GoalCategory.CODING: (
        "скрипт",
        "код",
        "code",
        "cursor",
        "refactor",
        "debug",
        "api",
        "database",
        "php",
        "python",
        "git",
        "compile",
        "deploy",
    ),
    GoalCategory.DOCUMENT_GENERATION: (
        "документ",
        "pdf",
        "docx",
        "document",
        "writer",
        "libreoffice",
        "word",
        "отчёт",
        "report",
        "presentation",
        "pptx",
        "резюме",
        "summary doc",
    ),
    GoalCategory.BROWSER_AUTOMATION: (
        "перейди",
        "открой",
        "open http",
        "open https",
        "browser",
        "браузер",
        "напиши пост",
        "publish",
        "опублик",
        "chatgpt",
        "mytape",
        "linkedin",
        "twitter",
        "заполни форму",
        "fill form",
        "а не сообщени",
        "не сообщение",
        "как пост",
        "публичный пост",
        "feed post",
    ),
    GoalCategory.DESKTOP_AUTOMATION: (
        "кликни",
        "click",
        "мыш",
        "mouse",
        "нажми",
        "keyboard",
        "введи",
        "type text",
        "desktop",
        "рабочий стол",
    ),
    GoalCategory.FILE_MANAGEMENT: (
        "файл",
        "file",
        "folder",
        "папк",
        "directory",
        "workspace",
        "сохрани",
        "save to",
        "создай папку",
        "create folder",
    ),
    GoalCategory.VISION_SCREEN: (
        "скриншот",
        "screenshot",
        "ocr",
        "экран",
        "screen",
        "что на экране",
        "what is on screen",
        "verify",
        "проверь экран",
    ),
    GoalCategory.WEBSITE_WIDGET: (
        "widget",
        "виджет",
        "embed",
        "site assistant",
        "помощник на сайте",
    ),
    GoalCategory.MARKETPLACE_APP: (
        "marketplace",
        "crm",
        "recruitment",
        "рекрут",
        "marketplace app",
    ),
    GoalCategory.CHAT_GENERAL: (
        "привет",
        "hello",
        "спасибо",
        "thanks",
        "explain",
        "объясни",
        "what is",
        "что такое",
    ),
}


@dataclass
class GoalRouteResult:
    """Routing decision for a user goal — classification only."""

    goal_text: str
    primary_category: GoalCategory
    primary_orchestrator: OrchestratorType
    confidence: float
    secondary_categories: list[GoalCategory] = field(default_factory=list)
    secondary_orchestrators: list[OrchestratorType] = field(default_factory=list)
    needs_clarification: bool = False
    planner_type: str | None = None
    session_preset: str | None = None
    explainability: dict[str, Any] = field(default_factory=dict)

    def to_audit_fields(self) -> dict[str, Any]:
        return {
            "goal_category": self.primary_category.value,
            "orchestrator": self.primary_orchestrator.value,
            "secondary_orchestrators": [o.value for o in self.secondary_orchestrators],
            "goal_route_confidence": self.confidence,
            "needs_clarification": self.needs_clarification,
            "planner_type": self.planner_type,
            "session_preset": self.session_preset,
        }


def _normalize(text: str) -> str:
    return re.sub(r"\s+", " ", str(text or "").strip().lower())


def _score_categories(normalized: str) -> dict[GoalCategory, float]:
    scores: dict[GoalCategory, float] = {cat: 0.0 for cat in GoalCategory}
    for category, tokens in _CATEGORY_TOKENS.items():
        for tok in tokens:
            if tok in normalized:
                weight = 2.5 if category in {
                    GoalCategory.CODING,
                    GoalCategory.BROWSER_AUTOMATION,
                    GoalCategory.FILE_MANAGEMENT,
                    GoalCategory.WEBSITE_WIDGET,
                    GoalCategory.MARKETPLACE_APP,
                } else 2.0
                scores[category] += weight
    if "виджет" in normalized or "widget" in normalized:
        scores[GoalCategory.WEBSITE_WIDGET] += 3.0
    return scores


def _detect_mixed(normalized: str, scores: dict[GoalCategory, float]) -> bool:
    active = [
        c
        for c, s in scores.items()
        if s > 0 and c not in {GoalCategory.CHAT_GENERAL, GoalCategory.UNKNOWN_CLARIFICATION}
    ]
    if len(active) >= 2:
        if any(marker in normalized for marker in _COMPOUND_MARKERS):
            return True
        if _MIXED_CONNECTORS.search(normalized):
            return True
    return False


def classify_goal(text: str) -> tuple[GoalCategory, list[GoalCategory], float, dict[str, Any]]:
    """Return primary category, secondary categories, confidence, explainability."""
    normalized = _normalize(text)
    if not normalized:
        return (
            GoalCategory.UNKNOWN_CLARIFICATION,
            [],
            0.2,
            {"reason": "empty_input"},
        )

    scores = _score_categories(normalized)
    ranked = sorted(
        ((cat, score) for cat, score in scores.items() if score > 0),
        key=lambda item: item[1],
        reverse=True,
    )

    if _detect_mixed(normalized, scores):
        secondaries = [cat for cat, _ in ranked[:3] if cat != GoalCategory.CHAT_GENERAL]
        return (
            GoalCategory.MIXED,
            secondaries,
            min(0.95, 0.7 + 0.05 * len(secondaries)),
            {"matched": "compound_intent", "scores": {k.value: v for k, v in ranked[:5]}},
        )

    if not ranked:
        if len(normalized) < 12:
            return (
                GoalCategory.CHAT_GENERAL,
                [],
                0.55,
                {"reason": "short_chat_default"},
            )
        return (
            GoalCategory.UNKNOWN_CLARIFICATION,
            [],
            0.35,
            {"reason": "no_category_match"},
        )

    primary, top_score = ranked[0]
    secondaries = [cat for cat, score in ranked[1:3] if score >= top_score * 0.5]
    confidence = min(0.98, 0.5 + top_score * 0.08)
    if primary == GoalCategory.CHAT_GENERAL and len(ranked) > 1 and ranked[1][1] > 0:
        primary = ranked[1][0]
        secondaries = [cat for cat, score in ranked[2:4] if score > 0]
        confidence = min(0.95, 0.45 + ranked[1][1] * 0.08)

    return (
        primary,
        secondaries,
        confidence,
        {"scores": {k.value: v for k, v in ranked[:5]}},
    )


def route_goal(text: str) -> GoalRouteResult:
    """Classify goal and select primary (+ optional secondary) orchestrators."""
    primary_cat, secondary_cats, confidence, explainability = classify_goal(text)
    needs_clarification = primary_cat == GoalCategory.UNKNOWN_CLARIFICATION

    primary_orch = _CATEGORY_ORCHESTRATOR[primary_cat]
    secondary_orchs: list[OrchestratorType] = []
    seen: set[OrchestratorType] = {primary_orch}

    if primary_cat == GoalCategory.MIXED:
        normalized = _normalize(text)
        if any(tok in normalized for tok in ("http", "www.", ".md", ".com", "сайт", "site")):
            if OrchestratorType.BROWSER not in seen:
                secondary_orchs.insert(0, OrchestratorType.BROWSER)
                seen.add(OrchestratorType.BROWSER)
        for cat in secondary_cats:
            orch = _CATEGORY_ORCHESTRATOR.get(cat)
            if orch and orch not in seen:
                secondary_orchs.append(orch)
                seen.add(orch)
        if secondary_orchs:
            candidates = [primary_orch] + [o for o in secondary_orchs if o != primary_orch]
            priority = (
                OrchestratorType.BROWSER,
                OrchestratorType.FILE,
                OrchestratorType.DOCUMENT,
                OrchestratorType.DEVELOPER,
                OrchestratorType.RESEARCH,
            )
            for preferred in priority:
                if preferred in candidates:
                    primary_orch = preferred
                    secondary_orchs = [o for o in candidates if o != preferred]
                    break
            else:
                primary_orch = candidates[0]
                secondary_orchs = candidates[1:]

    else:
        for cat in secondary_cats:
            orch = _CATEGORY_ORCHESTRATOR.get(cat)
            if orch and orch not in seen:
                secondary_orchs.append(orch)
                seen.add(orch)

    planner_type = _ORCHESTRATOR_PLANNER_TYPE.get(primary_orch)
    session_preset = _ORCHESTRATOR_SESSION_PRESET.get(primary_orch)

    return GoalRouteResult(
        goal_text=str(text or "").strip(),
        primary_category=primary_cat,
        primary_orchestrator=primary_orch,
        confidence=confidence,
        secondary_categories=list(secondary_cats),
        secondary_orchestrators=secondary_orchs,
        needs_clarification=needs_clarification,
        planner_type=planner_type,
        session_preset=session_preset,
        explainability=explainability,
    )


def orchestrator_for_planner_type(planner_type: str | None) -> OrchestratorType | None:
    """Reverse lookup: existing planner_type → orchestrator."""
    if not planner_type:
        return None
    key = str(planner_type).strip().lower()
    for orch, pt in _ORCHESTRATOR_PLANNER_TYPE.items():
        if pt == key:
            return orch
    return None
