"""
fw_server/context.py — Channel 2: Enriched Tool Returns (_framework_context).

Builds the ``_framework_context`` dict that is injected into every core
tool's JSON return.  The dict carries:

  1. **Session awareness** — cumulative state from Channel 3 (SessionLedger)
  2. **Structural directives** — enforcement hints keyed to the tool that
     just ran (what MUST/MUST-NOT the LLM do next?)
  3. **Cascade status** — NOMINAL / DEGRADED / CRITICAL

The ``_framework_context`` key intentionally echoes T14 (NeAynNeGayr):
it *participates* in the framework's truth without being identical to
the framework itself.

Design:
  - Pure function — no side-effects, no mutation of inputs.
  - KV₃ compliant — failure returns a minimal stub, never propagates.
  - All directive text uses enforcement language (MUST / MUST NOT / NEVER)
    aligned with Channel 1 (docstring rewrites).
"""

from __future__ import annotations

import logging
from typing import Any, Dict, List, Optional

logger = logging.getLogger("fw_server.context")


# ── Per-tool structural directives ──────────────────────────────────
# Each entry is a list of strings the LLM MUST receive after calling
# the corresponding tool.  These are the "transparent vessel" messages
# (AX27): they mediate framework constraints without originating them.

_TOOL_DIRECTIVES: Dict[str, List[str]] = {
    "dusun": [
        "The grade returned by dusun() is the COMPUTED ceiling for this "
        "session.  You MUST NOT claim a higher grade in your AX57 "
        "disclosure.  You MAY lower it conservatively.",
        "You MUST use the patterns_applied list in your response — "
        "omitting applicable patterns is a SC-1 violation.",
        "If composite_score ≥ 0.95, this MUST be flagged as an error "
        "(KV₄), not celebrated as success.",
    ],
    "validate_stage": [
        "The gate verdict (PASS/WARN/FAIL) is BINDING for this pipeline "
        "stage.  A FAIL gate means the stage MUST be revised before "
        "proceeding.  A WARN gate requires acknowledgment.",
        "Anomalies listed are structural signals (M4: roughness = signal). "
        "You MUST NOT smooth them away.",
        "If the cascade status is DEGRADED or CRITICAL, you MUST disclose "
        "this in your AX57 output.",
    ],
    "run_bileshke_pipeline": [
        "The composite score MUST stay below 1.0 (T6/KV₄). "
        "Approaching 1.0 indicates error, not perfection.",
        "The grade from bileshke is a ceiling — you MUST NOT inflate it "
        "in downstream claims.",
    ],
    "check_kavaid": [
        "Any kavaid failure (False) is a constraint violation that MUST "
        "be reported in your response.  Silence on kavaid failures is "
        "itself a framework violation.",
        "KV₇ (independence) failures invalidate convergence claims — "
        "if KV₇ is False, you MUST NOT cite instrument agreement as evidence.",
    ],
}


