#!/usr/bin/env python3
"""Build a focused route-assist page for the current confirmed-route blocker."""
from __future__ import annotations

import argparse
import html
import json
from pathlib import Path
from urllib.parse import urlencode


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"


def web_href(params: dict[str, str], web_prefix: str = "../web") -> str:
    return f"{web_prefix}/game.html?{urlencode(params)}"


def first_match(rows: list[dict], **fields: str) -> dict:
    for row in rows:
        if all(row.get(key) == value for key, value in fields.items()):
            return row
    return {}


def target_hint(candidate: dict, target: str) -> dict:
    hints = [hint for hint in candidate.get("targetHints") or [] if hint.get("target") == target]
    if not hints:
        return {}
    return min(hints, key=lambda hint: hint.get("rank") or [])


def candidate_route_url(source: str, target: str, sample: dict) -> str:
    return web_href({
        "map": source,
        "startTile": f"{sample.get('x')},{sample.get('y')}",
        "focusTile": f"{sample.get('x')},{sample.get('y')}",
        "collision": "1",
        "overview": "1",
        "trialTransitions": "routeAssist",
        "transitionTarget": target,
    })


def target_spawn_url(target: str, hint: dict) -> str:
    if not hint:
        return ""
    return web_href({
        "map": target,
        "startTile": f"{hint.get('x')},{hint.get('y')}",
        "focusTile": f"{hint.get('x')},{hint.get('y')}",
        "events": "1",
        "overview": "1",
    })


def build_candidate_rows(source: str, target: str, map_exit_candidates: list[dict]) -> list[dict]:
    source_exit_row = first_match(map_exit_candidates, map=source)
    rows = []
    for candidate in source_exit_row.get("exitCandidates") or []:
        target_candidates = set(candidate.get("selectorTargetCandidates") or [])
        blocked_targets = set(candidate.get("blockedTargetCandidates") or [])
        hint = target_hint(candidate, target)
        if target not in target_candidates and target not in blocked_targets and not hint:
            continue
        sample = candidate.get("sample") or {}
        rows.append({
            "source": source,
            "target": target,
            "side": candidate.get("side"),
            "componentIndex": candidate.get("componentIndex"),
            "edgeDistance": candidate.get("edgeDistance"),
            "tileCount": candidate.get("tileCount"),
            "sample": sample,
            "sourceStandable": sample.get("standable"),
            "layer0": sample.get("layer0"),
            "layer1": sample.get("layer1"),
            "autoTrigger": candidate.get("edgeDistance") == 0 and sample.get("standable") is True,
            "promotionStatus": candidate.get("promotionStatus"),
            "reason": candidate.get("reason"),
            "targetHint": hint,
            "sourceReviewUrl": candidate.get("reviewUrl"),
            "mapExitTrialUrl": candidate.get("trialUrl"),
            "routeAssistUrl": candidate_route_url(source, target, sample),
            "targetSpawnUrl": target_spawn_url(target, hint),
        })
    rows.sort(key=lambda row: (
        0 if row["autoTrigger"] else 1,
        row.get("edgeDistance") if row.get("edgeDistance") is not None else 999,
        {"top": 0, "bottom": 1, "left": 2, "right": 3}.get(str(row.get("side")), 9),
        -(row.get("tileCount") or 0),
    ))
    return rows


