#!/usr/bin/env python3
"""Apply public savedat byte samples to the current gate-base candidates."""
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"
CURRENT_FRONTIER_GROUP = 2
CURRENT_FRONTIER_SLOT = 0
EXPECTED_INDEX_MIN = 0
EXPECTED_INDEX_MAX = 11
FAILED_GATE_SAMPLE_VALUE_GATE_IDS = [
    "current-selector-2-0-sample",
    "runtime-pointer-mode-proof",
    "party-slot-index-evidence",
    "control-path-gate-byte-proof",
]
GATE_SAMPLE_VALUE_MISSING_EVIDENCE = [
    "public or captured sample covering current selector 2:0 gate bytes",
    "runtime pointer-mode proof selecting the correct gate base",
    "party-slot byte evidence that yields a valid branch-state index rather than stat bytes",
    "control-path proof that sampled gate bytes apply to the current frontier gates",
]
GATE_SAMPLE_VALUE_EVIDENCE_REFS = [
    {
        "path": "data/savedata_sample_gate_values.json",
        "description": "public savedat byte samples used to calibrate gate offsets",
    },
    {
        "path": "out/save_selector_gate_base_candidates.json",
        "description": "candidate gate-base mappings and save offsets being sampled",
    },
]


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


def hex8(value: int) -> str:
    return f"0x{value:02x}"


def sample_selector(sample: dict) -> str:
    return f"{sample.get('selectorGroup')}:{sample.get('selectorSlot')}"


def sample_tile(sample: dict) -> str:
    return f"{sample.get('tileX')},{sample.get('tileY')}"


def field_text(row: dict) -> str:
    field = row.get("knownField")
    if not field:
        return "-"
    return f"{field['field']} ({field['byteRole']})"


def value_at(sample: dict, offset_hex: str) -> int | None:
    values = sample.get("values") or {}
    value = values.get(offset_hex)
    if value is None:
        value = values.get(offset_hex.lower())
    return value if isinstance(value, int) else None


def in_expected_range(value: int | None) -> bool:
    return isinstance(value, int) and EXPECTED_INDEX_MIN <= value <= EXPECTED_INDEX_MAX


def candidate_rows(gate_base_candidates: dict, base_kind: str, classification: str | None = None) -> list[dict]:
    rows = [
        row for row in gate_base_candidates.get("baseCandidates") or []
        if row.get("baseKind") == base_kind and row.get("saveOffsetHex")
    ]
    if classification is not None:
        rows = [row for row in rows if row.get("classification") == classification]
    return rows


def sample_value_rows(samples: list[dict], candidates: list[dict]) -> list[dict]:
    rows = []
    for sample in samples:
        selector = sample_selector(sample)
        covers_current = (
            sample.get("selectorGroup") == CURRENT_FRONTIER_GROUP
            and sample.get("selectorSlot") == CURRENT_FRONTIER_SLOT
        )
        for candidate in candidates:
            offset_hex = candidate.get("saveOffsetHex")
            value = value_at(sample, offset_hex)
            rows.append({
                "sampleId": sample.get("id"),
                "samplePath": sample.get("path"),
                "selector": selector,
                "selectorGroup": sample.get("selectorGroup"),
                "selectorSlot": sample.get("selectorSlot"),
                "tile": sample_tile(sample),
                "coversCurrentFrontierSelector": covers_current,
                "baseKind": candidate.get("baseKind"),
                "slot": candidate.get("slot"),
                "gateOffsetHex": candidate.get("gateOffsetHex"),
                "saveOffsetHex": offset_hex,
                "knownField": candidate.get("knownField"),
                "value": value,
                "valueHex": hex8(value) if value is not None else None,
                "inExpectedIndexRange": in_expected_range(value),
                "candidateClassification": candidate.get("classification"),
            })
    return rows


def distinct_values(rows: list[dict]) -> list[str]:
    return sorted({row["valueHex"] for row in rows if row.get("valueHex")})


