#!/usr/bin/env python3
"""Decode Draw Poker 0x59 card bytes from packets.raw (calibration tool).

Wire encoding: see poker_card_wire_codec.py and POKER_CARD_WIRE.md.
"""

from __future__ import annotations

import argparse
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, Optional

from decode_x9_result import SOH, EOP, decode_progressive_block, parse_hex_line
from poker_card_wire_codec import (
    POKER_CARD_COUNT,
    POKER_CARD_DEAL_START,
    POKER_CARD_DRAW_START,
    build_wire_maps,
    wire_to_label,
)

POKER_RESULT_MSG = 0x59
POKER_WAGER_MSG = 0x50
POKER_GRANT_MSG = 0x5A
POKER_CLAIM_MSG = 0x52
DRAW_POKER_GAME_IDS = frozenset({"9400", "0394", "9403"})

WIRE_TO_CARD, CARD_TO_WIRE = build_wire_maps()


@dataclass(frozen=True)
class PokerCardWire:
    offset: int
    wire_byte: int
    phase: str
    rank: Optional[str]
    suit: Optional[str]

    @property
    def label(self) -> str:
        if self.rank and self.suit:
            return f"{self.rank}{self.suit}"
        return wire_to_label(self.wire_byte)


def game_id_hex(raw: bytes) -> Optional[str]:
    if len(raw) >= 7:
        return f"{raw[6]:02X}{raw[5]:02X}"
    return None


def extract_card_bytes(raw: bytes, *, include_draw: bool = True) -> list[PokerCardWire]:
    if len(raw) < 2 or raw[0] != SOH or raw[-1] != EOP:
        return []
    if raw[1] not in (POKER_RESULT_MSG, POKER_GRANT_MSG):
        return []

    out: list[PokerCardWire] = []
    regions = [(POKER_CARD_DEAL_START, "deal")]
    if include_draw:
        regions.append((POKER_CARD_DRAW_START, "draw"))

    for start, phase in regions:
        for i in range(POKER_CARD_COUNT):
            off = start + i
            if off >= len(raw) - 1:
                break
            b = raw[off]
            rank, suit = WIRE_TO_CARD.get(b, (None, None))
            out.append(
                PokerCardWire(
                    offset=off,
                    wire_byte=b,
                    phase=phase,
                    rank=rank,
                    suit=suit,
                )
            )
    return out


def format_frame(raw: bytes) -> str:
    msg = raw[1]
    game = game_id_hex(raw) or "?"
    prog = decode_progressive_block(raw)
    cards = extract_card_bytes(raw)
    deal = [c.label for c in cards if c.phase == "deal"]
    draw = [c.label for c in cards if c.phase == "draw"]
    card_str = f"deal=[{' '.join(deal)}] draw=[{' '.join(draw)}]"
    prog_str = ""
    if prog and prog.is_jackpot_hit:
        prog_str = f" progwin=${prog.progwin_dollars:.2f}"
    return f"0x{msg:02X} game={game} len={len(raw)} {card_str}{prog_str}"


def iter_packets_raw(path: Path) -> Iterable[bytes]:
    with path.open(encoding="utf-8", errors="replace") as f:
        for line in f:
            raw = parse_hex_line(line)
            if raw:
                yield raw


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="Decode Draw Poker card wire bytes")
    parser.add_argument("path", nargs="?", help="packets.raw or data/ directory")
    parser.add_argument("--line", help="Single hex line to decode")
    parser.add_argument("--all", action="store_true", help="Include non-poker opcodes")
    args = parser.parse_args(argv)

    if args.line:
        raw = parse_hex_line(args.line)
        if not raw:
            print("invalid line", file=sys.stderr)
            return 1
        print(format_frame(raw))
        for c in extract_card_bytes(raw):
            print(
                f"  offset {c.offset} ({c.phase}): 0x{c.wire_byte:02X} -> {c.label}"
            )
        return 0

    if not args.path:
        parser.print_help()
        return 1

    path = Path(args.path)
    if path.is_dir():
        path = path / "packets.raw"
    if not path.is_file():
        print(f"missing {path}", file=sys.stderr)
        return 1

    poker_ops = {POKER_RESULT_MSG, POKER_WAGER_MSG, POKER_GRANT_MSG, POKER_CLAIM_MSG}
    count = 0
    for raw in iter_packets_raw(path):
        msg = raw[1] if len(raw) > 1 else 0
        game = game_id_hex(raw)
        if not args.all:
            if msg not in poker_ops and game not in DRAW_POKER_GAME_IDS:
                continue
        print(format_frame(raw))
        count += 1
    if count == 0:
        print("No poker frames found — play Jacks or Better with skill-research profile first.")
    return 0


if __name__ == "__main__":
    sys.exit(main())
