"""Reverse-engineering helper: hexdump and diff RoborunPlus-exported .hex files.

Used against the real corpus at X:\\ESP\\FIDO\\PumpController\\testdata (pairs
of .mbs source + RoborunPlus-compiled .hex, real pump-controller revisions --
not a synthetic corpus, but very effective for this anyway since several
adjacent revisions differ by only one or two source-level edits).

Usage:
    python tools/hexdiff.py dump <file.hex>
    python tools/hexdiff.py diff <old.hex> <new.hex>

Requires the package to be installed (`pip install -e .`) so
`microbasic_compiler.hexfile` is importable.
"""

from __future__ import annotations

import argparse
import sys
from pathlib import Path

from microbasic_compiler.hexfile import read_intel_hex


def hexdump(data: bytes, width: int = 16) -> None:
    for i in range(0, len(data), width):
        chunk = data[i : i + width]
        hexpart = " ".join(f"{b:02X}" for b in chunk)
        asciipart = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
        print(f"{i:04X}  {hexpart:<{width * 3}}  {asciipart}")


def diff(a: bytes, b: bytes, context: int = 8) -> None:
    if len(a) != len(b):
        print(f"length differs: {len(a)} vs {len(b)} bytes (byte-for-byte diff below only covers the shared prefix)")
    n = min(len(a), len(b))
    diffs = [i for i in range(n) if a[i] != b[i]]
    print(f"{len(diffs)} differing byte(s) in shared range")
    for i in diffs:
        av, bv = a[i], b[i]
        ac = chr(av) if 32 <= av < 127 else "."
        bc = chr(bv) if 32 <= bv < 127 else "."
        print(f"  offset 0x{i:04X}: A={av:02X} ({ac})  B={bv:02X} ({bc})")
    for i in diffs:
        lo, hi = max(0, i - context), i + context
        print(f"  --- context around 0x{i:04X} ---")
        print("  A:", a[lo:hi].hex(" "))
        print("  B:", b[lo:hi].hex(" "))
    if len(a) != n or len(b) != n:
        longer, name = (a, "A") if len(a) > len(b) else (b, "B")
        print(f"  trailing bytes only in {name} (from offset 0x{n:04X}):")
        print(" ", longer[n:].hex(" "))


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    sub = parser.add_subparsers(dest="command", required=True)

    dump_p = sub.add_parser("dump", help="Hexdump a .hex file's decoded payload")
    dump_p.add_argument("file", type=Path)

    diff_p = sub.add_parser("diff", help="Byte-diff two .hex files' decoded payloads")
    diff_p.add_argument("old", type=Path)
    diff_p.add_argument("new", type=Path)

    args = parser.parse_args(argv)

    if args.command == "dump":
        hexdump(read_intel_hex(args.file.read_text()))
    elif args.command == "diff":
        diff(read_intel_hex(args.old.read_text()), read_intel_hex(args.new.read_text()))

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
