#!/usr/bin/env python3
"""
Interactive Twitch category search. Used by loading.sh to resolve a free-form
query into an exact directory entry.

  ./search-game.py "Heroes"
    stderr: numbered list of up to 20 matches + "Pick: " prompt
    stdout: a single line "<game_id>\\t<canonical_name>"  (only on success)
    exit:   0 success | 1 no-match / cancel / error | 130 ctrl-c

Auto-picks when there's exactly one result. Tempting to also auto-pick on an
exact-name match within a larger result set (e.g. typing "Doom Eternal" and
having that exact entry near the top), but a real test surfaced the failure:
typing "Heroes" returns "Heroes" (TV show) plus all the Heroes-* games, and
auto-picking the standalone match silently steals from the user — who is
*using search* precisely because the query is ambiguous. So: only one rule.

Endpoint: /helix/search/categories — same one Twitch's dashboard autocomplete
uses. Accepts any user/app token; we reuse the broadcaster token from
.env.ophi118 because it's already there and refreshable.
"""

import sys
import urllib.parse

import _twitch as tw

MAX_RESULTS = 20


def err(msg=""):
    print(msg, file=sys.stderr, flush=True)


def search(env: dict, query: str) -> list:
    qs = urllib.parse.urlencode({"query": query, "first": MAX_RESULTS})
    status, body = tw.auth_call("GET", f"{tw.HELIX}/search/categories?{qs}", env)
    if status != 200:
        raise RuntimeError(f"search failed (status={status}): {body}")
    return (body or {}).get("data") or []


def pick(results: list, query: str) -> dict:
    if not results:
        raise RuntimeError(f"no Twitch categories match {query!r}")

    if len(results) == 1:
        err(f"  ✓ only match: {results[0]['name']}")
        return results[0]

    err(f"── {len(results)} matches for {query!r} ──")
    for i, g in enumerate(results, 1):
        err(f"  {i:2}) {g['name']}")
    err("   0) cancel")

    while True:
        # input()'s prompt arg writes to stdout — fatal here because bash
        # captures stdout for the resolved <id>\t<name> line. Print to stderr
        # explicitly, then call input() with no prompt.
        print("Pick: ", end="", file=sys.stderr, flush=True)
        try:
            raw = input().strip()
        except EOFError:
            raise RuntimeError("cancelled (EOF)")
        if not raw.isdigit():
            err("  ! enter a number")
            continue
        n = int(raw)
        if n == 0:
            raise RuntimeError("cancelled")
        if 1 <= n <= len(results):
            return results[n - 1]
        err(f"  ! must be 0..{len(results)}")


def main():
    if len(sys.argv) != 2 or not sys.argv[1].strip():
        print("usage: search-game.py <query>", file=sys.stderr)
        sys.exit(2)
    query = sys.argv[1].strip()

    env = tw.load_env()
    needed = ("TWITCH_ACCESS_TOKEN", "TWITCH_REFRESH_TOKEN", "TWITCH_CLIENT_ID")
    missing = [k for k in needed if not env.get(k)]
    if missing:
        err(f"[err] missing in {tw.ENV_FILE.name}: {', '.join(missing)}")
        sys.exit(1)

    try:
        results = search(env, query)
        chosen  = pick(results, query)
    except Exception as e:
        err(f"[err] {type(e).__name__}: {e}")
        sys.exit(1)

    # ONLY the resolved tuple goes to stdout — bash captures this.
    print(f"{chosen['id']}\t{chosen['name']}")


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        sys.exit(130)