def build_summary(samples: dict | None = None, gate_base_candidates: dict | None = None) -> dict:
    samples = samples if samples is not None else load_json(ROOT / "data" / "savedata_sample_gate_values.json", {})
    gate_base_candidates = gate_base_candidates if gate_base_candidates is not None else load_json(
        OUT / "save_selector_gate_base_candidates.json",
        {},
    )
    sample_items = samples.get("samples") or []
    save_runtime_candidates = candidate_rows(gate_base_candidates, "save/runtime block base")
    party_slot_stat_candidates = candidate_rows(
        gate_base_candidates,
        "party slot base",
        classification="implausible-stat-byte-index",
    )
    save_runtime_rows = sample_value_rows(sample_items, save_runtime_candidates)
    party_slot_stat_rows = sample_value_rows(sample_items, party_slot_stat_candidates)
    current_frontier_covered = any(
        sample.get("selectorGroup") == CURRENT_FRONTIER_GROUP
        and sample.get("selectorSlot") == CURRENT_FRONTIER_SLOT
        for sample in sample_items
    )
    sample_sources = []
    for sample in sample_items:
        source = sample.get("source") or samples.get("source")
        source_url = sample.get("sourceUrl") or samples.get("sourceUrl")
        item = {"source": source, "sourceUrl": source_url}
        if source and item not in sample_sources:
            sample_sources.append(item)
    selectors = sorted({sample_selector(sample) for sample in sample_items})
    conclusion = (
        "The public savedat bytes keep the save/runtime block base plausible only as calibration: "
        "loader-aware mapping puts 0x004576d8+0xe8/0xea at save offsets 0x00e2/0x00e4 after the six-byte runtime gap, "
        "and all public samples read 0x00 there. Those values are in the expected 0..11 branch-index range, but the "
        "samples cover selectors 0:0, 1:0, and 22:0, not the current frontier selector 2:0. The same samples make the "
        "party-slot-base mapping weaker: the corresponding stat-byte candidates are 0xe7, 0xa6, or 0x7a, outside "
        "the expected selection-index range. This rejects the stat-byte interpretation as promotion evidence while "
        "leaving global/save-runtime buffer or object pointer table runtime proof still required."
    )
    return {
        "source": SOURCE,
        "target": TARGET,
        "sampleSource": ", ".join(row["source"] for row in sample_sources),
        "sampleSourceUrl": ", ".join(row["sourceUrl"] for row in sample_sources if row.get("sourceUrl")),
        "sampleSources": sample_sources,
        "gateOffsetsHex": gate_base_candidates.get("gateOffsetsHex") or [],
        "expectedIndexRange": [EXPECTED_INDEX_MIN, EXPECTED_INDEX_MAX],
        "currentFrontierSelector": f"{CURRENT_FRONTIER_GROUP}:{CURRENT_FRONTIER_SLOT}",
        "sampleCount": len(sample_items),
        "uniqueSelectorCount": len(selectors),
        "sampleSelectors": selectors,
        "currentFrontierSampleCovered": current_frontier_covered,
        "saveRuntimeGateCandidateCount": len(save_runtime_candidates),
        "partySlotStatCandidateCount": len(party_slot_stat_candidates),
        "saveRuntimeGateSampleRows": save_runtime_rows,
        "partySlotStatSampleRows": party_slot_stat_rows,
        "saveRuntimeGateSampleValueCount": sum(1 for row in save_runtime_rows if row.get("value") is not None),
        "saveRuntimeGateInRangeSampleValueCount": sum(1 for row in save_runtime_rows if row["inExpectedIndexRange"]),
        "saveRuntimeGateDistinctValuesHex": distinct_values(save_runtime_rows),
        "partySlotStatSampleValueCount": sum(1 for row in party_slot_stat_rows if row.get("value") is not None),
        "partySlotStatInRangeSampleValueCount": sum(1 for row in party_slot_stat_rows if row["inExpectedIndexRange"]),
        "partySlotStatDistinctValuesHex": distinct_values(party_slot_stat_rows),
        "runtimePointerModeStillRequired": True,
        "controlPathProofStatus": "blocked",
        "proofFound": False,
        "gateSampleValueProofFound": False,
        "failedGateSampleValueGateIds": FAILED_GATE_SAMPLE_VALUE_GATE_IDS,
        "missingEvidence": GATE_SAMPLE_VALUE_MISSING_EVIDENCE,
        "evidenceRefs": GATE_SAMPLE_VALUE_EVIDENCE_REFS,
        "evidenceRefCount": len(GATE_SAMPLE_VALUE_EVIDENCE_REFS),
        "promotionStatus": "blocked",
        "conclusion": conclusion,
    }


