#!/usr/bin/env python3
"""Add DriftwoodXI as a non-Steam game with grid art + Deck Gamepad template."""
from __future__ import annotations

import argparse
import binascii
import os
import re
import shutil
import struct
import sys
from pathlib import Path


def shortcut_appid(exe: str, name: str) -> int:
    crc = binascii.crc32((exe + name).encode("utf-8")) & 0xFFFFFFFF
    return (crc | 0x80000000) & 0xFFFFFFFF


def write_str(buf: bytearray, key: str, val: str) -> None:
    buf.append(0x01)
    buf.extend(key.encode("utf-8") + b"\x00")
    buf.extend(val.encode("utf-8") + b"\x00")


def write_int(buf: bytearray, key: str, val: int) -> None:
    buf.append(0x02)
    buf.extend(key.encode("utf-8") + b"\x00")
    buf.extend(struct.pack("<I", val & 0xFFFFFFFF))


def build_entry(index: int, appid: int, name: str, exe: str, start_dir: str, icon: str) -> bytes:
    b = bytearray()
    b.append(0x00)
    b.extend(str(index).encode("utf-8") + b"\x00")
    write_int(b, "appid", appid)
    write_str(b, "AppName", name)
    write_str(b, "Exe", exe)
    write_str(b, "StartDir", start_dir)
    write_str(b, "icon", icon)
    write_str(b, "ShortcutPath", "")
    write_str(b, "LaunchOptions", "")
    write_int(b, "IsHidden", 0)
    write_int(b, "AllowDesktopConfig", 1)
    write_int(b, "AllowOverlay", 1)
    write_int(b, "OpenVR", 0)
    write_int(b, "Devkit", 0)
    write_str(b, "DevkitGameID", "")
    write_int(b, "DevkitOverrideAppID", 0)
    write_int(b, "LastPlayTime", 0)
    write_str(b, "FlatpakAppID", "")
    b.append(0x00)
    b.extend(b"tags\x00")
    write_str(b, "0", "DriftwoodXI")
    b.append(0x08)
    b.append(0x08)
    return bytes(b)


def find_userdata() -> Path:
    steam = Path.home() / ".local/share/Steam/userdata"
    if not steam.is_dir():
        steam = Path.home() / ".steam/steam/userdata"
    if not steam.is_dir():
        raise SystemExit("Steam userdata not found")
    candidates = [p for p in steam.iterdir() if p.is_dir() and p.name.isdigit() and p.name != "0"]
    if not candidates:
        raise SystemExit("No Steam user id under userdata/")
    # Prefer the one with config/shortcuts.vdf or largest config
    candidates.sort(key=lambda p: (p / "config/shortcuts.vdf").exists(), reverse=True)
    return candidates[0]


def upsert_shortcut(shortcuts: Path, name: str, exe: str, start_dir: str, icon: str, appid: int) -> None:
    shortcuts.parent.mkdir(parents=True, exist_ok=True)
    if shortcuts.exists():
        shutil.copy2(shortcuts, shortcuts.with_suffix(".vdf.bak-driftwood"))
        data = shortcuts.read_bytes()
    else:
        data = b"\x00shortcuts\x00\x08\x08"

    # Already present?
    if exe.encode("utf-8") in data and name.encode("utf-8") in data:
        print(f"shortcut already present for {name}")
        return

    idxs = [int(x) for x in re.findall(rb"\x00(\d+)\x00\x02appid\x00", data)]
    next_idx = (max(idxs) + 1) if idxs else 0
    entry = build_entry(next_idx, appid, name, exe, start_dir, icon)
    if data.endswith(b"\x08\x08"):
        new = data[:-2] + entry + b"\x08\x08"
    elif data.endswith(b"\x08"):
        new = data[:-1] + entry + b"\x08\x08"
    else:
        new = data + entry + b"\x08\x08"
    shortcuts.write_bytes(new)
    print(f"wrote shortcuts.vdf entry index={next_idx} appid={appid}")


