"""
Buddhi — Discriminative Intelligence (The Driver of the Chariot).

PrakritiElement #2 — Protocol Layer: decision
Category: ANTAHKARANA (Internal Instrument)

In the Vedic model, Buddhi is the DRIVER of the chariot (Katha Upanishad).
It doesn't perceive (that's Manas), doesn't store (that's Chitta),
doesn't detect (that's Gandha). It DISCRIMINATES and DECIDES.

Antahkarana Composition:
    Manas  (#1, cognition) — perceives user intent -> ManasPerception
    Buddhi (#2, decision)  — discriminates -> BuddhiDirective / BuddhiVerdict
    Chitta (#4, awareness) — stores impressions, derives phase
    Gandha (#9, detect)    — detects patterns -> Detection

Phase Machine (derived from Chitta):
    ORIENT   — exploring/reading (context gathering)
    EXECUTE  — making changes (writing/editing)
    VERIFY   — checking work (running tests)
    COMPLETE — task appears done (tests passed)

Buddhi reads Chitta's phase and adjusts tool selection + guidance.
No hardcoded round thresholds. The impressions determine the phase.

Usage:
    buddhi = Buddhi()
    directive = buddhi.pre_flight(user_message, round_num)
    verdict = buddhi.evaluate(tool_calls, tool_results)
"""

from __future__ import annotations

import logging
from dataclasses import dataclass
from enum import StrEnum

from vibe_core.mahamantra.protocols.compression import IntentGuna
from vibe_core.runtime.semantic_actions import SemanticActionType

from steward.antahkarana.chitta import Chitta, ExecutionPhase
from steward.antahkarana.gandha import VerdictAction, detect_patterns
from steward.antahkarana.manas import Manas
from steward.types import ToolUse

logger = logging.getLogger("STEWARD.BUDDHI")


# ── ToolNamespace — Shakti-derived capability domains ────────────────
#
# Vedic mapping (Shakti → Domain):
#   JNANA  (knowledge)     → OBSERVE  — read, search, discover
#   PALANA (maintenance)   → MODIFY   — write, edit, create
#   KSHATRA (enforcement)  → EXECUTE  — bash, system commands
#   UDDHARA (rescue/spawn) → DELEGATE — sub-agent, task delegation
#
# This is the composable layer: actions select NAMESPACES, not tools.
# New tools register into a namespace → automatically participate.
# Runtime-mutable: add/remove tools without code changes.


class ToolNamespace(StrEnum):
    """Semantic tool capability domains (Shakti → business English)."""

    OBSERVE = "observe"  # JNANA: read, search, discover
    MODIFY = "modify"  # PALANA: write, edit, create
    EXECUTE = "execute"  # KSHATRA: bash, system commands
    DELEGATE = "delegate"  # UDDHARA: sub-agent, task delegation


# Namespace → tool names (runtime-mutable)
_NAMESPACE_TOOLS: dict[ToolNamespace, set[str]] = {
    ToolNamespace.OBSERVE: {"read_file", "glob", "grep", "http"},
    ToolNamespace.MODIFY: {"write_file", "edit_file"},
    ToolNamespace.EXECUTE: {"bash"},
    ToolNamespace.DELEGATE: {"sub_agent"},
}

# Action → namespaces (which capability domains are needed)
_ACTION_NAMESPACES: dict[SemanticActionType, frozenset[ToolNamespace]] = {
    SemanticActionType.RESEARCH: frozenset({ToolNamespace.OBSERVE}),
    SemanticActionType.ANALYZE: frozenset({ToolNamespace.OBSERVE}),
    SemanticActionType.MONITOR: frozenset({ToolNamespace.OBSERVE}),
    SemanticActionType.REVIEW: frozenset({ToolNamespace.OBSERVE}),
    SemanticActionType.IMPLEMENT: frozenset({ToolNamespace.OBSERVE, ToolNamespace.MODIFY, ToolNamespace.EXECUTE}),
    SemanticActionType.REFACTOR: frozenset({ToolNamespace.OBSERVE, ToolNamespace.MODIFY, ToolNamespace.EXECUTE}),
    SemanticActionType.DESIGN: frozenset(
        {ToolNamespace.OBSERVE, ToolNamespace.MODIFY, ToolNamespace.EXECUTE, ToolNamespace.DELEGATE}
    ),
    SemanticActionType.PLAN: frozenset({ToolNamespace.OBSERVE, ToolNamespace.DELEGATE}),
    SemanticActionType.SYNTHESIZE: frozenset({ToolNamespace.OBSERVE, ToolNamespace.DELEGATE}),
    SemanticActionType.DEBUG: frozenset({ToolNamespace.OBSERVE, ToolNamespace.MODIFY, ToolNamespace.EXECUTE}),
    SemanticActionType.TEST: frozenset({ToolNamespace.OBSERVE, ToolNamespace.EXECUTE}),
    SemanticActionType.RESPOND: frozenset({ToolNamespace.OBSERVE, ToolNamespace.MODIFY, ToolNamespace.EXECUTE}),
}

