"""Symbolic-logic eval lane runner.

Tests the structural foundations CORE provides for proposition-based
inference: premise-chain storage, replay determinism, and recallability
from the probe.

For each case the runner:
  1. Runs the premise list on a fresh CognitiveTurnPipeline,
     collecting per-turn pack_mutation_proposal counts.
  2. Runs the probe on that pipeline.
  3. Runs the whole sequence again on a *separate* fresh pipeline
     to verify trace-hash determinism.

Sub-metrics (per case):
  M1. premise_recall   — probe vault_hits >= min_vault_hits
  M2. replay_determinism — trace_hash matches across the two runs
  M3. proposal_storage — count of fired proposals == expected_proposals

See contract.md for the structural claim and gaps.md for the
architectural findings underlying v1's signal choice.

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.config import RuntimeConfig
from evals.parallel import run_cases_parallel


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


def _run_chain(premises: list[str], probe: str) -> tuple[int, str, int]:
    """Return (vault_hits, trace_hash, proposal_count) for one fresh run."""
    runtime = ChatRuntime()
    pipeline = CognitiveTurnPipeline(runtime)
    proposal_count = 0
    for premise in premises:
        try:
            r = pipeline.run(premise, max_tokens=8)
        except ValueError:
            continue
        if r.pack_mutation_proposal is not None:
            proposal_count += 1
    try:
        probe_result = pipeline.run(probe, max_tokens=8)
    except ValueError:
        return 0, "", proposal_count
    return probe_result.vault_hits, probe_result.trace_hash, proposal_count


def _run_case(case: dict[str, Any]) -> dict[str, Any]:
    premises = case.get("premises", [])
    probe = case["probe"]
    min_vault_hits = int(case.get("min_vault_hits", 1))
    expected_proposals = int(case.get("expected_proposals", 0))

    vh1, hash1, pc1 = _run_chain(premises, probe)
    vh2, hash2, pc2 = _run_chain(premises, probe)

    premise_recall_pass = vh1 >= min_vault_hits
    replay_pass = bool(hash1) and hash1 == hash2 and vh1 == vh2 and pc1 == pc2
    proposal_pass = pc1 == expected_proposals

    return {
        "id": case.get("id", ""),
        "pattern": case.get("pattern", ""),
        "vault_hits": vh1,
        "trace_hash": hash1,
        "trace_hash_replay": hash2,
        "proposal_count": pc1,
        "expected_proposals": expected_proposals,
        "min_vault_hits": min_vault_hits,
        "premise_recall_pass": premise_recall_pass,
        "replay_pass": replay_pass,
        "proposal_pass": proposal_pass,
        "passed": premise_recall_pass and replay_pass and proposal_pass,
    }


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

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

    pr = sum(1 for d in case_details if d["premise_recall_pass"]) / total
    rd = sum(1 for d in case_details if d["replay_pass"]) / total
    ps = sum(1 for d in case_details if d["proposal_pass"]) / total
    overall = sum(1 for d in case_details if d["passed"]) / total

    overall_pass = pr >= 0.80 and rd >= 0.95 and ps >= 0.80

    metrics: dict[str, Any] = {
        "premise_recall": round(pr, 4),
        "replay_determinism": round(rd, 4),
        "proposal_storage": round(ps, 4),
        "all_three_pass_rate": round(overall, 4),
        "case_count": total,
        "overall_pass": overall_pass,
    }

    return LaneReport(metrics=metrics, case_details=case_details)
