#!/usr/bin/env python3
"""Watch J19 GPIO candidates for HS IN sense (after OUT proven on BCM3/BCM8).

OUT phase (silk pins 3+5): BCM3 = OIS_OUT, BCM8 = M-S_OUT.
IN phase: poll remaining lines as inputs; print every level change so we can
see which pad follows bus HS / LED activity.

Defaults watch BCM 9, 10, 11, 2 (everything on the flex except the OUT pair).

  cd ~/EtherGold/sync
  sudo ./run_hs_in_watch.sh
  # or: sudo python3 hs_in_watch.py --bcms 9,10,11,2

3.3 V GPIO only. Ctrl-C restores pull-ups.
"""

from __future__ import annotations

import argparse
import signal
import sys
import time

from j19_io_mapper import MapperError, pinctrl_get, set_input_pull_up

# OUT pair (do not watch as IN unless --include-out).
OUT_BCMS = {3, 8}
DEFAULT_IN_BCMS = (9, 10, 11, 2)
LABELS = {
    11: "probe-M-S_OUT / candidate",
    9: "probe-M-S_IN / preferred",
    10: "probe-OIS_OUT / candidate",
    8: "OUT M-S (silk p5)",
    2: "LED1 / candidate",
    3: "OUT OIS (silk p3)",
}


def _level_hi(bcm: int) -> bool | None:
    text = pinctrl_get(bcm).lower()
    if " hi " in f" {text} " or text.rstrip().endswith("hi") or "| hi" in text:
        return True
    if " lo " in f" {text} " or text.rstrip().endswith("lo") or "| lo" in text:
        return False
    # Fallback: look for "level=N" style
    if "level=1" in text or " = 1" in text:
        return True
    if "level=0" in text or " = 0" in text:
        return False
    return None


def main() -> int:
    ap = argparse.ArgumentParser(description="Watch J19 GPIOs for HS IN edges")
    ap.add_argument(
        "--bcms",
        default=",".join(str(b) for b in DEFAULT_IN_BCMS),
        help="Comma BCM list (default 9,10,11,2)",
    )
    ap.add_argument(
        "--include-out",
        action="store_true",
        help="Also watch BCM3/8 (normally OUT drive)",
    )
    ap.add_argument(
        "--poll-ms",
        type=float,
        default=1.0,
        help="Poll period ms (default 1)",
    )
    args = ap.parse_args()

    bcms: list[int] = []
    for part in args.bcms.split(","):
        part = part.strip()
        if not part:
            continue
        n = int(part)
        if not args.include_out and n in OUT_BCMS:
            print(f"skip BCM{n} (OUT pair; pass --include-out to watch)", flush=True)
            continue
        bcms.append(n)
    if not bcms:
        print("FATAL: no BCMs to watch", file=sys.stderr)
        return 2

    print("hs_in_watch: input edge poll (Ctrl-C to stop)", flush=True)
    print(
        "  OUT proven: silk p3=BCM3 OIS, silk p5=BCM8 M-S — looking for IN pads",
        flush=True,
    )
    for bcm in bcms:
        try:
            set_input_pull_up(bcm)
        except MapperError as exc:
            print(f"FATAL: {exc}", file=sys.stderr)
            return 1
        lab = LABELS.get(bcm, "?")
        print(f"  watch BCM{bcm:<2d}  {lab}  ({pinctrl_get(bcm)})", flush=True)

    stop = {"flag": False}

    def _on_sig(_s, _f) -> None:
        stop["flag"] = True

    signal.signal(signal.SIGINT, _on_sig)
    signal.signal(signal.SIGTERM, _on_sig)

    last: dict[int, bool | None] = {b: _level_hi(b) for b in bcms}
    edges = 0
    t0 = time.monotonic()
    poll_s = max(0.0005, args.poll_ms / 1000.0)

    try:
        while not stop["flag"]:
            now = time.monotonic()
            for bcm in bcms:
                lvl = _level_hi(bcm)
                if lvl is None or lvl == last[bcm]:
                    continue
                prev = last[bcm]
                last[bcm] = lvl
                edges += 1
                edge = "rise" if lvl else "fall"
                print(
                    f"{now - t0:8.3f}s  BCM{bcm}  {edge}  "
                    f"{'hi' if prev else 'lo'}->{('hi' if lvl else 'lo')}  "
                    f"edges={edges}  {LABELS.get(bcm, '')}",
                    flush=True,
                )
            time.sleep(poll_s)
    finally:
        print(f"\nhs_in_watch: stop after {edges} edges — restoring inputs", flush=True)
        for bcm in bcms:
            try:
                set_input_pull_up(bcm)
            except MapperError:
                pass
    return 0


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