"""Populate Unity-ready game configurations and tracking rows for every concept."""

from __future__ import annotations

import csv
import json
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1] / "content" / "speech"
PLAN = json.loads((ROOT / "plan" / "content-plan.json").read_text(encoding="utf-8"))
CONCEPTS = {c["key"]: c for c in PLAN["concepts"]}


def concept_path(c: dict) -> Path:
    return ROOT / "concepts" / Path(*c["category"].split(".")) / c["key"]


def target_assets(key: str) -> dict:
    return {
        "teaching": [f"{key}_isolated_01", f"{key}_natural_context_01"],
        "variation": [f"{key}_variation_01", f"{key}_viewpoint_01"],
        "discrimination": [f"{key}_context_complex_01"],
        "unseen_probe": [f"{key}_unfamiliar_01"],
    }


def prompt_keys(key: str) -> dict:
    return {
        "ready": "game.prompt.ready",
        "identify": f"{key}.instruction.identify",
        "match": "game.instruction.match_same_meaning",
        "functional_use": f"{key}.instruction.functional_use",
        "replay": "game.action.replay_prompt",
        "correct": "game.feedback.correct_neutral",
        "retry": "game.feedback.try_again_neutral",
        "pause": "game.action.pause",
        "stop": "game.action.stop",
        "no_response": "game.feedback.no_response_neutral",
    }


def tests(key: str, near: list[str]) -> list[dict]:
    first_near = near[0] if near else None
    return [
        {"test_key": f"{key}_load_all_modes", "type": "edit_mode", "given": "valid config and six target assets", "when": "all five game keys load", "then": "no exception and every stable key resolves"},
        {"test_key": f"{key}_one_target", "type": "property", "given": "100 seeded scored choice trials", "when": "choices are constructed", "then": "exactly one target is present in every trial"},
        {"test_key": f"{key}_position_balance", "type": "property", "given": "1000 seeded trials at each field size", "when": "target positions are counted", "then": "position frequency differs by no more than 10 percent"},
        {"test_key": f"{key}_run_limit", "type": "property", "given": "any deterministic seed", "when": "target positions are generated", "then": "the same target position never occurs more than twice consecutively"},
        {"test_key": f"{key}_clear_before_near", "type": "play_mode", "given": "learner has not met level-1 advancement", "when": "a distractor set is requested", "then": "near distractors are excluded"},
        {"test_key": f"{key}_near_unlock", "type": "play_mode", "given": "three independent correct trials across two exemplars", "when": "difficulty is updated", "then": f"near distractor {first_near or 'configured_near_contrast'} may be introduced"},
        {"test_key": f"{key}_prompted_not_mastery", "type": "unit", "given": "a correct response after a prompt", "when": "mastery evidence is calculated", "then": "correctness is true and independence is false"},
        {"test_key": f"{key}_timeout_semantics", "type": "unit", "given": "optional clinician timeout enabled", "when": "no response occurs", "then": "outcome is no_response and not incorrect"},
        {"test_key": f"{key}_deescalation", "type": "play_mode", "given": "two consecutive errors or no-responses", "when": "difficulty adapts", "then": "field size or distractor similarity decreases without negative feedback"},
        {"test_key": f"{key}_probe_isolation", "type": "play_mode", "given": "novel probe mode", "when": "the first scored response is incorrect", "then": "no corrective teaching changes the probe score"},
        {"test_key": f"{key}_access_paths", "type": "play_mode", "given": "touch keyboard switch-scan and clinician-observed profiles", "when": "the same trial is completed", "then": "all paths produce equivalent semantic event data"},
        {"test_key": f"{key}_stop_every_state", "type": "play_mode", "given": "each runtime state", "when": "stop is activated", "then": "the game stops immediately and records stop_reason"},
        {"test_key": f"{key}_missing_locale", "type": "edit_mode", "given": "required prompt key is missing", "when": "session validation runs", "then": "learner trial does not start and a non-identifying configuration error is returned"},
        {"test_key": f"{key}_no_pii", "type": "schema", "given": "serialized trial event", "when": "fields are inspected", "then": "no name birth date recording free text or medical identifier exists"},
    ]


