#!/usr/bin/env python3
"""Poll selected-pointer state while multiple public saves are staged in SaveData slots."""
from __future__ import annotations

import argparse
import json
import shutil
from argparse import Namespace
from pathlib import Path
from typing import Any

from probe_runtime_input_path import DEFAULT_PREFIX, ROOT
from probe_runtime_selected_pointer_poll import build_summary as build_poll_summary
from probe_runtime_selected_pointer_poll import write_outputs as write_poll_outputs
from runtime_case_aliases import cleanup_case_aliases, prepare_case_aliases


OUT = ROOT / "out"
DEFAULT_SLOT_SOURCES = [
    (1, ROOT / "data" / "public_savedata" / "flack3r" / "savedat2.dat"),
    (2, ROOT / "data" / "public_savedata" / "HandyHwanseEditor" / "bin" / "Debug" / "savedat2.dat"),
    (3, ROOT / "data" / "public_savedata" / "HandyHwanseEditor" / "bin" / "Debug" / "savedat1.dat"),
]
DEFAULT_SEQUENCES = [
    "slot1-load-down-enter=Down,Return",
    "slot1-load-down-enter-enter=Down,Return,Return",
    "slot1-load-down-z=Down,z",
    "slot1-load-down-space=Down,space",
    "slot2-load-down-enter-down-enter=Down,Return,Down,Return",
    "slot3-load-down-enter-down-down-enter=Down,Return,Down,Down,Return",
]


def selector_label_for_save(path: Path) -> str:
    data = path.read_bytes()
    if len(data) <= 3:
        return "unreadable"
    return f"{data[2]}:{data[3]}"


def parse_slot_source(raw: str) -> tuple[int, Path]:
    if "=" not in raw:
        raise SystemExit(f"slot source must be SLOT=PATH, got {raw!r}")
    slot_text, path_text = raw.split("=", 1)
    try:
        slot = int(slot_text, 10)
    except ValueError as exc:
        raise SystemExit(f"invalid slot in {raw!r}") from exc
    if slot < 1 or slot > 9:
        raise SystemExit(f"slot must be 1..9, got {slot}")
    path = Path(path_text)
    if not path.is_absolute():
        path = ROOT / path
    if not path.exists():
        raise SystemExit(f"save source does not exist: {path}")
    return slot, path


def prepare_temporary_saves(slot_sources: list[tuple[int, Path]]) -> tuple[list[dict[str, Any]], dict[str, Any]]:
    save_dir = ROOT / "SaveData"
    created_dir = False
    if not save_dir.exists():
        save_dir.mkdir()
        created_dir = True
    rows = []
    seen_slots = set()
    try:
        for slot, source in slot_sources:
            if slot in seen_slots:
                raise SystemExit(f"duplicate slot {slot}")
            seen_slots.add(slot)
            target = save_dir / f"savedat{slot}.dat"
            if target.exists():
                raise SystemExit(f"{target.relative_to(ROOT)} already exists; aborting to avoid overwriting")
            shutil.copyfile(source, target)
            rows.append({
                "slot": slot,
                "source": str(source.relative_to(ROOT)),
                "target": str(target.relative_to(ROOT)),
                "selector": selector_label_for_save(source),
                "createdFile": True,
            })
    except Exception:
        cleanup_temporary_saves(rows, created_dir)
        raise
    return rows, {"createdDirectory": created_dir}


def cleanup_temporary_saves(rows: list[dict[str, Any]], created_dir: bool) -> dict[str, Any]:
    removed = []
    for row in rows:
        target = ROOT / row["target"]
        removed_file = False
        if target.exists():
            target.unlink()
            removed_file = True
        removed.append({
            "slot": row.get("slot"),
            "target": row.get("target"),
            "removedFile": removed_file,
        })
    removed_dir = False
    if created_dir:
        try:
            (ROOT / "SaveData").rmdir()
            removed_dir = True
        except OSError:
            removed_dir = False
    return {
        "removed": removed,
        "removedDirectory": removed_dir,
    }