def build_rows(
    playable_progress: dict,
    route_investigation_queue: list[dict],
    map_exit_candidates: list[dict],
) -> list[dict]:
    rows = []
    frontier_by_source_target = {
        (row.get("source"), row.get("target")): row
        for row in playable_progress.get("saveSelectorCandidateFrontier") or []
    }
    for blocker in playable_progress.get("confirmedRouteBlockers") or []:
        source = blocker.get("map")
        for target in blocker.get("saveSelectorFrontierTargets") or []:
            queue = first_match(route_investigation_queue, source=source, target=target)
            frontier = frontier_by_source_target.get((source, target), {})
            candidates = build_candidate_rows(source, target, map_exit_candidates)
            route_start = next(iter(playable_progress.get("confirmedEntryRoutes") or []), {})
            start_tile = route_start.get("startTile") or {}
            start_params = {
                "map": route_start.get("startMap") or source,
                "events": "1",
                "overview": "1",
                "trialTransitions": "routeAssist",
            }
            if start_tile:
                start_params["startTile"] = f"{start_tile.get('x')},{start_tile.get('y')}"
            rows.append({
                "source": source,
                "target": target,
                "status": queue.get("blockerStatus") or queue.get("status") or "blocked",
                "promotionRisk": queue.get("promotionRisk") or frontier.get("promotionRisk"),
                "missingEvidence": queue.get("missingEvidence") or [],
                "branchConditions": queue.get("branchConditions") or frontier.get("branchConditions") or [],
                "selectors": frontier.get("selectors") or queue.get("selectors") or [],
                "leafPointers": frontier.get("leafPointers") or queue.get("leafPointers") or [],
                "sourceTilesetMatch": queue.get("sourceTilesetMatch"),
                "targetTilesetMatch": queue.get("targetTilesetMatch"),
                "frontierNote": frontier.get("promotionNote") or "",
                "candidateExitCount": len(candidates),
                "autoCandidateExitCount": sum(1 for candidate in candidates if candidate.get("autoTrigger")),
                "candidateExits": candidates,
                "startRouteAssistUrl": web_href(start_params),
                "sourceRouteAssistUrl": web_href({
                    "map": source,
                    "events": "1",
                    "overview": "1",
                    "trialTransitions": "routeAssist",
                    "transitionTarget": target,
                }),
                "sourceOverviewUrl": web_href({"map": source, "events": "1", "overview": "1"}),
                "targetOverviewUrl": web_href({"map": target, "events": "1", "overview": "1"}),
                "queueUrl": "route_investigation_queue.html",
                "mapExitUrl": "map_exit_candidates.html",
                "promotionStatus": "trial-only",
                "conclusion": (
                    "This page is a focused manual route-assist index. The links keep geometry-only exits and "
                    "save-selector scene links separated from confirmed normal progression; they are not promotion proof."
                ),
            })
    return rows


def markdown(rows: list[dict]) -> str:
    lines = [
        "# Route Assist Frontier",
        "",
        "Focused manual links for the current confirmed-route blocker. These links are trial-only and do not promote a normal transition.",
        "",
        "| source | target | status | missing evidence | exits | open |",
        "| --- | --- | --- | --- | ---: | --- |",
    ]
    for row in rows:
        links = [
            f"[start assist]({row['startRouteAssistUrl']})",
            f"[source assist]({row['sourceRouteAssistUrl']})",
            f"[target]({row['targetOverviewUrl']})",
            f"[queue]({row['queueUrl']})",
        ]
        lines.append(
            f"| {row.get('source')} | {row.get('target')} | {row.get('status')} / {row.get('promotionRisk')} | "
            f"{', '.join(row.get('missingEvidence') or []) or '-'} | "
            f"{row.get('candidateExitCount')} ({row.get('autoCandidateExitCount')} auto) | {' / '.join(links)} |"
        )
    lines.extend([
        "",
        "## Candidate Exits",
        "",
        "| source | side | tile | auto | target spawn hint | route assist | map-exit trial | target spawn |",
        "| --- | --- | --- | --- | --- | --- | --- | --- |",
    ])
    for row in rows:
        for candidate in row.get("candidateExits") or []:
            sample = candidate.get("sample") or {}
            hint = candidate.get("targetHint") or {}
            hint_text = (
                f"{hint.get('side')} {hint.get('x')},{hint.get('y')} auto={hint.get('autoTrigger')} "
                f"projection={hint.get('projectionDelta')}"
                if hint else "-"
            )
            links = [
                f"[assist]({candidate['routeAssistUrl']})",
                f"[exit]({candidate.get('mapExitTrialUrl')})",
                f"[spawn]({candidate.get('targetSpawnUrl')})" if candidate.get("targetSpawnUrl") else "-",
            ]
            lines.append(
                f"| {candidate.get('source')} | {candidate.get('side')} | "
                f"{sample.get('x')},{sample.get('y')} l0={candidate.get('layer0')} l1={candidate.get('layer1')} | "
                f"{candidate.get('autoTrigger')} | {hint_text} | {links[0]} | {links[1]} | {links[2]} |"
            )
    if not rows:
        lines.append("| - | - | - | - | - | - | - | - |")
    lines.append("")
    return "\n".join(lines)