updated = 0
for concept in PLAN["concepts"]:
    key = concept["key"]
    base = concept_path(concept)
    game_dir = base / "games"
    brief = json.loads((game_dir / "game-briefs.json").read_text(encoding="utf-8"))
    semantic = json.loads((base / "semantic-definition.json").read_text(encoding="utf-8"))
    near = []
    for value in semantic["distractor_rules"]["level_2_near_contrast"]["candidates"]:
        if value in CONCEPTS and value != key and value not in near:
            near.append(value)
    clear = [x for x in semantic["distractor_rules"]["level_1_clear_contrast"]["candidates"] if x in CONCEPTS and x != key]
    modes = []
    for game in brief["games"]:
        field = 1 if game["template"] == "natural_routine" else (2 if game["difficulty"] == 1 else 3)
        modes.append({
            "game_key": game["game_key"],
            "template": game["template"],
            "purpose": game["purpose"],
            "start_difficulty": game["difficulty"],
            "field_size": {"default": field, "minimum": 1 if field == 1 else 2, "maximum": 4},
            "target_pool": "unseen_probe" if game["template"] == "probe" else ("variation" if game["difficulty"] > 1 else "teaching"),
            "distractor_pool": "none_or_contextual" if field == 1 else ("near_then_advanced" if game["difficulty"] > 1 else "clear_contrast"),
            "response_modes": game["response_modes"],
            "prompt_semantic_key": prompt_keys(key)["functional_use" if game["template"] == "natural_routine" else ("match" if game["template"] == "image_match" else "identify")],
            "correct_response": {"rule": "selected_asset_semantically_matches_target", "target_concept_key": key},
            "feedback_profile": "calm_neutral_noncompetitive",
            "scored_as_probe": game["template"] == "probe",
        })
    config = {
        "schema_version": 1,
        "unity_config_version": 1,
        "concept_key": key,
        "display_name_semantic_key": f"{key}.label",
        "applicable_node_keys": brief["applicable_node_keys"],
        "target_assets": target_assets(key),
        "distractors": {
            "clear_contrast_concept_keys": clear,
            "near_contrast_concept_keys": near,
            "advanced_policy": semantic["distractor_rules"]["level_3_advanced"]["rule"],
            "prohibited_cues": semantic["distractor_rules"]["prohibited_cues"],
            "asset_policy": "Use approved isolated or natural-context assets; match crop, background complexity, visual size and audio availability across choices.",
        },
        "difficulty_progression": [
            {"level": 0, "name": "access_and_exposure", "field_size": 1, "targets": "teaching", "distractors": "none", "prompt_start": "full_model_or_visual", "exit": "access method and engagement confirmed"},
            {"level": 1, "name": "clear_choice", "field_size": 2, "targets": "teaching", "distractors": "clear_contrast", "prompt_start": "least_supportive_safe_prompt", "exit": "3 consecutive independent correct across 2 exemplars"},
            {"level": 2, "name": "near_discrimination", "field_size": 2, "targets": "teaching_and_variation", "distractors": "near_contrast", "prompt_start": "independent", "exit": "80 percent independent across 10 trials and 3 exemplars"},
            {"level": 3, "name": "mixed_context", "field_size": 3, "targets": "variation_and_discrimination", "distractors": "near_and_advanced", "prompt_start": "independent", "exit": "80 percent independent in 2 contexts"},
            {"level": 4, "name": "generalisation_probe", "field_size": 2, "targets": "unseen_probe", "distractors": "balanced_near", "prompt_start": "independent", "exit": "record only; do not auto-teach during scored probe"},
        ],
        "adaptation": {
            "increase_when": "3 consecutive independent correct responses across at least 2 target exemplars",
            "decrease_when": "2 consecutive incorrect or no-response trials, or clinician-observed distress/fatigue",
            "hold_when": "raw accuracy improves while prompt dependence increases",
            "decrease_action": "reduce field size first, then reduce distractor similarity; preserve target meaning",
            "clinician_override": True,
        },
        "prompts": prompt_keys(key),
        "prompt_hierarchy": ["independent", "natural_cue", "visual_cue", "gesture", "verbal_cue", "partial_model", "full_model", "physical_assistance"],
        "scoring": {
            "trial_outcomes": ["correct", "incorrect", "no_response", "aborted", "not_scorable"],
            "store_separately": ["raw_correctness", "prompt_level", "independence", "attempt", "latency_ms", "response_mode", "clinician_override", "stop_reason"],
            "independent_rule": "Correct on first scorable attempt with prompt_level independent or natural_cue.",
            "prompted_rule": "Correctness may be true but independence is false after visual cue or greater support.",
            "mastery_rule": "At least 80 percent independent across 10 scorable trials, 3 exemplars and 2 sessions; clinician may require broader evidence.",
            "generalisation_rule": "Independent success with unseen exemplar and at least 2 people or contexts as applicable.",
            "no_response_rule": "Never convert no_response to incorrect automatically.",
            "points_rewards": "No public points, penalties, streak pressure or leaderboards; reinforcement is optional and calm.",
        },
        "runtime": {"randomization": "deterministic_session_seed", "max_same_position_run": 2, "default_timeout": None, "input_debounce_ms": 300, "selected_highlight_ms": 800, "stop_available_in_all_states": True},
        "accessibility": {"minimum_target_pixels": 96, "minimum_spacing_pixels": 24, "large_card_mode": True, "reduced_motion": True, "high_contrast": True, "audio_optional": True, "switch_scan_seconds_range": [1, 10], "drag_alternative_required": True},
        "trial_event_fields": ["schema_version", "session_id", "session_seed", "concept_key", "node_key", "game_key", "language", "stimulus_asset_key", "choice_asset_keys", "target_index", "response_mode", "raw_response", "correct", "independent", "prompt_level", "attempt", "latency_ms", "difficulty", "clinician_override", "stop_reason", "timestamp_utc"],
        "test_cases": tests(key, near),
        "review_status": "implementation_and_clinical_review_required",
    }
    output = game_dir / "unity-game-config.json"
    text = json.dumps(config, ensure_ascii=False, indent=2) + "\n"
    if not output.exists() or output.read_text(encoding="utf-8") != text:
        output.write_text(text, encoding="utf-8"); updated += 1

    csv_path = game_dir / "games-list.csv"
    header = ["game_key", "node_key", "template_key", "purpose", "target_asset_policy", "distractor_policy", "difficulty", "response_modes", "status", "owner", "review_notes"]
    rows = []
    default_node = brief["applicable_node_keys"][0] if brief["applicable_node_keys"] else "clinical_selection_required"
    for mode in modes:
        rows.append([mode["game_key"], default_node, mode["template"], mode["purpose"], mode["target_pool"], mode["distractor_pool"], mode["start_difficulty"], "|".join(mode["response_modes"]), "config_ready_review_required", "", "See unity-game-config.json; confirm node per learner and complete SLP accessibility language and technical review"])
    with csv_path.open("w", encoding="utf-8", newline="") as stream:
        writer = csv.writer(stream); writer.writerow(header); writer.writerows(rows)

print(f"Unity game configs ready: {len(PLAN['concepts'])} concepts; {updated} files updated")
