"""Offline identification of the installed game's actual menu state machines."""
import concurrent.futures
import importlib.machinery
import json
from pathlib import Path
import re
import sys
import zlib

sys.stdout.reconfigure(encoding="utf-8")
scan = importlib.machinery.SourceFileLoader("relog_resources", "artifacts/ship-model-reference/scan-ship-assets.py").load_module()
output = Path("artifacts/relog-20261023/resources")
output.mkdir(parents=True, exist_ok=True)


def inspect(directory):
    found = []
    for path in directory.iterdir():
        if not path.is_file():
            continue
        try:
            with path.open("rb") as file:
                header = file.read(262)
            if header[:4] not in (b"FREA", b"FREE"):
                continue
            raw = pow(int.from_bytes(header[6:262], "big"), scan.E, scan.N).to_bytes(256, "big")
            prefix = raw[raw.index(b"\0", 2) + 37:]
            if b"CodeStateMachine:csm" not in prefix and b"MenuActivity" not in prefix:
                continue
            data = scan.decode(path, limit=262 + 65536)
            strings = [m.group().decode("ascii") for m in re.finditer(rb"[A-Za-z_][A-Za-z0-9_: ./-]{3,}", data[:16384])]
            candidates = [s for s in strings if re.search(r"gameflow|mainmenu|pausemenu|quit|frontend|continue|multiplayer", s, re.I)]
            full = scan.decode(path)
            blocks = []
            for match in re.finditer(rb"\x78[\x01\x9c\xda]", full[:65536]):
                try:
                    block = zlib.decompress(full[match.start():])
                    if len(block) < 32:
                        continue
                    blocks.append({"offset": match.start(), "bytes": len(block), "strings": [m.group().decode("ascii") for m in re.finditer(rb"[A-Za-z_][A-Za-z0-9_: ./-]{3,}", block)][:3000]})
                    if any(term in block.lower() for term in (b"mainmenu", b"gameflow", b"quit to", b"setupplaysolo", b"pausemenu")):
                        candidates.extend(s for s in blocks[-1]["strings"] if re.search(r"mainmenu|gameflow|quit|setupplay|pausemenu", s, re.I))
                        (output / (path.name + f"-{match.start()}.bin")).write_bytes(block)
                except zlib.error:
                    pass
            if candidates:
                (output / (path.name + ".fres")).write_bytes(full)
                found.append({"hash": path.name, "candidates": candidates, "strings": strings, "blocks": blocks})
        except (OSError, ValueError) as error:
            print(json.dumps({"error": path.name, "message": str(error)}), flush=True)
    return found


rows = []
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
    for batch in pool.map(inspect, [p for p in scan.root.iterdir() if p.is_dir() and len(p.name) == 2]):
        rows.extend(batch)
        (output / "index.json").write_text(json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8")
        print(json.dumps({"found": len(rows), "new": [{"hash": row["hash"], "candidates": row["candidates"][:10]} for row in batch]}), flush=True)
