#!/usr/bin/env python3
"""Classify the nearest local selected-pointer producer before opcode 0x08 activators."""
from __future__ import annotations

import argparse
import html
import json
from pathlib import Path
from typing import Any


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

SOURCE = "map1_01a"
TARGET = "map2_02d"
SOURCE_SELECTOR = "0:0"
PREDECESSOR_SELECTOR = "1:0"
CURRENT_SELECTOR = "2:0"


def load_json(path: Path, fallback: Any) -> Any:
    if not path.exists():
        return fallback
    return json.loads(path.read_text(encoding="utf-8"))


def va_int(row: dict) -> int:
    return int(row.get("vaHex") or "0", 16)


def producer_pointer(row: dict) -> dict:
    opcode = row.get("opcodeHex")
    if opcode == "0x07":
        pointer_class = row.get("selectedValueClass") or {}
        return {
            "producerVaHex": row.get("vaHex"),
            "producerOpcodeHex": opcode,
            "producerValueHex": row.get("valueHex"),
            "producerPointerHex": row.get("selectedValueHex"),
            "producerPointerClass": pointer_class,
            "producerPointerKind": pointer_class.get("kind"),
            "producerPointerSelector": pointer_class.get("selector"),
            "producesCurrentRoot": row.get("selectedCurrentRoot") is True,
            "producesCurrentRange": row.get("selectedCurrentRange") is True,
        }
    if opcode == "0x09":
        pointer_class = row.get("storedPointerClass") or {}
        return {
            "producerVaHex": row.get("vaHex"),
            "producerOpcodeHex": opcode,
            "producerValueHex": row.get("valueHex"),
            "producerPointerHex": row.get("storedPointerHex"),
            "producerPointerClass": pointer_class,
            "producerPointerKind": pointer_class.get("kind"),
            "producerPointerSelector": pointer_class.get("selector"),
            "producesCurrentRoot": row.get("storesCurrentRoot") is True,
            "producesCurrentRange": row.get("storesCurrentRange") is True,
        }
    return {}


def producer_bucket(selector: str, producer: dict | None) -> str:
    if not producer:
        return "no-local-producer"
    if producer.get("producesCurrentRoot"):
        return "current-root"
    if producer.get("producesCurrentRange"):
        return "current-range"
    if producer.get("producerPointerSelector") == selector:
        return "own-range"
    kind = producer.get("producerPointerKind")
    if kind:
        return str(kind)
    return "unknown"


def activation_row(selector: str, row: dict, producer: dict | None) -> dict:
    producer = producer or {}
    distance = None
    if producer.get("producerVaHex"):
        distance = va_int(row) - int(producer["producerVaHex"], 16)
    return {
        "selector": selector,
        "activationVaHex": row.get("vaHex"),
        "activationValueHex": row.get("valueHex"),
        "nearestProducerVaHex": producer.get("producerVaHex"),
        "nearestProducerOpcodeHex": producer.get("producerOpcodeHex"),
        "nearestProducerValueHex": producer.get("producerValueHex"),
        "nearestProducerPointerHex": producer.get("producerPointerHex"),
        "nearestProducerPointerKind": producer.get("producerPointerKind"),
        "nearestProducerPointerSelector": producer.get("producerPointerSelector"),
        "nearestProducerProducesCurrentRoot": producer.get("producesCurrentRoot") is True,
        "nearestProducerProducesCurrentRange": producer.get("producesCurrentRange") is True,
        "nearestProducerOwnRange": producer.get("producerPointerSelector") == selector,
        "nearestProducerBucket": producer_bucket(selector, producer),
        "distanceBytes": distance,
    }


def scan_context(scan: dict) -> dict:
    selector = scan.get("selector")
    rows = sorted(scan.get("rows") or [], key=va_int)
    last_producer: dict | None = None
    activations = []
    for row in rows:
        opcode = row.get("opcodeHex")
        if opcode in {"0x07", "0x09"}:
            last_producer = producer_pointer(row)
        elif opcode == "0x08":
            activations.append(activation_row(selector, row, last_producer))
    return summarize_context(scan, activations)