def install_art(grid: Path, appid: int, art_dir: Path) -> None:
    grid.mkdir(parents=True, exist_ok=True)
    mapping = {
        f"{appid}p.png": art_dir / "portrait.png",
        f"{appid}.jpg": art_dir / "capsule_616x353.jpg",
        f"{appid}_hero.jpg": art_dir / "hero.jpg",
        f"{appid}_logo.png": art_dir / "logo.png",
    }
    if not mapping[f"{appid}.jpg"].exists() and (art_dir / "wide.jpg").exists():
        mapping[f"{appid}.jpg"] = art_dir / "wide.jpg"
    for dest_name, src in mapping.items():
        if not src.exists():
            print(f"skip missing art: {src.name}")
            continue
        dest = grid / dest_name
        shutil.copy2(src, dest)
        print(f"art {dest.name} ({dest.stat().st_size} bytes)")


def patch_neptune_gamepad(appids: list[int]) -> None:
    cfg = (
        Path.home()
        / ".local/share/Steam/steamapps/common/Steam Controller Configs"
    )
    # userdata id folder under Steam Controller Configs
    if not cfg.is_dir():
        print("Steam Controller Configs missing; skip gamepad template")
        return
    user_dirs = [p for p in cfg.iterdir() if p.is_dir() and p.name.isdigit()]
    if not user_dirs:
        print("no controller config user dir; skip")
        return
    cfgset = user_dirs[0] / "config" / "configset_controller_neptune.vdf"
    cfgset.parent.mkdir(parents=True, exist_ok=True)
    if cfgset.exists():
        shutil.copy2(cfgset, cfgset.with_suffix(".vdf.bak-driftwood"))
        text = cfgset.read_text(errors="ignore")
    else:
        text = '"controller_config"\n{\n}\n'

    def upsert(appid: str, text: str) -> str:
        block = f'\t"{appid}"\n\t{{\n\t\t"template"\t\t"controller_neptune_gamepad_joystick.vdf"\n\t}}\n'
        pat = rf'\t"{appid}"\s*\n\s*\{{[^{{}}]*\}}\n'
        if re.search(pat, text):
            return re.sub(pat, block, text, count=1)
        idx = text.rfind("}")
        return text[:idx] + block + text[idx:]

    for a in appids:
        text = upsert(str(a), text)
    cfgset.write_text(text)
    print(f"neptune gamepad template -> {cfgset}")


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("--root", default=os.environ.get("DRIFTWOOD_ROOT", str(Path.home() / "ffxi")))
    ap.add_argument("--name", default="DriftwoodXI")
    ap.add_argument("--art", default="", help="Art directory (portrait/hero/logo/capsule)")
    args = ap.parse_args()

    root = Path(args.root).expanduser().resolve()
    play = root / "bin" / "play-steam.sh"
    if not play.is_file():
        raise SystemExit(f"missing {play}; run install.sh first")

    art_dir = Path(args.art).expanduser() if args.art else Path(__file__).resolve().parent / "art"
    if not art_dir.is_dir():
        art_dir = root / "art"

    exe = f'"{play}"'
    name = args.name
    appid = shortcut_appid(exe, name)
    print(f"deterministic appid={appid}")

    user = find_userdata()
    shortcuts = user / "config" / "shortcuts.vdf"
    grid = user / "config" / "grid"
    icon = str(art_dir / "portrait.png") if (art_dir / "portrait.png").exists() else ""

    upsert_shortcut(shortcuts, name, exe, str(root), icon, appid)
    install_art(grid, appid, art_dir)
    patch_neptune_gamepad([appid])

    (root / "steam-appid.txt").write_text(f"{appid}\n")
    print("Done. Fully restart Steam (or return to Game Mode) to see DriftwoodXI.")
    print("Do NOT force Proton on this shortcut — play-steam.sh already uses GE-Proton.")
    print("Controller: Gamepad template applied; confirm in game properties if needed.")


if __name__ == "__main__":
    main()