def html_page(rows: list[dict]) -> str:
    summary_rows = []
    candidate_rows = []
    for row in rows:
        summary_links = " ".join([
            f'<a href="{html.escape(row["startRouteAssistUrl"])}">start assist</a>',
            f'<a href="{html.escape(row["sourceRouteAssistUrl"])}">source assist</a>',
            f'<a href="{html.escape(row["targetOverviewUrl"])}">target</a>',
            f'<a href="{html.escape(row["queueUrl"])}">queue</a>',
        ])
        summary_rows.append(
            "<tr>"
            f"<td>{html.escape(str(row.get('source')))}</td>"
            f"<td>{html.escape(str(row.get('target')))}</td>"
            f"<td>{html.escape(str(row.get('status')))} / {html.escape(str(row.get('promotionRisk')))}</td>"
            f"<td>{html.escape(', '.join(row.get('missingEvidence') or []) or '-')}</td>"
            f"<td>{row.get('candidateExitCount')} ({row.get('autoCandidateExitCount')} auto)</td>"
            f"<td>{summary_links}</td>"
            "</tr>"
        )
        for candidate in row.get("candidateExits") or []:
            sample = candidate.get("sample") or {}
            hint = candidate.get("targetHint") or {}
            hint_text = (
                f"{hint.get('side')} {hint.get('x')},{hint.get('y')} auto={hint.get('autoTrigger')} "
                f"projection={hint.get('projectionDelta')}"
                if hint else "-"
            )
            target_spawn = (
                f'<a href="{html.escape(candidate["targetSpawnUrl"])}">spawn</a>'
                if candidate.get("targetSpawnUrl") else "-"
            )
            candidate_rows.append(
                "<tr>"
                f"<td>{html.escape(str(candidate.get('source')))}</td>"
                f"<td>{html.escape(str(candidate.get('side')))}</td>"
                f"<td>{sample.get('x')},{sample.get('y')}<br><code>l0={candidate.get('layer0')} l1={candidate.get('layer1')}</code></td>"
                f"<td>{html.escape(str(candidate.get('autoTrigger')))}</td>"
                f"<td>{html.escape(hint_text)}</td>"
                f"<td><a href=\"{html.escape(candidate['routeAssistUrl'])}\">assist</a></td>"
                f"<td><a href=\"{html.escape(str(candidate.get('mapExitTrialUrl')))}\">exit</a></td>"
                f"<td>{target_spawn}</td>"
                "</tr>"
            )
    return "\n".join([
        "<!doctype html>",
        '<html lang="en">',
        "<head>",
        '<meta charset="utf-8">',
        "<title>Route Assist Frontier</title>",
        "<style>",
        "body{font-family:system-ui,sans-serif;background:#111;color:#eee;margin:24px}",
        "table{border-collapse:collapse;width:100%;margin:16px 0}",
        "th,td{border:1px solid #444;padding:6px 8px;vertical-align:top}",
        "a{color:#8fd3ff}code{color:#b7f0ff}",
        ".note{max-width:900px;color:#bbb}",
        "</style>",
        "</head>",
        "<body>",
        "<h1>Route Assist Frontier</h1>",
        '<p class="note">Focused manual links for the current confirmed-route blocker. These links are trial-only and do not promote a normal transition.</p>',
        "<h2>Blockers</h2>",
        "<table><thead><tr><th>source</th><th>target</th><th>status</th><th>missing evidence</th><th>exits</th><th>open</th></tr></thead><tbody>",
        "\n".join(summary_rows) or '<tr><td colspan="6">No route-assist frontier rows.</td></tr>',
        "</tbody></table>",
        "<h2>Candidate Exits</h2>",
        "<table><thead><tr><th>source</th><th>side</th><th>tile</th><th>auto</th><th>target spawn hint</th><th>route assist</th><th>map-exit trial</th><th>target spawn</th></tr></thead><tbody>",
        "\n".join(candidate_rows) or '<tr><td colspan="8">No candidate exits.</td></tr>',
        "</tbody></table>",
        "</body>",
        "</html>",
    ])


def write_outputs(rows: list[dict], out_dir: Path) -> None:
    (out_dir / "route_assist_frontier.json").write_text(
        json.dumps(rows, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    (out_dir / "route_assist_frontier.html").write_text(html_page(rows), encoding="utf-8")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--playable-progress", type=Path, default=OUT / "playable_progress.json")
    parser.add_argument("--route-queue", type=Path, default=OUT / "route_investigation_queue.json")
    parser.add_argument("--map-exits", type=Path, default=OUT / "map_exit_candidates.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    rows = build_rows(
        json.loads(args.playable_progress.read_text(encoding="utf-8")),
        json.loads(args.route_queue.read_text(encoding="utf-8")),
        json.loads(args.map_exits.read_text(encoding="utf-8")),
    )
    write_outputs(rows, args.out_dir)
    print(f"wrote route assist frontier -> {args.out_dir / 'route_assist_frontier.html'}")


if __name__ == "__main__":
    main()