def build_summary(args: argparse.Namespace) -> dict[str, Any]:
    slot_sources = [parse_slot_source(raw) for raw in args.slot_source] if args.slot_source else DEFAULT_SLOT_SOURCES
    staged_kind = args.staged_kind
    temporary_saves: list[dict[str, Any]] = []
    directory_info: dict[str, Any] = {"createdDirectory": False}
    cleanup: dict[str, Any] = {}
    case_aliases = prepare_case_aliases(ROOT, args.case_aliases)
    alias_cleanup: dict[str, Any] = {}
    try:
        temporary_saves, directory_info = prepare_temporary_saves(slot_sources)
        poll_args = Namespace(
            startup_wait=args.startup_wait,
            hold=args.hold,
            gap=args.gap,
            interval=args.interval,
            prelude=args.prelude,
            max_sequences=len(args.sequence or DEFAULT_SEQUENCES),
            sequence_name=[],
            sequence=args.sequence or DEFAULT_SEQUENCES,
            prefix=args.prefix,
            out_dir=args.out_dir,
            output_prefix=args.output_prefix,
        )
        summary = build_poll_summary(poll_args)
    finally:
        cleanup = cleanup_temporary_saves(temporary_saves, directory_info["createdDirectory"])
        alias_cleanup = cleanup_case_aliases(ROOT, case_aliases)
    public_selectors = sorted({row["selector"] for row in temporary_saves})
    observed_selectors = summary.get("observedSelectors") or []
    observed_public = [selector for selector in observed_selectors if selector in public_selectors]
    summary["objective"] = f"non-debugger selected-pointer polling with {staged_kind} saves staged in SaveData slots"
    summary["stagedSaveKind"] = staged_kind
    summary["temporarySaves"] = temporary_saves
    summary["temporarySaveDirectory"] = directory_info
    summary["cleanup"] = cleanup
    summary["caseAliases"] = case_aliases
    summary["caseAliasCleanup"] = alias_cleanup
    summary["publicSaveSelectors"] = public_selectors
    summary["observedPublicSaveSelectors"] = observed_public
    summary["anyReachedPublicSaveSelector"] = bool(observed_public)
    summary["promotionStatus"] = "blocked"
    summary["conclusion"] = (
        f"Staged {len(temporary_saves)} {staged_kind} save(s) under SaveData/savedatN.dat and polled "
        f"selected-pointer global 0x0059de30 across {summary.get('sequenceCount')} load-menu candidate sequence(s). "
        f"Public save selectors={','.join(public_selectors) or '-'}; observed selectors="
        f"{','.join(observed_selectors) or '-'}; observed public save selector={bool(observed_public)}; "
        f"route selector 2:0 reached={summary.get('anyReachedRouteSelectorContext')}. "
        "This checks original load-menu reachability across multiple slots. It remains non-promoting unless a real "
        "captured selector 2:0 route save or equivalent runtime trace is observed."
    )
    return summary


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--startup-wait", type=float, default=18.0)
    parser.add_argument("--hold", type=float, default=0.7)
    parser.add_argument("--gap", type=float, default=0.25)
    parser.add_argument("--interval", type=float, default=0.02)
    parser.add_argument("--prelude", choices=["focus-only", "input-path"], default="focus-only")
    parser.add_argument("--sequence", action="append", default=[])
    parser.add_argument(
        "--slot-source",
        action="append",
        default=[],
        help="temporary SaveData slot source as SLOT=PATH; default stages public selectors 1:0, 0:0, and 22:0",
    )
    parser.add_argument(
        "--case-aliases",
        action="store_true",
        help="temporarily symlink original uppercase archives to the mixed/lowercase names embedded in Hwanse2.exe",
    )
    parser.add_argument(
        "--staged-kind",
        default="public captured",
        help="human-readable kind for the temporary saves; used only in generated evidence text",
    )
    parser.add_argument("--prefix", type=Path, default=DEFAULT_PREFIX)
    parser.add_argument("--out-dir", type=Path, default=OUT)
    parser.add_argument("--output-prefix", default="runtime_selected_pointer_multislot_savedata_load_poll")
    args = parser.parse_args()
    summary = build_summary(args)
    write_poll_outputs(summary, args.out_dir, args.output_prefix)
    print(f"wrote multislot SaveData selected-pointer poll -> {args.out_dir / (args.output_prefix + '.html')}")


if __name__ == "__main__":
    main()