def build_framework_context(
    tool_name: str,
    *,
    grade: Optional[str] = None,
    gate: Optional[str] = None,
    composite_score: Optional[float] = None,
    kavaid_pass_count: Optional[int] = None,
    anomalies: Optional[List[str]] = None,
    session_context: Optional[Dict[str, Any]] = None,
    cascade_status: Optional[Dict[str, Any]] = None,
    cascade_forward: Optional[Dict[str, Any]] = None,
    sentinel_status: Optional[Dict[str, Any]] = None,
    shaping_directives: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
    """Build the ``_framework_context`` dict for injection into tool returns.

    Parameters
    ----------
    tool_name : str
        Which tool just ran (key into _TOOL_DIRECTIVES).
    grade : str | None
        Computed epistemic grade from the tool.
    gate : str | None
        Gate verdict (PASS/WARN/FAIL) if applicable.
    composite_score : float | None
        Bileshke composite score.
    kavaid_pass_count : int | None
        Number of kavaid that passed (out of 8).
    anomalies : list[str] | None
        Anomaly strings from the tool run.
    session_context : dict | None
        Output of ``SessionLedger.get_cumulative_context()``.
    cascade_status : dict | None
        Output of ``SessionLedger.get_cascade_status()``.
    cascade_forward : dict | None
        Output of ``SessionLedger.get_cascade_forward()`` — rich
        forward-looking envelope with prior anomalies, directives,
        and grade constraints for Channel 4 enforcement.

    Returns
    -------
    dict
        The ``_framework_context`` payload.  On any error, returns a
        minimal stub with ``"error"`` key.
    """
    try:
        ctx: Dict[str, Any] = {}

        # ── Structural directives for this tool ──────────────────────
        directives = _TOOL_DIRECTIVES.get(tool_name, [])
        if directives:
            ctx["directives"] = directives

        # ── Grade ceiling enforcement ────────────────────────────────
        if grade is not None:
            ctx["grade_ceiling"] = grade
            ctx["grade_ceiling_source"] = tool_name
            ctx["grade_inflation_warning"] = (
                f"Claiming a grade above '{grade}' is a §1.4 violation. "
                f"The computed grade from {tool_name} is the CEILING."
            )

        # ── Gate enforcement ─────────────────────────────────────────
        if gate is not None:
            ctx["gate_verdict"] = gate
            if gate == "FAIL":
                ctx["gate_enforcement"] = (
                    "This stage received a FAIL verdict. You MUST revise "
                    "and re-run before proceeding to subsequent stages."
                )
            elif gate == "WARN":
                ctx["gate_enforcement"] = (
                    "This stage received a WARN verdict. You MUST "
                    "acknowledge all anomalies in your response."
                )

        # ── Composite score bound ────────────────────────────────────
        if composite_score is not None:
            ctx["composite_score"] = composite_score
            if composite_score >= 0.95:
                ctx["kv4_violation"] = (
                    f"CRITICAL: composite={composite_score:.4f} ≥ 0.95. "
                    "This MUST be flagged as error per KV₄. "
                    "The map can NEVER be the territory."
                )

        # ── Kavaid summary ───────────────────────────────────────────
        if kavaid_pass_count is not None:
            ctx["kavaid_pass_count"] = kavaid_pass_count
            if kavaid_pass_count < 8:
                ctx["kavaid_warning"] = (
                    f"Only {kavaid_pass_count}/8 kavaid passed. "
                    "Failed kavaid MUST be disclosed."
                )

        # ── Anomalies ────────────────────────────────────────────────
        if anomalies:
            ctx["anomaly_count"] = len(anomalies)
            ctx["anomaly_directive"] = (
                f"{len(anomalies)} anomalies detected. "
                "These are structural signals (M4) — "
                "you MUST NOT smooth them away."
            )

        # ── Session awareness (Channel 3 payload) ────────────────────
        if session_context is not None:
            ctx["session"] = session_context

        # ── Cascade status ───────────────────────────────────────────
        if cascade_status is not None:
            ctx["cascade"] = cascade_status
            level = cascade_status.get("degradation_level", "NOMINAL")
            if level != "NOMINAL":
                ctx["cascade_directive"] = (
                    f"Cascade is {level}. "
                    "You MUST disclose degradation in AX57."
                )

        # ── Cascade forward (Channel 4 rich envelope) ────────────────
        if cascade_forward is not None:
            ctx["cascade_forward"] = cascade_forward
            fwd_directives = cascade_forward.get("forward_directives", [])
            if fwd_directives:
                ctx["cascade_forward_directives"] = fwd_directives

        # ── Shaping directives (emergent response guidance) ──────────
        if shaping_directives is not None:
            ctx["shaping_directives"] = shaping_directives

        # ── Sentinel awareness (fills AX52 self-diagnosis zero) ──────
        if sentinel_status is not None:
            ctx["sentinel"] = sentinel_status
            if sentinel_status.get("active_signal"):
                sig = sentinel_status["active_signal"]
                ctx["sentinel_directive"] = (
                    "The Epistemic Sentinel has detected a potential "
                    f"{sig.get('taxonomy_id', 'unknown')} "
                    f"({sig.get('name', '')}) "
                    "in this interaction. Review and acknowledge."
                )

        return ctx

    except Exception as exc:
        logger.debug("context.build_framework_context error: %s", exc)
        return {"error": "context_build_failed", "tool_name": tool_name}
