#!/usr/bin/env python3
"""Poll the observed trail-start left reciprocal exit with an edge-overrun move."""
from __future__ import annotations

from argparse import Namespace
import html
from typing import Any

import probe_runtime_input_path as runtime_input
import probe_runtime_predecessor_coordinate_branch_state_poll as coord_poll
import probe_runtime_predecessor_trail_start_branch_state_poll as trail_poll
from probe_runtime_input_path import DEFAULT_PREFIX, OUT
from probe_runtime_selected_pointer_multislot_savedata_poll import build_summary
from probe_runtime_selected_pointer_poll import write_outputs
from summarize_map_tiles import load_maps


OUTPUT_PREFIX = "runtime_selected_pointer_predecessor_trail_left_overrun_branch_state_poll"
TARGET_TILE = {"x": 1, "y": 14}
OUTSIDE_TILE = {"x": 0, "y": 14}


def planned_sequences() -> tuple[list[str], list[dict[str, Any]]]:
    maps = load_maps(trail_poll.ROOT / "out" / "maps.js")
    info = maps[trail_poll.SOURCE_MAP]
    path_keys = coord_poll.find_path(info, trail_poll.TRAIL_START_TILE, TARGET_TILE)
    candidates = [
        ("predecessor-trail-left-overrun-once", [*path_keys, "Left"]),
        ("predecessor-trail-left-overrun-twice", [*path_keys, "Left", "Left"]),
        ("predecessor-trail-left-overrun-return", [*path_keys, "Left", "Return"]),
    ]
    sequences = []
    plans = []
    for name, tail_keys in candidates:
        keys = ["Down", "Return", *tail_keys]
        sequences.append(f"{name}={','.join(keys)}")
        plans.append({
            "name": name,
            "sourceMap": trail_poll.SOURCE_MAP,
            "targetMap": trail_poll.TARGET_MAP,
            "startTile": trail_poll.TRAIL_START_TILE,
            "candidateTile": TARGET_TILE,
            "outsideTile": OUTSIDE_TILE,
            "candidateSide": "left",
            "pathStepCount": len(path_keys),
            "pathRunLengths": coord_poll.run_lengths(path_keys),
            "pathKeys": path_keys,
            "tailKeys": tail_keys[len(path_keys):],
            "sequenceKeyCount": len(keys),
            "sequenceKeys": keys,
        })
    return sequences, plans


def pair_seen(row: dict[str, Any], prefix: str, slot_count: int, pair: dict[str, int]) -> list[int]:
    expected = coord_poll.pair_hex(int(pair["x"]), int(pair["y"]))
    slots = []
    for slot in range(slot_count):
        if expected in coord_poll.unique_pair_set(row, f"{prefix}{slot}TilePair"):
            slots.append(slot)
    return slots


def pair_movement(row: dict[str, Any], prefix: str, slot_count: int) -> list[int]:
    slots = []
    for slot in range(slot_count):
        if len(coord_poll.unique_pair_set(row, f"{prefix}{slot}TilePair")) > 1:
            slots.append(slot)
    return slots


def camera_pairs(row: dict[str, Any]) -> list[dict[str, Any]]:
    pairs = []
    for value in coord_poll.unique_values(row, "cameraTilePair"):
        decoded = coord_poll.unpack_pair_hex(value.get("valueHex"))
        if decoded:
            pairs.append(decoded | {"count": value.get("count")})
    return pairs


def camera_seen(row: dict[str, Any], pair: dict[str, int]) -> bool:
    expected = coord_poll.pair_hex(int(pair["x"]), int(pair["y"]))
    return expected in coord_poll.unique_pair_set(row, "cameraTile")


def camera_pair_seen(row: dict[str, Any], pair: dict[str, int]) -> bool:
    expected = coord_poll.pair_hex(int(pair["x"]), int(pair["y"]))
    return expected in {
        value.get("valueHex")
        for value in coord_poll.unique_values(row, "cameraTilePair")
    }


def branch_state_all_zero(row: dict[str, Any]) -> bool:
    for index in range(12):
        values = coord_poll.unique_values(row, f"secondaryBranchState{index}")
        if len(values) != 1 or values[0].get("valueHex") != "0x00":
            return False
    return True


