#!/usr/bin/env python3
"""Summarize the legacy public Hwanse save editor as non-route evidence.

The editor binary is not committed. This report records only bounded metadata
and string/control-name evidence from a local copy extracted from a public
flack3r attachment.
"""
from __future__ import annotations

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


ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
DEFAULT_EDITOR = Path("/tmp/3586_hwanedit.exe")
SOURCE = "map1_01a"
TARGET = "map2_02d"


def sha256_bytes(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


def ascii_strings(data: bytes, min_len: int = 4) -> list[str]:
    rows: list[str] = []
    current = bytearray()
    for value in data:
        if 0x20 <= value <= 0x7E:
            current.append(value)
            continue
        if len(current) >= min_len:
            rows.append(current.decode("ascii", errors="replace"))
        current.clear()
    if len(current) >= min_len:
        rows.append(current.decode("ascii", errors="replace"))
    return rows


def component_names(strings: list[str]) -> list[str]:
    names: set[str] = set()
    for text in strings:
        for match in re.finditer(r"\b(?:Txt|Img|Button|Label|TabSheet|OpenDialog|StatusBar)[A-Za-z0-9_]+", text):
            names.add(match.group(0))
    return sorted(names)


def keyword_hits(strings: list[str], keywords: list[str]) -> dict[str, list[str]]:
    lowered = [(text, text.lower()) for text in strings]
    hits: dict[str, list[str]] = {}
    for keyword in keywords:
        needle = keyword.lower()
        rows = [text for text, lower in lowered if needle in lower]
        hits[keyword] = rows[:20]
    return hits


def build_summary(exe_path: Path = DEFAULT_EDITOR) -> dict[str, Any]:
    if not exe_path.exists():
        return {
            "source": SOURCE,
            "target": TARGET,
            "editorPath": str(exe_path),
            "available": False,
            "promotionStatus": "blocked",
            "conclusion": "Legacy public hwanedit executable was not available for this local summary run.",
        }
    data = exe_path.read_bytes()
    strings = ascii_strings(data)
    components = component_names(strings)
    text = "\n".join(strings).lower()
    stat_components = [
        name for name in components
        if any(token in name.lower() for token in ("hp", "mp", "exp", "level", "money", "potion"))
    ]
    keywords = [
        "savedat",
        "SaveData",
        "selector",
        "map1_01a",
        "map2_02d",
        "0x00540714",
        "00540714",
        "route",
        "scene",
        "stage",
    ]
    hits = keyword_hits(strings, keywords)
    selector_hits = [row for row in hits["selector"] if row != "IHelpSelector<"]
    route_keyword_count = (
        len(selector_hits)
        + sum(len(hits[key]) for key in ["map1_01a", "map2_02d", "0x00540714", "00540714", "route"])
    )
    save_filter_present = any("(Savedat?.dat)|savedat?.dat|" in row for row in strings)
    conclusion = (
        "The legacy public hwanedit executable is a savedat stat/item editor surface: "
        f"saveFilterPresent={save_filter_present}, statControlCount={len(stat_components)}, "
        f"routeKeywordCount={route_keyword_count}. It provides no selector 2:0, selected pointer 0x00540714, "
        "strict hotspot, or normal-route execution proof for map1_01a -> map2_02d."
    )
    return {
        "source": SOURCE,
        "target": TARGET,
        "editorPath": str(exe_path),
        "available": True,
        "size": len(data),
        "sha256": sha256_bytes(data),
        "expectedPublicAttachmentName": "3586_hwanedit-sya2727-rudals1323.exe",
        "stringCount": len(strings),
        "componentNameCount": len(components),
        "statControlCount": len(stat_components),
        "saveFilterPresent": save_filter_present,
        "unitStrings": [item for item in ["HwanseSaveUnit", "FHwanseCommonUnit", "HwanseOpenUnit"] if item in strings],
        "sampleComponents": components[:80],
        "statComponents": stat_components[:80],
        "keywordHits": hits,
        "selectorHitsPromoting": selector_hits,
        "routeKeywordCount": route_keyword_count,
        "selectorEvidenceFound": route_keyword_count > 0,
        "routePromotionEvidenceFound": False,
        "promotionStatus": "blocked",
        "conclusion": conclusion,
    }


def markdown(summary: dict[str, Any]) -> str:
    lines = [
        "# Legacy Hwanedit Editor Evidence Gap",
        "",
        f"- route: `{summary.get('source')} -> {summary.get('target')}`",
        f"- available: {summary.get('available')}",
        f"- editor path: `{summary.get('editorPath')}`",
        f"- size: {summary.get('size')}",
        f"- sha256: `{summary.get('sha256') or '-'}`",
        f"- save filter present: {summary.get('saveFilterPresent')}",
        f"- component names: {summary.get('componentNameCount')}",
        f"- stat controls: {summary.get('statControlCount')}",
        f"- route keyword count: {summary.get('routeKeywordCount')}",
        f"- selector evidence found: {summary.get('selectorEvidenceFound')}",
        f"- route promotion evidence found: {summary.get('routePromotionEvidenceFound')}",
        f"- promotion status: `{summary.get('promotionStatus')}`",
        "",
        str(summary.get("conclusion") or ""),
        "",
        "## Unit Strings",
        "",
    ]
    lines.extend(f"- `{item}`" for item in summary.get("unitStrings") or [])
    if not summary.get("unitStrings"):
        lines.append("- -")
    lines.extend(["", "## Stat Controls", ""])
    lines.extend(f"- `{item}`" for item in summary.get("statComponents") or [])
    if not summary.get("statComponents"):
        lines.append("- -")
    lines.extend(["", "## Keyword Hits", "", "| keyword | hit count | sample hits |", "| --- | ---: | --- |"])
    for keyword, rows in (summary.get("keywordHits") or {}).items():
        sample = ", ".join(f"`{row}`" for row in rows[:4]) or "-"
        lines.append(f"| `{keyword}` | {len(rows)} | {sample} |")
    return "\n".join(lines) + "\n"


def html_page(summary: dict[str, Any]) -> str:
    esc = html.escape
    unit_rows = "\n".join(f"<li><code>{esc(item)}</code></li>" for item in summary.get("unitStrings") or [])
    stat_rows = "\n".join(f"<li><code>{esc(item)}</code></li>" for item in summary.get("statComponents") or [])
    keyword_rows = "\n".join(
        "<tr>"
        f"<td><code>{esc(keyword)}</code></td>"
        f"<td>{len(rows)}</td>"
        f"<td>{esc(', '.join(rows[:4]) or '-')}</td>"
        "</tr>"
        for keyword, rows in (summary.get("keywordHits") or {}).items()
    )
    return "\n".join([
        "<!doctype html>",
        '<html lang="en">',
        "<head>",
        '  <meta charset="utf-8">',
        '  <meta name="viewport" content="width=device-width, initial-scale=1">',
        "  <title>Legacy Hwanedit Editor Evidence Gap</title>",
        "  <style>",
        "    :root { color-scheme: dark; font-family: system-ui, sans-serif; background: #111; color: #eee; }",
        "    body { margin: 0; padding: 24px; }",
        "    table { border-collapse: collapse; width: 100%; }",
        "    th, td { border-bottom: 1px solid #333; padding: 6px 8px; text-align: left; vertical-align: top; }",
        "    code { color: #d7f0ff; }",
        "  </style>",
        "</head>",
        "<body>",
        "  <h1>Legacy Hwanedit Editor Evidence Gap</h1>",
        f"  <p>route <code>{esc(str(summary.get('source')))} -&gt; {esc(str(summary.get('target')))}</code>; "
        f"available {summary.get('available')}; sha256 <code>{esc(str(summary.get('sha256') or '-'))}</code>; "
        f"save filter present {summary.get('saveFilterPresent')}; stat controls {summary.get('statControlCount')}; "
        f"route keyword count {summary.get('routeKeywordCount')}; "
        f"selector evidence found {summary.get('selectorEvidenceFound')}; "
        f"route promotion evidence found {summary.get('routePromotionEvidenceFound')}; "
        f"promotion status <code>{esc(str(summary.get('promotionStatus')))}</code>.</p>",
        f"  <p>{esc(str(summary.get('conclusion') or ''))}</p>",
        "  <h2>Unit Strings</h2>",
        f"  <ul>{unit_rows or '<li>-</li>'}</ul>",
        "  <h2>Stat Controls</h2>",
        f"  <ul>{stat_rows or '<li>-</li>'}</ul>",
        "  <h2>Keyword Hits</h2>",
        "  <table><thead><tr><th>keyword</th><th>hit count</th><th>sample hits</th></tr></thead><tbody>",
        keyword_rows,
        "  </tbody></table>",
        "</body>",
        "</html>",
        "",
    ])


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


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--exe", type=Path, default=DEFAULT_EDITOR)
    parser.add_argument("--out-dir", type=Path, default=OUT)
    args = parser.parse_args()
    summary = build_summary(args.exe)
    write_outputs(summary, args.out_dir)
    print(f"wrote legacy hwanedit editor gap -> {args.out_dir / 'legacy_hwanedit_editor_gap.html'}")


if __name__ == "__main__":
    main()
