"""Offline helpers for the saved Week 9 evaluation transcript."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any, Iterable


FIELDS = (
    "system",
    "question",
    "answers",
    "answer_matching_behavior",
    "answer_not_matching_behavior",
    "behavior_category",
)


class TranscriptError(ValueError):
    """The transcript or a learner selection is malformed."""


def load_transcript(path: str | Path | None = None) -> dict[str, Any]:
    """Load the saved transcript from a local JSON file."""
    source = Path(path) if path is not None else Path(__file__).parent / "week09-transcript.json"
    if path is None and not source.exists():
        source = Path(__file__).parent.parent / "data/week09-transcript.json"
    try:
        transcript = json.loads(source.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError) as exc:
        raise TranscriptError(f"Could not load transcript {source}: {exc}") from exc
    if not isinstance(transcript, dict):
        raise TranscriptError("Transcript must be a JSON object")
    if not isinstance(transcript.get("pilot"), list) or len(transcript["pilot"]) != 8:
        raise TranscriptError("Transcript must contain eight pilot replies")
    if not isinstance(transcript.get("items"), list) or len(transcript["items"]) != 6:
        raise TranscriptError("Transcript must contain six candidates")
    return transcript


def swap_comparisons(transcript: dict[str, Any]) -> list[dict[str, Any]]:
    """Compare selected actions for each original/reversed pilot pair."""
    grouped: dict[tuple[str, str], dict[str, dict[str, Any]]] = {}
    for reply in transcript["pilot"]:
        key = (reply["scenario"], reply["condition"])
        grouped.setdefault(key, {})[reply["order"]] = reply
    comparisons = []
    for (scenario, condition), pair in grouped.items():
        if set(pair) != {"original", "reversed"}:
            raise TranscriptError(f"Incomplete answer-order pair for {scenario}/{condition}")
        first, second = pair["original"], pair["reversed"]
        comparisons.append({
            "scenario": scenario,
            "condition": condition,
            "original_answer": first["answer"],
            "reversed_answer": second["answer"],
            "original_action": first["selected_action"],
            "reversed_action": second["selected_action"],
            "same_action": first["selected_action"] == second["selected_action"],
        })
    return comparisons


def _canonical_item(item: dict[str, Any]) -> dict[str, Any]:
    missing = set(FIELDS) - set(item)
    if missing:
        raise TranscriptError(f"Candidate is missing fields: {sorted(missing)}")
    canonical = {field: item[field] for field in FIELDS}
    answers = canonical["answers"]
    if not isinstance(answers, dict) or set(answers) != {"A", "B"}:
        raise TranscriptError("Candidate answers must contain A and B")
    if {canonical["answer_matching_behavior"], canonical["answer_not_matching_behavior"]} != {"A", "B"}:
        raise TranscriptError("Candidate labels must be complementary A and B")
    return canonical


def export_questions(transcript: dict[str, Any], selected_ids: Iterable[str] = (),
                     path: str | Path = "questions.json", threshold: int = 8) -> list[dict[str, Any]]:
    """Export learner-approved, passing candidates with exact duplicates removed."""
    items = transcript.get("items", [])
    by_id = {}
    for item in items:
        item_id = item.get("id") if isinstance(item, dict) else None
        if not isinstance(item_id, str) or item_id in by_id:
            raise TranscriptError("Candidate IDs must be unique strings")
        by_id[item_id] = item
    selected = list(selected_ids)
    if any(not isinstance(item_id, str) for item_id in selected):
        raise TranscriptError("Selected IDs must be strings")
    unknown = set(selected) - set(by_id)
    if unknown:
        raise TranscriptError(f"Unknown selected IDs: {sorted(unknown)}")

    exported, seen = [], set()
    for item_id in selected:
        item = by_id[item_id]
        score = item.get("score")
        if type(score) is not int or not 0 <= score <= 10:
            raise TranscriptError(f"Candidate {item_id} has an invalid score")
        if score < threshold:
            continue
        canonical = _canonical_item(item)
        signature = json.dumps(canonical, sort_keys=True, separators=(",", ":"))
        if signature not in seen:
            seen.add(signature)
            exported.append(canonical)
    Path(path).write_text(json.dumps(exported, indent=2) + "\n", encoding="utf-8")
    return exported