def analyze(summary: dict[str, Any], plans: list[dict[str, Any]]) -> dict[str, Any]:
    rows = []
    any_camera_target = False
    any_camera_outside = False
    any_actor_target = False
    any_trail_target = False
    any_route = bool(summary.get("anyReachedRouteSelectorContext"))
    for row, plan in zip(summary.get("rows") or [], plans):
        actor_target = pair_seen(row, "actor", coord_poll.ACTOR_POINTER_SLOT_COUNT, TARGET_TILE)
        trail_target = pair_seen(row, "trail", coord_poll.ACTOR_HISTORY_SLOT_COUNT, TARGET_TILE)
        actor_outside = pair_seen(row, "actor", coord_poll.ACTOR_POINTER_SLOT_COUNT, OUTSIDE_TILE)
        trail_outside = pair_seen(row, "trail", coord_poll.ACTOR_HISTORY_SLOT_COUNT, OUTSIDE_TILE)
        camera_target = camera_pair_seen(row, TARGET_TILE)
        camera_outside = camera_pair_seen(row, OUTSIDE_TILE)
        any_camera_target = any_camera_target or camera_target
        any_camera_outside = any_camera_outside or camera_outside
        any_actor_target = any_actor_target or bool(actor_target)
        any_trail_target = any_trail_target or bool(trail_target)
        rows.append({
            "name": row.get("name"),
            "keys": row.get("keys") or [],
            "sampleCount": row.get("sampleCount"),
            "eventCount": row.get("eventCount"),
            "eventsTruncated": row.get("eventsTruncated"),
            "selectors": row.get("uniqueSelectorContexts") or [],
            "targetTile": TARGET_TILE,
            "outsideTile": OUTSIDE_TILE,
            "cameraPairs": camera_pairs(row),
            "cameraTargetObserved": camera_target,
            "cameraOutsideObserved": camera_outside,
            "actorTargetSlots": actor_target,
            "trailTargetSlots": trail_target,
            "actorOutsideSlots": actor_outside,
            "trailOutsideSlots": trail_outside,
            "actorMovementSlots": pair_movement(row, "actor", coord_poll.ACTOR_POINTER_SLOT_COUNT),
            "trailMovementSlots": pair_movement(row, "trail", coord_poll.ACTOR_HISTORY_SLOT_COUNT),
            "branchStateAllZero": branch_state_all_zero(row),
            "plan": plan,
        })
    if any_route:
        classification = "route-selector-observed"
    elif any_actor_target or any_trail_target or any_camera_target:
        classification = "edge-target-observed-without-route"
    elif any_camera_outside:
        classification = "outside-camera-observed-without-route"
    else:
        classification = "edge-target-not-observed"
    return {
        "classification": classification,
        "sequenceCount": len(rows),
        "targetTile": TARGET_TILE,
        "outsideTile": OUTSIDE_TILE,
        "anyCameraTargetObserved": any_camera_target,
        "anyCameraOutsideObserved": any_camera_outside,
        "anyActorTargetObserved": any_actor_target,
        "anyTrailTargetObserved": any_trail_target,
        "anyReachedRouteSelectorContext": any_route,
        "rows": rows,
    }


def overrun_markdown(summary: dict[str, Any]) -> str:
    analysis = summary.get("trailLeftOverrunAnalysis") or {}
    lines = [
        "",
        "## Trail Left Overrun Confirmation",
        "",
        f"- classification: `{analysis.get('classification')}`",
        f"- target tile: `{TARGET_TILE['x']},{TARGET_TILE['y']}`",
        f"- outside tile: `{OUTSIDE_TILE['x']},{OUTSIDE_TILE['y']}`",
        f"- camera target observed: {analysis.get('anyCameraTargetObserved')}",
        f"- camera outside observed: {analysis.get('anyCameraOutsideObserved')}",
        f"- actor target observed: {analysis.get('anyActorTargetObserved')}",
        f"- trail target observed: {analysis.get('anyTrailTargetObserved')}",
        f"- route selector 2:0 observed: {analysis.get('anyReachedRouteSelectorContext')}",
        "",
        "| sequence | samples | selectors | camera pairs | camera target/outside | actor target/outside | trail target/outside/move | branch state |",
        "| --- | ---: | --- | --- | --- | --- | --- | --- |",
    ]
    for row in analysis.get("rows") or []:
        pairs = "; ".join(
            f"{pair.get('x')},{pair.get('y')}x{pair.get('count')}"
            for pair in row.get("cameraPairs") or []
        )
        selectors = [
            item.get("selector") if isinstance(item, dict) else str(item)
            for item in row.get("selectors") or []
        ]
        lines.append(
            f"| `{row.get('name')}` | {row.get('sampleCount')} | "
            f"`{','.join(selector for selector in selectors if selector) or '-'}` | `{pairs or '-'}` | "
            f"{row.get('cameraTargetObserved')}/{row.get('cameraOutsideObserved')} | "
            f"`{','.join(str(item) for item in row.get('actorTargetSlots') or []) or '-'}`/"
            f"`{','.join(str(item) for item in row.get('actorOutsideSlots') or []) or '-'}` | "
            f"`{','.join(str(item) for item in row.get('trailTargetSlots') or []) or '-'}`/"
            f"`{','.join(str(item) for item in row.get('trailOutsideSlots') or []) or '-'}`/"
            f"`{','.join(str(item) for item in row.get('trailMovementSlots') or []) or '-'}` | "
            f"{'all-zero' if row.get('branchStateAllZero') else 'mixed'} |"
        )
    return "\n".join(lines)