def markdown(summary: dict) -> str:
    lines = [
        "# Save Selector Gate Sample Values",
        "",
        f"- route: {summary['source']} -> {summary['target']}",
        f"- sample source: {summary['sampleSource']} ({summary['sampleSourceUrl']})",
        f"- gate offsets: {', '.join(f'`{item}`' for item in summary['gateOffsetsHex'])}",
        f"- expected selection index range: {summary['expectedIndexRange'][0]}..{summary['expectedIndexRange'][1]}",
        f"- sample count: {summary['sampleCount']}",
        f"- sample selectors: {', '.join(f'`{item}`' for item in summary['sampleSelectors'])}",
        f"- current frontier selector: `{summary['currentFrontierSelector']}`",
        f"- current frontier sample covered: {summary['currentFrontierSampleCovered']}",
        f"- save/runtime in-range values: {summary['saveRuntimeGateInRangeSampleValueCount']} / {summary['saveRuntimeGateSampleValueCount']}",
        f"- party-slot stat in-range values: {summary['partySlotStatInRangeSampleValueCount']} / {summary['partySlotStatSampleValueCount']}",
        f"- runtime pointer mode still required: {summary['runtimePointerModeStillRequired']}",
        f"- proofFound: `{summary['proofFound']}`",
        f"- gateSampleValueProofFound: `{summary['gateSampleValueProofFound']}`",
        f"- promotion status: `{summary['promotionStatus']}`",
        "",
        summary["conclusion"],
        "",
        "## Failed Gates",
        "",
    ]
    lines.extend(f"- `{item}`" for item in summary["failedGateSampleValueGateIds"])
    lines.extend([
        "",
        "## Missing Evidence",
        "",
    ])
    lines.extend(f"- {item}" for item in summary["missingEvidence"])
    lines.extend([
        "",
        "## Evidence Refs",
        "",
    ])
    lines.extend(
        f"- `{row['path']}`: {row['description']}"
        for row in summary["evidenceRefs"]
    )
    lines.extend([
        "",
        "## Save/Runtime Gate Bytes",
        "",
        "| sample | selector | tile | gate | save offset | value | in range | covers current selector |",
        "| --- | --- | --- | --- | --- | ---: | --- | --- |",
    ])
    for row in summary["saveRuntimeGateSampleRows"]:
        lines.append(
            f"| {row['sampleId']} | `{row['selector']}` | `{row['tile']}` | `{row['gateOffsetHex']}` | "
            f"`{row['saveOffsetHex']}` | `{row['valueHex'] or '-'}` | {row['inExpectedIndexRange']} | "
            f"{row['coversCurrentFrontierSelector']} |"
        )
    lines.extend([
        "",
        "## Party Slot Stat Bytes",
        "",
        "| sample | selector | slot | gate | save offset | known save field | value | in range |",
        "| --- | --- | ---: | --- | --- | --- | ---: | --- |",
    ])
    for row in summary["partySlotStatSampleRows"]:
        lines.append(
            f"| {row['sampleId']} | `{row['selector']}` | {row['slot']} | `{row['gateOffsetHex']}` | "
            f"`{row['saveOffsetHex']}` | {field_text(row)} | `{row['valueHex'] or '-'}` | "
            f"{row['inExpectedIndexRange']} |"
        )
    lines.append("")
    return "\n".join(lines)