# Guna → namespaces (fallback when action tools are empty)
_GUNA_NAMESPACES: dict[IntentGuna, frozenset[ToolNamespace]] = {
    IntentGuna.SATTVA: frozenset({ToolNamespace.OBSERVE}),
    IntentGuna.RAJAS: frozenset({ToolNamespace.OBSERVE, ToolNamespace.MODIFY, ToolNamespace.EXECUTE}),
    IntentGuna.TAMAS: frozenset({ToolNamespace.OBSERVE, ToolNamespace.MODIFY, ToolNamespace.EXECUTE}),
    IntentGuna.SUDDHA: frozenset(ToolNamespace),  # all namespaces
}

# Phase → namespace overlays (add capabilities during specific phases)
_PHASE_NS_OVERLAY: dict[ExecutionPhase, frozenset[ToolNamespace]] = {
    ExecutionPhase.ORIENT: frozenset(),
    ExecutionPhase.EXECUTE: frozenset({ToolNamespace.MODIFY, ToolNamespace.EXECUTE}),
    ExecutionPhase.VERIFY: frozenset({ToolNamespace.EXECUTE, ToolNamespace.OBSERVE}),
    ExecutionPhase.COMPLETE: frozenset(),
}


def resolve_namespaces(namespaces: frozenset[ToolNamespace]) -> frozenset[str]:
    """Resolve namespace set to concrete tool names (O(N) where N=namespaces, not tools)."""
    tools: set[str] = set()
    for ns in namespaces:
        tools.update(_NAMESPACE_TOOLS.get(ns, set()))
    return frozenset(tools)


def register_tool(namespace: ToolNamespace, tool_name: str) -> None:
    """Register a tool into a namespace (runtime composition)."""
    _NAMESPACE_TOOLS[namespace].add(tool_name)


def unregister_tool(namespace: ToolNamespace, tool_name: str) -> None:
    """Remove a tool from a namespace (runtime composition)."""
    _NAMESPACE_TOOLS[namespace].discard(tool_name)


_ACTION_MAX_TOKENS: dict[SemanticActionType, int] = {
    SemanticActionType.RESEARCH: 2048,
    SemanticActionType.ANALYZE: 2048,
    SemanticActionType.MONITOR: 2048,
    SemanticActionType.REVIEW: 2048,
    SemanticActionType.IMPLEMENT: 4096,
    SemanticActionType.REFACTOR: 4096,
    SemanticActionType.DESIGN: 4096,
    SemanticActionType.PLAN: 2048,
    SemanticActionType.SYNTHESIZE: 2048,
    SemanticActionType.DEBUG: 4096,
    SemanticActionType.TEST: 2048,
    SemanticActionType.RESPOND: 4096,
}

# ── ModelTier — cost-aware LLM routing ─────────────────────────────


class ModelTier(StrEnum):
    """Cost-aware model tiers for ProviderChamber routing.

    FLASH: cheapest/fastest (Groq, Mistral) — simple reads, tests
    STANDARD: balanced (default prana ordering) — implementation, debug
    PRO: most capable (Claude, expensive) — design, synthesis
    """

    FLASH = "flash"
    STANDARD = "standard"
    PRO = "pro"


_ACTION_TIER: dict[SemanticActionType, ModelTier] = {
    SemanticActionType.RESEARCH: ModelTier.FLASH,
    SemanticActionType.ANALYZE: ModelTier.STANDARD,
    SemanticActionType.MONITOR: ModelTier.FLASH,
    SemanticActionType.REVIEW: ModelTier.STANDARD,
    SemanticActionType.IMPLEMENT: ModelTier.STANDARD,
    SemanticActionType.REFACTOR: ModelTier.STANDARD,
    SemanticActionType.DESIGN: ModelTier.PRO,
    SemanticActionType.PLAN: ModelTier.STANDARD,
    SemanticActionType.SYNTHESIZE: ModelTier.PRO,
    SemanticActionType.DEBUG: ModelTier.STANDARD,
    SemanticActionType.TEST: ModelTier.FLASH,
    SemanticActionType.RESPOND: ModelTier.STANDARD,
}


