#!/usr/bin/env python3
"""Poll longer public-predecessor reciprocal exit paths with branch-state watches."""
from __future__ import annotations

from argparse import Namespace
from collections import deque
from pathlib import Path
from typing import Any

import probe_runtime_input_path as runtime_input
from probe_runtime_input_path import DEFAULT_PREFIX, OUT, ROOT
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
from summarize_original_collision_route_audit import can_move_from_tile


OUTPUT_PREFIX = "runtime_selected_pointer_predecessor_reciprocal_exit_branch_state_poll"
SOURCE_SAVE = "data/public_savedata/flack3r/savedat2.dat"
SOURCE_MAP = "map2_02d"
TARGET_MAP = "map1_01a"
START_TILE = {"x": 5, "y": 15}

BRANCH_STATE_WATCH_VALUES = {
    "activeSelectionFlag": (0x00457744, 1),
    **{
        f"secondaryBranchState{index}": (0x0059E360 + index, 1)
        for index in range(12)
    },
}

RECIPROCAL_EXIT_CANDIDATES = [
    {
        "name": "predecessor-reciprocal-top-auto",
        "sourceMap": SOURCE_MAP,
        "targetMap": TARGET_MAP,
        "startTile": START_TILE,
        "candidateTile": {"x": 46, "y": 0},
        "candidateSide": "top",
        "targetSpawn": {"x": 16, "y": 47, "side": "bottom"},
        "reason": "reciprocal geometry hint for map1_01a bottom exit",
    },
    {
        "name": "predecessor-reciprocal-bottom-auto",
        "sourceMap": SOURCE_MAP,
        "targetMap": TARGET_MAP,
        "startTile": START_TILE,
        "candidateTile": {"x": 47, "y": 47},
        "candidateSide": "bottom",
        "targetSpawn": {"x": 18, "y": 0, "side": "top"},
        "reason": "reciprocal geometry hint for map1_01a top exit",
    },
    {
        "name": "predecessor-reciprocal-right-auto",
        "sourceMap": SOURCE_MAP,
        "targetMap": TARGET_MAP,
        "startTile": START_TILE,
        "candidateTile": {"x": 95, "y": 16},
        "candidateSide": "right",
        "targetSpawn": {"x": 3, "y": 14, "side": "left"},
        "reason": "reciprocal geometry hint for map1_01a left exit",
    },
]

MOVE_ORDER = [
    ("Right", 1, 0),
    ("Left", -1, 0),
    ("Down", 0, 1),
    ("Up", 0, -1),
]


def find_path(info: dict[str, Any], start: dict[str, int], target: dict[str, int]) -> list[str]:
    start_cell = (int(start["x"]), int(start["y"]))
    target_cell = (int(target["x"]), int(target["y"]))
    queue: deque[tuple[int, int]] = deque([start_cell])
    previous: dict[tuple[int, int], tuple[int, int] | None] = {start_cell: None}
    move_to: dict[tuple[int, int], str] = {}
    while queue:
        x, y = queue.popleft()
        if (x, y) == target_cell:
            break
        for key, dx, dy in MOVE_ORDER:
            next_cell = (x + dx, y + dy)
            if next_cell in previous:
                continue
            if can_move_from_tile(info, x, y, dx, dy):
                previous[next_cell] = (x, y)
                move_to[next_cell] = key
                queue.append(next_cell)
    if target_cell not in previous:
        raise SystemExit(f"no original-layer1 path from {start_cell} to {target_cell}")
    keys: list[str] = []
    current = target_cell
    while current != start_cell:
        keys.append(move_to[current])
        parent = previous[current]
        if parent is None:
            break
        current = parent
    keys.reverse()
    return keys


def run_lengths(keys: list[str]) -> list[dict[str, int | str]]:
    rows: list[dict[str, int | str]] = []
    for key in keys:
        if rows and rows[-1]["key"] == key:
            rows[-1]["count"] = int(rows[-1]["count"]) + 1
        else:
            rows.append({"key": key, "count": 1})
    return rows


def planned_sequences() -> tuple[list[str], list[dict[str, Any]]]:
    maps = load_maps(ROOT / "out" / "maps.js")
    info = maps[SOURCE_MAP]
    sequences: list[str] = []
    plans: list[dict[str, Any]] = []
    for candidate in RECIPROCAL_EXIT_CANDIDATES:
        target_tile = candidate["candidateTile"]
        path_keys = find_path(info, START_TILE, target_tile)
        keys = ["Down", "Return", *path_keys, "Return"]
        sequences.append(f"{candidate['name']}={','.join(keys)}")
        plans.append({
            **candidate,
            "pathStepCount": len(path_keys),
            "pathRunLengths": run_lengths(path_keys),
            "sequenceKeyCount": len(keys),
            "sequenceKeys": keys,
        })
    return sequences, plans


def main() -> None:
    runtime_input.ROUTE_WATCH_VALUES = dict(runtime_input.ROUTE_WATCH_VALUES)
    runtime_input.ROUTE_WATCH_VALUES.update(BRANCH_STATE_WATCH_VALUES)
    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={SOURCE_SAVE}"],
        case_aliases=True,
        staged_kind="public predecessor reciprocal-exit branch-state watch",
        prefix=DEFAULT_PREFIX,
        out_dir=OUT,
        output_prefix=OUTPUT_PREFIX,
    )
    summary = build_summary(args)
    summary["objective"] = "public predecessor reciprocal-exit path poll with branch-state watch values"
    summary["targetedExitCandidates"] = plans
    summary["branchStateWatchValues"] = BRANCH_STATE_WATCH_VALUES
    summary["pathPlanner"] = {
        "map": SOURCE_MAP,
        "startTile": START_TILE,
        "collisionModel": "original layer1 direction flags via can_move_from_tile",
        "excludedAlreadyCoveredCandidate": {
            "candidateTile": {"x": 1, "y": 14},
            "candidateSide": "left",
            "coveredBy": "runtime_selected_pointer_predecessor_nearest_exit_branch_state_poll",
        },
    }
    write_outputs(summary, OUT, OUTPUT_PREFIX)
    print(f"wrote predecessor reciprocal-exit branch-state poll -> {OUT / (OUTPUT_PREFIX + '.html')}")


if __name__ == "__main__":
    main()