def html_page(summary: dict) -> str:
    failed_gates = "".join(
        f"<li><code>{html.escape(item)}</code></li>"
        for item in summary["failedGateSampleValueGateIds"]
    )
    missing_evidence = "".join(
        f"<li>{html.escape(item)}</li>"
        for item in summary["missingEvidence"]
    )
    evidence_refs = "".join(
        f"<li><code>{html.escape(row['path'])}</code>: {html.escape(row['description'])}</li>"
        for row in summary["evidenceRefs"]
    )
    save_runtime_rows = []
    for row in summary["saveRuntimeGateSampleRows"]:
        save_runtime_rows.append(
            "<tr>"
            f"<td>{html.escape(row['sampleId'] or '-')}</td>"
            f"<td><code>{html.escape(row['selector'])}</code></td>"
            f"<td><code>{html.escape(row['tile'])}</code></td>"
            f"<td><code>{html.escape(row['gateOffsetHex'])}</code></td>"
            f"<td><code>{html.escape(row['saveOffsetHex'])}</code></td>"
            f"<td><code>{html.escape(row['valueHex'] or '-')}</code></td>"
            f"<td>{row['inExpectedIndexRange']}</td>"
            f"<td>{row['coversCurrentFrontierSelector']}</td>"
            "</tr>"
        )
    party_slot_rows = []
    for row in summary["partySlotStatSampleRows"]:
        party_slot_rows.append(
            "<tr>"
            f"<td>{html.escape(row['sampleId'] or '-')}</td>"
            f"<td><code>{html.escape(row['selector'])}</code></td>"
            f"<td>{row['slot']}</td>"
            f"<td><code>{html.escape(row['gateOffsetHex'])}</code></td>"
            f"<td><code>{html.escape(row['saveOffsetHex'])}</code></td>"
            f"<td>{html.escape(field_text(row))}</td>"
            f"<td><code>{html.escape(row['valueHex'] or '-')}</code></td>"
            f"<td>{row['inExpectedIndexRange']}</td>"
            "</tr>"
        )
    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 Gate Sample Values</title>",
        "  <style>",
        "    :root { color-scheme: dark; font-family: system-ui, sans-serif; background: #101010; color: #eee; }",
        "    body { margin: 0; padding: 24px; }",
        "    h1 { margin: 0 0 8px; font-size: 24px; }",
        "    h2 { margin: 24px 0 8px; font-size: 18px; }",
        "    p { max-width: 1120px; color: #bbb; line-height: 1.45; }",
        "    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 { position: sticky; top: 0; background: #181818; color: #ddd; }",
        "    code { color: #d7f0ff; }",
        "  </style>",
        "</head>",
        "<body>",
        "  <h1>Save Selector Gate Sample Values</h1>",
        f"  <p>route {html.escape(summary['source'])} -&gt; {html.escape(summary['target'])}; "
        f"sample source {html.escape(summary.get('sampleSource') or '-')}; "
        f"sample selectors {html.escape(', '.join(summary['sampleSelectors']))}; "
        f"current frontier selector <code>{html.escape(summary['currentFrontierSelector'])}</code>; "
        f"save/runtime in-range values {summary['saveRuntimeGateInRangeSampleValueCount']} / {summary['saveRuntimeGateSampleValueCount']}; "
        f"party-slot stat in-range values {summary['partySlotStatInRangeSampleValueCount']} / {summary['partySlotStatSampleValueCount']}; "
        f"proofFound <code>{summary['proofFound']}</code>; "
        f"promotion status <code>{html.escape(summary['promotionStatus'])}</code>.</p>",
        f"  <p>{html.escape(summary['conclusion'])}</p>",
        "  <h2>Failed Gates</h2>",
        f"  <ul>{failed_gates}</ul>",
        "  <h2>Missing Evidence</h2>",
        f"  <ul>{missing_evidence}</ul>",
        "  <h2>Evidence Refs</h2>",
        f"  <ul>{evidence_refs}</ul>",
        "  <h2>Save/Runtime Gate Bytes</h2>",
        "  <table><thead><tr><th>sample</th><th>selector</th><th>tile</th><th>gate</th><th>save offset</th><th>value</th><th>in range</th><th>covers current selector</th></tr></thead><tbody>",
        *save_runtime_rows,
        "  </tbody></table>",
        "  <h2>Party Slot Stat Bytes</h2>",
        "  <table><thead><tr><th>sample</th><th>selector</th><th>slot</th><th>gate</th><th>save offset</th><th>known save field</th><th>value</th><th>in range</th></tr></thead><tbody>",
        *party_slot_rows,
        "  </tbody></table>",
        "</body>",
        "</html>",
        "",
    ])


def write_outputs(summary: dict, out_dir: Path = OUT) -> None:
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "save_selector_gate_sample_values.json").write_text(
        json.dumps(summary, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    (out_dir / "save_selector_gate_sample_values.html").write_text(html_page(summary), encoding="utf-8")


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--samples", type=Path, default=ROOT / "data" / "savedata_sample_gate_values.json")
    parser.add_argument("--gate-base-candidates", type=Path, default=OUT / "save_selector_gate_base_candidates.json")
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    summary = build_summary(
        load_json(args.samples, {}),
        load_json(args.gate_base_candidates, {}),
    )
    write_outputs(summary, args.out_dir)
    print(f"wrote save selector gate sample values -> {args.out_dir / 'save_selector_gate_sample_values.html'}")


if __name__ == "__main__":
    main()
