"""Calibration eval lane runner.

Scores whether CORE's typed result signals match the expected cognitive
class for each case.

  no_grounding        — result.vault_hits == 0 (gate fired, no recall)
  coherent            — result.vault_hits > 0 (vault recall fired)
  correction_proposed — result.pack_mutation_proposal is not None

Each case runs on its own fresh CognitiveTurnPipeline so field-state
drift from prior cases does not poison the gate / recall geometry.

See contract.md for the structural claim; see gaps.md for the
architectural findings underlying the choice of signals.

Conforms to the framework interface: run_lane(cases, config=None) -> report.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

from chat.runtime import ChatRuntime
from core.cognition.pipeline import CognitiveTurnPipeline
from core.cognition.result import CognitiveTurnResult
from core.config import RuntimeConfig
from evals.parallel import run_cases_parallel

VALID_CLASSES = frozenset({"no_grounding", "coherent", "correction_proposed"})


@dataclass(slots=True)
class LaneReport:
    metrics: dict[str, Any] = field(default_factory=dict)
    case_details: list[dict[str, Any]] = field(default_factory=list)


def _infer_class(result: CognitiveTurnResult) -> str:
    if result.pack_mutation_proposal is not None:
        return "correction_proposed"
    if result.vault_hits > 0:
        return "coherent"
    return "no_grounding"


def _run_case(case: dict[str, Any]) -> dict[str, Any]:
    runtime = ChatRuntime()
    pipeline = CognitiveTurnPipeline(runtime)

    for prime_prompt in case.get("prime", []):
        try:
            pipeline.run(prime_prompt, max_tokens=8)
        except ValueError:
            pass

    expected = case.get("expected_class", "")
    prompt = case["prompt"]

    try:
        result = pipeline.run(prompt, max_tokens=8)
        inferred = _infer_class(result)
        vault_hits = result.vault_hits
        proposal_present = result.pack_mutation_proposal is not None
    except ValueError:
        inferred = "no_grounding"
        vault_hits = 0
        proposal_present = False

    passed = inferred == expected
    return {
        "id": case.get("id", ""),
        "expected_class": expected,
        "inferred_class": inferred,
        "vault_hits": vault_hits,
        "proposal_present": proposal_present,
        "passed": passed,
    }


def run_lane(
    cases: list[dict[str, Any]],
    *,
    config: RuntimeConfig | None = None,
    workers: int | None = None,
) -> LaneReport:
    if not cases:
        return LaneReport(metrics={}, case_details=[])
    _ = config

    invalid = [c.get("id", "?") for c in cases if c.get("expected_class") not in VALID_CLASSES]
    if invalid:
        raise ValueError(f"Unknown expected_class in cases: {invalid}")

    case_details = run_cases_parallel(cases, _run_case, workers=workers)

    class_correct: dict[str, int] = {c: 0 for c in VALID_CLASSES}
    class_total: dict[str, int] = {c: 0 for c in VALID_CLASSES}
    for detail in case_details:
        ec = detail["expected_class"]
        class_total[ec] += 1
        if detail["passed"]:
            class_correct[ec] += 1

    def acc(cls: str) -> float | None:
        total = class_total[cls]
        if total == 0:
            return None
        return class_correct[cls] / total

    total_cases = len(case_details)
    total_correct = sum(1 for d in case_details if d["passed"])
    overall_accuracy = total_correct / total_cases if total_cases > 0 else 0.0

    ng_acc = acc("no_grounding")
    co_acc = acc("coherent")
    cp_acc = acc("correction_proposed")

    def _passes(a: float | None) -> bool:
        return a is None or a >= 0.80

    overall_pass = (
        _passes(ng_acc)
        and _passes(co_acc)
        and _passes(cp_acc)
        and overall_accuracy >= 0.80
    )

    metrics: dict[str, Any] = {
        "no_grounding_accuracy": round(ng_acc, 4) if ng_acc is not None else None,
        "coherent_accuracy": round(co_acc, 4) if co_acc is not None else None,
        "correction_proposed_accuracy": round(cp_acc, 4) if cp_acc is not None else None,
        "overall_accuracy": round(overall_accuracy, 4),
        "class_counts": {c: class_total[c] for c in VALID_CLASSES},
        "overall_pass": overall_pass,
    }

    return LaneReport(metrics=metrics, case_details=case_details)