def summarize_context(scan: dict, activations: list[dict]) -> dict:
    def count(key: str, value: Any = True) -> int:
        return sum(1 for row in activations if row.get(key) == value)

    return {
        "selector": scan.get("selector"),
        "role": scan.get("role"),
        "rangeHex": scan.get("rangeHex"),
        "activationCount": len(activations),
        "nearestCurrentRootProducerCount": count("nearestProducerProducesCurrentRoot"),
        "nearestCurrentRangeProducerCount": count("nearestProducerProducesCurrentRange"),
        "nearestOwnRangeProducerCount": count("nearestProducerOwnRange"),
        "nearestScriptScalarProducerCount": count("nearestProducerBucket", "script-scalar-or-pair"),
        "nearestUnreadableProducerCount": count("nearestProducerBucket", "unreadable"),
        "nearestNoLocalProducerCount": count("nearestProducerBucket", "no-local-producer"),
        "activations": activations,
    }


def build_summary(selected_pointer_opcode_paths: dict | None = None) -> dict:
    selected_pointer_opcode_paths = selected_pointer_opcode_paths or load_json(
        OUT / "save_selector_selected_pointer_opcode_paths.json",
        {},
    )
    scans = [scan_context(scan) for scan in selected_pointer_opcode_paths.get("contextScans") or []]
    source_or_predecessor = [
        scan for scan in scans
        if scan.get("selector") in {SOURCE_SELECTOR, PREDECESSOR_SELECTOR}
    ]
    current = next((scan for scan in scans if scan.get("selector") == CURRENT_SELECTOR), {})
    source_predecessor_activator_count = sum(row["activationCount"] for row in source_or_predecessor)
    source_predecessor_current_root_count = sum(row["nearestCurrentRootProducerCount"] for row in source_or_predecessor)
    source_predecessor_current_range_count = sum(row["nearestCurrentRangeProducerCount"] for row in source_or_predecessor)
    source_predecessor_own_range_count = sum(row["nearestOwnRangeProducerCount"] for row in source_or_predecessor)
    source_predecessor_script_scalar_count = sum(row["nearestScriptScalarProducerCount"] for row in source_or_predecessor)
    source_predecessor_unreadable_count = sum(row["nearestUnreadableProducerCount"] for row in source_or_predecessor)
    current_internal_current_range_count = current.get("nearestCurrentRangeProducerCount", 0)
    conclusion = (
        "Nearest local producer windows before source/predecessor opcode 0x08 activators still do not produce "
        "the current selector 2:0 root/range. The windows are dominated by own-range continuation stores, "
        "script-scalar table selections, and unreadable pointer-like operands; only current 2:0 has nearest "
        "current-range producers, and those are internal current-root continuations. Therefore opcode 0x08 "
        "activation still requires a runtime selected-pointer trace or a real selector 2:0 save before route "
        "promotion."
    )
    return {
        "source": SOURCE,
        "target": TARGET,
        "sourceSelector": SOURCE_SELECTOR,
        "predecessorSelector": PREDECESSOR_SELECTOR,
        "currentSelector": CURRENT_SELECTOR,
        "contextWindows": scans,
        "sourceOrPredecessorOpcode08ActivatorCount": source_predecessor_activator_count,
        "sourceOrPredecessorCurrentRootProducerCount": source_predecessor_current_root_count,
        "sourceOrPredecessorCurrentRangeProducerCount": source_predecessor_current_range_count,
        "sourceOrPredecessorOwnRangeProducerCount": source_predecessor_own_range_count,
        "sourceOrPredecessorScriptScalarProducerCount": source_predecessor_script_scalar_count,
        "sourceOrPredecessorUnreadableProducerCount": source_predecessor_unreadable_count,
        "currentInternalCurrentRangeProducerCount": current_internal_current_range_count,
        "opcode08ActivationPromotesRoute": False,
        "promotionStatus": "blocked",
        "remainingProofs": [
            "capture a selected-pointer runtime trace at opcode 0x08",
            "replace the synthetic selector 2:0 savedat vector with a captured gameplay save",
            "find a strict map1_01a source coordinate or hotspot",
        ],
        "conclusion": conclusion,
    }