# Phase-based token budget (only applies when lower than action budget)
# VERIFY and COMPLETE tighten budget — work should be winding down.
# ORIENT and EXECUTE defer to the action's natural budget.
_PHASE_MAX_TOKENS: dict[ExecutionPhase, int] = {
    ExecutionPhase.ORIENT: 4096,  # defer to action budget
    ExecutionPhase.EXECUTE: 4096,  # full budget for active work
    ExecutionPhase.VERIFY: 2048,  # tighten for test/check phase
    ExecutionPhase.COMPLETE: 2048,  # tighten for wrap-up
}


@dataclass(frozen=True)
class BuddhiDirective:
    """Pre-flight directive — what the LLM needs for THIS call.

    Determined deterministically from substrate cognition + Chitta phase.
    """

    action: SemanticActionType
    guna: IntentGuna
    tool_names: frozenset[str]
    max_tokens: int
    tier: ModelTier = ModelTier.STANDARD
    function: str = ""
    approach: str = ""
    phase: ExecutionPhase | str = ""  # defaults to "" before Chitta derives it


@dataclass(frozen=True)
class BuddhiVerdict:
    """Buddhi's judgment after evaluating a tool round.

    Actions:
        continue  — proceed normally
        reflect   — inject reflection prompt (LLM needed)
        redirect  — suggest alternative approach (deterministic)
        abort     — stop the loop (unrecoverable)
    """

    action: VerdictAction
    reason: str = ""
    suggestion: str = ""