def overrun_html(summary: dict[str, Any]) -> str:
    return "\n".join([
        "<h2>Trail Left Overrun Confirmation</h2>",
        f"<pre>{html.escape(overrun_markdown(summary))}</pre>",
    ])


def write_overrun_outputs(summary: dict[str, Any]) -> None:
    write_outputs(summary, OUT, OUTPUT_PREFIX)
    html_path = OUT / f"{OUTPUT_PREFIX}.html"
    html_path.write_text(html_path.read_text(encoding="utf-8") + overrun_html(summary), encoding="utf-8")


def main() -> None:
    runtime_input.ROUTE_WATCH_VALUES = dict(runtime_input.ROUTE_WATCH_VALUES)
    runtime_input.ROUTE_WATCH_VALUES.update(coord_poll.BRANCH_STATE_WATCH_VALUES)
    coord_poll.EVENT_WATCH_VALUE_KEYS.update(trail_poll.TRAIL_WATCH_KEYS)
    sequences, plans = planned_sequences()
    args = Namespace(
        startup_wait=18.0,
        hold=0.7,
        gap=0.25,
        interval=0.02,
        prelude="input-path",
        sequence=sequences,
        slot_source=[f"1={trail_poll.SOURCE_SAVE}"],
        case_aliases=True,
        staged_kind="public predecessor trail-left overrun branch-state watch",
        prefix=DEFAULT_PREFIX,
        out_dir=OUT,
        output_prefix=OUTPUT_PREFIX,
    )
    coord_poll.install_coordinate_sampler()
    try:
        summary = build_summary(args)
    finally:
        coord_poll.restore_coordinate_sampler()
    analysis = analyze(summary, plans)
    summary["objective"] = "public predecessor trail-start left reciprocal exit overrun poll"
    summary["sourceSave"] = trail_poll.SOURCE_SAVE
    summary["sourceMap"] = trail_poll.SOURCE_MAP
    summary["targetMap"] = trail_poll.TARGET_MAP
    summary["trailStartTile"] = trail_poll.TRAIL_START_TILE
    summary["targetedExitCandidates"] = plans
    summary["trailLeftOverrunAnalysis"] = analysis
    summary["trailLeftOverrunBranchStateSplit"] = {
        "classification": analysis.get("classification"),
        "anyCameraTargetObserved": analysis.get("anyCameraTargetObserved"),
        "anyCameraOutsideObserved": analysis.get("anyCameraOutsideObserved"),
        "anyActorTargetObserved": analysis.get("anyActorTargetObserved"),
        "anyTrailTargetObserved": analysis.get("anyTrailTargetObserved"),
        "anyReachedCurrentRoot": summary.get("anyReachedCurrentRoot"),
        "anyReachedRouteSelectorContext": summary.get("anyReachedRouteSelectorContext"),
    }
    summary["conclusion"] = (
        "The trail-start left overrun poll reaches the reciprocal left-edge target as runtime coordinate evidence "
        "only if camera/actor/trail watches observe it. It remains non-promoting unless selector 2:0/current root "
        "or a branch-state fill is observed."
    )
    coord_poll.prune_summary_for_output(summary)
    write_overrun_outputs(summary)
    print(f"wrote predecessor trail-left overrun branch-state poll -> {OUT / (OUTPUT_PREFIX + '.html')}")


if __name__ == "__main__":
    main()