def context_brief(row: dict) -> str:
    return (
        f"op8={row.get('activationCount')} "
        f"currentRange={row.get('nearestCurrentRangeProducerCount')} "
        f"ownRange={row.get('nearestOwnRangeProducerCount')} "
        f"scriptScalar={row.get('nearestScriptScalarProducerCount')} "
        f"unreadable={row.get('nearestUnreadableProducerCount')}"
    )


def interesting_activations(context: dict) -> list[dict]:
    rows = []
    for row in context.get("activations") or []:
        if row.get("nearestProducerProducesCurrentRange"):
            rows.append(row)
        elif len(rows) < 10:
            rows.append(row)
    return rows[:32]


def markdown(summary: dict) -> str:
    lines = [
        "# Save Selector Opcode 0x08 Activation Windows",
        "",
        f"- route: {summary['source']} -> {summary['target']}",
        f"- source/predecessor activators: {summary['sourceOrPredecessorOpcode08ActivatorCount']}",
        f"- source/predecessor current-root producers: {summary['sourceOrPredecessorCurrentRootProducerCount']}",
        f"- source/predecessor current-range producers: {summary['sourceOrPredecessorCurrentRangeProducerCount']}",
        f"- own-range producers: {summary['sourceOrPredecessorOwnRangeProducerCount']}",
        f"- script-scalar producers: {summary['sourceOrPredecessorScriptScalarProducerCount']}",
        f"- unreadable producers: {summary['sourceOrPredecessorUnreadableProducerCount']}",
        f"- current internal current-range producers: {summary['currentInternalCurrentRangeProducerCount']}",
        f"- opcode08 activation promotes route: {summary['opcode08ActivationPromotesRoute']}",
        f"- promotion status: `{summary['promotionStatus']}`",
        "",
        summary["conclusion"],
        "",
        "## Context Windows",
        "",
        "| selector | role | range | counts |",
        "| --- | --- | --- | --- |",
    ]
    for context in summary["contextWindows"]:
        lines.append(
            f"| `{context['selector']}` | {context['role']} | `{context['rangeHex']}` | {context_brief(context)} |"
        )
    lines.extend([
        "",
        "## Activation Samples",
        "",
        "| selector | activation | nearest producer | opcode | pointer | bucket | distance |",
        "| --- | --- | --- | --- | --- | --- | ---: |",
    ])
    for context in summary["contextWindows"]:
        for row in interesting_activations(context):
            lines.append(
                f"| `{row['selector']}` | `{row['activationVaHex']}` | `{row.get('nearestProducerVaHex') or '-'}` | "
                f"`{row.get('nearestProducerOpcodeHex') or '-'}` | `{row.get('nearestProducerPointerHex') or '-'}` | "
                f"{row.get('nearestProducerBucket') or '-'} | {row.get('distanceBytes') if row.get('distanceBytes') is not None else '-'} |"
            )
    lines.extend(["", "## Remaining Proofs", ""])
    lines.extend(f"- {item}" for item in summary["remainingProofs"])
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    context_rows = "\n".join(
        "<tr>"
        f"<td><code>{html.escape(str(context['selector']))}</code></td>"
        f"<td>{html.escape(str(context['role']))}</td>"
        f"<td><code>{html.escape(str(context['rangeHex']))}</code></td>"
        f"<td>{html.escape(context_brief(context))}</td>"
        "</tr>"
        for context in summary["contextWindows"]
    )
    activation_rows = []
    for context in summary["contextWindows"]:
        for row in interesting_activations(context):
            activation_rows.append(
                "<tr>"
                f"<td><code>{html.escape(str(row['selector']))}</code></td>"
                f"<td><code>{html.escape(str(row['activationVaHex']))}</code></td>"
                f"<td><code>{html.escape(str(row.get('nearestProducerVaHex') or '-'))}</code></td>"
                f"<td><code>{html.escape(str(row.get('nearestProducerOpcodeHex') or '-'))}</code></td>"
                f"<td><code>{html.escape(str(row.get('nearestProducerPointerHex') or '-'))}</code></td>"
                f"<td>{html.escape(str(row.get('nearestProducerBucket') or '-'))}</td>"
                f"<td>{html.escape(str(row.get('distanceBytes') if row.get('distanceBytes') is not None else '-'))}</td>"
                "</tr>"
            )
    proofs = "".join(f"<li>{html.escape(item)}</li>" for item in summary["remainingProofs"])
    return "\n".join([
        "<!doctype html>",
        '<html lang="en">',
        "<head>",
        '  <meta charset="utf-8">',
        '  <meta name="viewport" content="width=device-width, initial-scale=1">',
        "  <title>Save Selector Opcode 0x08 Activation Windows</title>",
        "  <style>",
        "    :root { color-scheme: dark; font-family: system-ui, sans-serif; background: #101010; color: #eee; }",
        "    body { margin: 0; padding: 24px; }",
        "    table { width: 100%; border-collapse: collapse; margin: 12px 0 20px; font-size: 13px; }",
        "    th, td { border-bottom: 1px solid #303030; padding: 7px 8px; text-align: left; vertical-align: top; }",
        "    th { background: #181818; color: #ddd; }",
        "    code { color: #d7f0ff; }",
        "  </style>",
        "</head>",
        "<body>",
        "  <h1>Save Selector Opcode 0x08 Activation Windows</h1>",
        f"  <p>route {html.escape(summary['source'])} -&gt; {html.escape(summary['target'])}; source/predecessor activators {summary['sourceOrPredecessorOpcode08ActivatorCount']}; source/predecessor current-range producers {summary['sourceOrPredecessorCurrentRangeProducerCount']}; opcode08 activation promotes route {summary['opcode08ActivationPromotesRoute']}.</p>",
        f"  <p>own-range producers {summary['sourceOrPredecessorOwnRangeProducerCount']}; script-scalar producers {summary['sourceOrPredecessorScriptScalarProducerCount']}; unreadable producers {summary['sourceOrPredecessorUnreadableProducerCount']}; current internal current-range producers {summary['currentInternalCurrentRangeProducerCount']}.</p>",
        f"  <p>{html.escape(summary['conclusion'])}</p>",
        "  <h2>Context Windows</h2>",
        "  <table><thead><tr><th>selector</th><th>role</th><th>range</th><th>counts</th></tr></thead><tbody>",
        context_rows,
        "  </tbody></table>",
        "  <h2>Activation Samples</h2>",
        "  <table><thead><tr><th>selector</th><th>activation</th><th>nearest producer</th><th>opcode</th><th>pointer</th><th>bucket</th><th>distance</th></tr></thead><tbody>",
        "\n".join(activation_rows),
        "  </tbody></table>",
        f"  <h2>Remaining Proofs</h2><ul>{proofs}</ul>",
        "</body></html>",
    ])


def write_outputs(summary: dict, out_dir: Path, html_out: Path | None = None) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "save_selector_opcode08_activation_windows.json").write_text(
        json.dumps(summary, ensure_ascii=False, separators=(",", ":")),
        encoding="utf-8",
    )
    if html_out is not None:
        html_out.parent.mkdir(parents=True, exist_ok=True)
        html_out.write_text(html_page(summary), encoding="utf-8")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--selected-pointer", type=Path, default=OUT / "save_selector_selected_pointer_opcode_paths.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    parser.add_argument("--html-out", type=Path, default=None, help="Optional HTML report output path.")
    args = parser.parse_args()
    summary = build_summary(load_json(args.selected_pointer, {}))
    write_outputs(summary, args.out_dir, args.html_out)
    print(f"wrote save selector opcode 0x08 activation windows -> {args.out_dir / 'save_selector_opcode08_activation_windows.json'}")


if __name__ == "__main__":
    main()