class Buddhi:
    """Discriminative intelligence — the Driver of the Chariot.

    Composes the Antahkarana elements:
        Manas  — perceives intent (classification)
        Chitta — stores impressions, derives phase
        Gandha — detects patterns (stuck loops, errors)

    Phase machine (derived from Chitta):
        ORIENT -> EXECUTE -> VERIFY -> COMPLETE
        Errors regress to ORIENT.

    Buddhi reads the phase, adjusts tools, and injects guidance
    at phase transitions.
    """

    def __init__(self) -> None:
        self._manas = Manas()
        self._chitta = Chitta()
        self._prev_phase = ExecutionPhase.ORIENT

    def pre_flight(
        self,
        user_message: str,
        round_num: int,
        context_pct: float = 0.0,
    ) -> BuddhiDirective:
        """Pre-flight gate — Manas perception + Chitta phase -> tool selection.

        Round 0: Manas perceives user intent (deterministic, zero LLM).
        All rounds: Buddhi uses Chitta's phase for tool + token decisions.

        Args:
            user_message: The original user request
            round_num: Current tool-use round (0 = first LLM call)
            context_pct: Current context budget usage (0.0 to 1.0)

        Returns:
            BuddhiDirective with action, guna, tool_names, max_tokens, phase
        """
        # Round 0: Manas perceives (classify intent once)
        if round_num == 0:
            perception = self._manas.perceive(user_message)
            self._action = perception.action
            self._guna = perception.guna
            self._function = perception.function
            self._approach = perception.approach
            logger.info(
                "Buddhi pre-flight: action=%s guna=%s function=%s approach=%s",
                self._action.value,
                self._guna.value,
                self._function,
                self._approach,
            )

        # Action-based namespace selection (primary)
        action_ns = _ACTION_NAMESPACES.get(self._action, frozenset())
        max_tokens = _ACTION_MAX_TOKENS.get(self._action, 4096)
        base_tools = resolve_namespaces(action_ns)

        # Guna fallback if no namespaces for action
        if not base_tools:
            guna_ns = _GUNA_NAMESPACES.get(self._guna, frozenset(ToolNamespace))
            base_tools = resolve_namespaces(guna_ns)

        # Phase-aware overlay: Chitta's phase adds namespace capabilities
        phase = self._chitta.phase
        phase_ns = _PHASE_NS_OVERLAY.get(phase, frozenset())
        if phase_ns:
            base_tools = frozenset(base_tools | resolve_namespaces(phase_ns))

        # Phase-aware token budget
        phase_max = _PHASE_MAX_TOKENS.get(phase, 4096)
        max_tokens = min(max_tokens, phase_max)

        # Context pressure: tighten budget further
        if context_pct >= 0.7:
            max_tokens = min(max_tokens, 1024)
        elif context_pct >= 0.5:
            max_tokens = min(max_tokens, 2048)

        # After errors, always grant bash for debugging
        if round_num > 0:
            recent_errors = sum(1 for r in self._chitta.recent(3) if not r.success)
            if recent_errors >= 2:
                base_tools = frozenset(base_tools | {"bash"})

        # ModelTier: action-derived, phase-adjusted
        tier = _ACTION_TIER.get(self._action, ModelTier.STANDARD)
        # PRO tasks demote to STANDARD in VERIFY/COMPLETE (work is done)
        if tier == ModelTier.PRO and phase in (ExecutionPhase.VERIFY, ExecutionPhase.COMPLETE):
            tier = ModelTier.STANDARD
        # Under context pressure, demote to save tokens
        if context_pct >= 0.7:
            tier = ModelTier.FLASH

        return BuddhiDirective(
            action=self._action,
            guna=self._guna,
            tool_names=base_tools,
            max_tokens=max_tokens,
            tier=tier,
            function=self._function,
            approach=self._approach,
            phase=phase,
        )

    def evaluate(
        self,
        tool_calls: list[ToolUse],
        results: list[tuple[bool, str]],
    ) -> BuddhiVerdict:
        """Evaluate the outcome of a tool round.

        1. Record impressions in Chitta
        2. Gandha detects patterns
        3. Check for phase transitions -> inject guidance
        4. Buddhi makes final verdict

        Args:
            tool_calls: Tools that were called this round
            results: List of (success, error_msg) tuples

        Returns:
            BuddhiVerdict with recommended action
        """
        prev_phase = self._chitta.phase
        self._chitta.advance_round()

        # Record impressions in Chitta (with file paths for tracking)
        for tc, (success, error) in zip(tool_calls, results):
            params_hash = hash(frozenset((k, str(v)) for k, v in sorted(tc.parameters.items()))) if tc.parameters else 0
            path = str(tc.parameters.get("path", "")) if tc.parameters else ""
            self._chitta.record(
                name=tc.name,
                params_hash=params_hash,
                success=success,
                error=error,
                path=path,
            )

        # Gandha detects patterns in Chitta's impressions (cross-turn aware)
        all_tools = resolve_namespaces(frozenset(ToolNamespace))
        detection = detect_patterns(
            self._chitta.impressions,
            prior_reads=self._chitta.prior_reads,
            available_tools=all_tools,
        )
        if detection is not None:
            verdict = BuddhiVerdict(
                action=detection.severity,
                reason=detection.reason,
                suggestion=detection.suggestion,
            )
            logger.info(
                "Buddhi verdict at round %d: %s — %s",
                self._chitta.round,
                verdict.action,
                verdict.reason,
            )
            return verdict

        # Phase transition guidance
        curr_phase = self._chitta.phase
        if prev_phase != curr_phase:
            logger.info(
                "Buddhi phase transition: %s -> %s (round %d)",
                prev_phase,
                curr_phase,
                self._chitta.round,
            )
            guidance = _phase_guidance(prev_phase, curr_phase, self._chitta)
            if guidance:
                return BuddhiVerdict(
                    action=VerdictAction.REFLECT,
                    reason=f"Phase {prev_phase}->{curr_phase}",
                    suggestion=guidance,
                )

        return BuddhiVerdict(action=VerdictAction.CONTINUE)

    @property
    def phase(self) -> str:
        """Current phase (delegates to Chitta)."""
        return self._chitta.phase

    def reset(self) -> None:
        """Reset for a new task."""
        self._chitta.clear()
        self._prev_phase = ExecutionPhase.ORIENT

    @property
    def stats(self) -> dict[str, object]:
        """Diagnostic stats — delegates to Chitta."""
        return self._chitta.stats


def _phase_guidance(
    prev: ExecutionPhase,
    curr: ExecutionPhase,
    chitta: Chitta,
) -> str:
    """Generate guidance for a phase transition.

    Only injects guidance for FORWARD transitions that the LLM
    needs to know about. Error regression is Gandha's job.

    Returns guidance text or empty string (no guidance needed).
    """
    # EXECUTE -> VERIFY: nudge to run tests after modifications
    if prev == ExecutionPhase.EXECUTE and curr == ExecutionPhase.VERIFY:
        modified = chitta.files_written
        if modified:
            file_list = ", ".join(modified[:5])
            extra = f" (+{len(modified) - 5} more)" if len(modified) > 5 else ""
            return f"You've modified: {file_list}{extra}. Consider running tests to verify your changes work correctly."
        return "You've made changes. Consider running tests to verify."

    return ""
