"""The Garage: real vehicles for a custom 3D game (OhMyGames GARAGE, operator 30 Sept 2026 11:52 CET: "vehicle kit and
reskins should be connected to real OMG builds").

A custom 3D game (open world or walk-around room lane; no board, not a side view) whose words name vehicles gets the Garage kit (JavaScript ES
modules with TypeScript types, built by FGE `tools/garage/pack-omg.sh`) plus its CC0 vehicle models as attachments, and
the builder contract joins the brief. Cars, vans, trucks, taxis, police cars and tractors are `createVehicle` models
(25 Kenney CC0 vehicles repainted by one shader), re-skinned for the game's world with a theme (city, desert, snow,
wasteland, neon, candy, army or a small JSON) at no cost; karts are `createKart` (the FGE kart genre's procedural karts,
winners of the 30 Sept blind bake-off, with an optional cartoon animal driver). Before this, every vehicle in these
games was drawn from boxes and cylinders by the builder. The Engine stages attachments at
`.forge/attachments/att<N>/original.<ext>` in order, like games_room and games_cast. A missing bundle = no Garage, the
build goes on as before.
"""

from __future__ import annotations

import re
from typing import Any

from forge_corp.api.routes.games_room import ASSETS, _data_url

# the card's own words (and the player's prompt); a race alone is not a vehicle (a foot race, a snowboard race, horses)
_VEHICLE_RE = re.compile(
    r"\b(cars?|go-?karts?|karts?|karting|trucks?|pick-?ups?|vans?|taxis?|cabs|jeeps?|suvs?|sedans?|tractors?|ambulances?|"
    r"fire ?trucks?|fire ?engines?|police cars?|cop cars?|squad cars?|patrol cars?|getaway|car chases?|traffic|vehicles?|"
    r"automobiles?|driving|drivers?|drive-?by|race ?cars?|racecars?|rally|rallies|drift\w*|street rac\w*|road ?trips?|highways?|"
    r"freeways?|motorways?|parking|garages?|derby|demolition|monster trucks?|dune buggy|buggies|buggy|hot ?rods?|muscle cars?|"
    r"limo\w*|convoys?|delivery trucks?|delivery vans?|nascar|formula ?(?:1|one)|grand prix)\b", re.I)
_KART_RE = re.compile(r"\b(go-?karts?|karts?|karting|mario ?kart\w*)\b", re.I)
# in a ball or racket sport these words are shots and exchanges, not vehicles
_BALL_SPORT_RE = re.compile(r"\b(tennis|padel|squash|badminton|pickleball|ping[- ]?pong|volleyball|golf\w*|rackets?|racquets?|"
                            r"forehands?|backhands?|shuttlecocks?|cricket|baseball)\b", re.I)
_SPORT_WORDS_RE = re.compile(r"\b(rally|rallies|drives?|driv\w*|drift\w*)\b", re.I)


def _card_text(card: Any, prompt: str = "") -> str:
    world = getattr(card, "world", None)
    parts = [getattr(card, "title", ""), getattr(card, "pitch", ""), getattr(card, "genre", ""), getattr(card, "core_loop", ""),
             getattr(card, "first_version", ""), " ".join(getattr(card, "mood", None) or []),
             getattr(world, "setting", None) or "", getattr(world, "look", None) or "", prompt or ""]
    return " ".join(str(p) for p in parts if p)


def garage_kind(card: Any, prompt: str = "") -> str | None:
    """"karts" (a kart game), "vehicles" (any other game with vehicles) or None, deterministic from the words."""
    text = _card_text(card, prompt)
    if _BALL_SPORT_RE.search(text):  # a tennis rally, a golf driver or a "drive" down the line is not a car (Ace Machine, 4 Oct 2026)
        text = _SPORT_WORDS_RE.sub(" ", text)
    if not _VEHICLE_RE.search(text):
        return None
    return "karts" if _KART_RE.search(text) else "vehicles"


def garage_fits(card: Any) -> bool:
    world = getattr(card, "world", None)
    # Vehicle Model v1 (30 Sept 2026): room-lane games (shooters, heists, open towns) get it too, so a parked jeep or the
    # traffic in a walk-around game comes from the kit. Board games and side views keep their own kits.
    return (getattr(card, "lane", None) == "custom" and getattr(card, "stage", None) in ("none", "room") and world is not None
            and getattr(world, "view", None) in ("first", "third", "top"))


def garage_sha() -> str | None:
    """sha256 of the kit tarball (the one `garage.sha` names), for meta; None when the kit is not vendored."""
    try:
        return (ASSETS / "garage.sha").read_text().strip() or None
    except OSError:
        pass
    try:
        import hashlib

        return hashlib.sha256((ASSETS / "garage.tar.gz").read_bytes()).hexdigest()
    except OSError:
        return None


_IDS = ("sedan, sedan-sports, hatchback-sports, suv, suv-luxury, taxi, police, race, race-future, van, ambulance, delivery, "
        "delivery-flat, truck, truck-flat, firetruck, garbage-truck, tractor, tractor-police, tractor-shovel")


def garage_contract(kit_id: str, files_id: str, kind: str) -> str:
    """The builder contract for the Garage. kit_id / files_id are attachment ids (att2...)."""
    karts = ("Every kart (the player and each rival) is `createKart({ style, paint, driver })` from `@/lib/garage/kart.mjs` "
             "(8 styles: buggy, bruiser, sleek, racer, log, hotrod, sled, egg; a cartoon animal driver or `driver: false` "
             "for your own character); give each racer its own style and colours. ") if kind == "karts" else ""
    return (
        " VEHICLES COME FROM OUR GARAGE KIT. Attachment " + kit_id + " is the Garage kit (JavaScript ES modules with TypeScript "
        "types, three.js, a gzipped tar) and attachment " + files_id + " holds its CC0 vehicle models (a gzipped tar). First run: "
        "`mkdir -p src/lib/garage && tar -xzf .forge/attachments/" + kit_id + "/original.* -C src/lib/garage && "
        "tar -xzf .forge/attachments/" + files_id + "/original.* -C public`, then read `src/lib/garage/GARAGE.md` and `src/lib/garage/VEHICLE-LAWS.md` (laws V1-V9). " + karts +
        "Every car, van, truck, taxi, police car, ambulance, fire truck and tractor is `createVehicle({ id, size, theme })` from "
        "`@/lib/garage/index.mjs` (ids: " + _IDS + "), preloaded with `preloadVehicles(ids)` before play, and re-skinned for "
        "this game's world with a theme: a preset (`city`, `desert`, `snow`, `wasteland`, `neon`, `candy`, `army`) or a small "
        "theme JSON (paint, finish, pattern, weather, glow, addons) written from the palette. Import the kit only from the "
        "client code that builds the three.js scene. Each frame move and turn `vehicle.group` from your physics (vehicles "
        "face +Z), call `setSteer(angle)` and `spin(metres travelled)`; mark the player's vehicle `vehicle.player = true`. "
        "Parked cars, traffic and rivals are kit vehicles too. Use `vehicle.dims` for collision boxes. Never build a "
        "vehicle body or wheels from boxes and cylinders, never stretch or rescale a vehicle, and do not edit src/lib/garage "
        "or public/garage."
    )


# Ground look (operator 3 Oct 2026 10:50 CET: "wire the kart look into OMG builds"): the kit ships `look/` (FGE main 611dae27).
# Only for games where vehicles are driven (not the walk-around room lane, where cars are mostly parked props).
def look_contract() -> str:
    return (
        " GROUND LOOK: vehicles leave tire tracks and kick up dust the colour of the ground. Use `createVehicleGroundLook` "
        "from `@/lib/garage/look/index.mjs` (GARAGE.md, \"Ground look\"): pass `THREE`, your renderer, the ground material(s) "
        "the vehicles drive on and `surfaceAt(x, z)` naming the ground there ('road', 'dirt', 'sand', 'mud', 'snow', 'grass' "
        "or 'ice'), and always `heightAt(x, z)` returning the ground height there (the same height your vehicles sit on; "
        "`() => 0` only when the ground is flat at y = 0). heightAt is required: without it the wheels never know they are in "
        "the air and tracks run on under every jump; with it a hop leaves a broken mark and a landing digs a smear. `track()` "
        "every vehicle and kart; call `update(dt, playerPosition)` every frame before rendering. Wheels "
        "then leave ruts in soft ground and rubber marks on the road when sliding or braking hard. Colour your wheel dust "
        "with `dustColor(x, z, color)` and size it with `dustAmount(vehicle)`; keep dust low, at wheel height, and thin "
        "enough that the vehicles stay visible. Hills and mountains made from a heightfield are carved with `erodeTerrain` "
        "from the same module, with weight 0 on the track and everywhere the player drives. Use `quality: 'low'` on phones. "
        "This is look only: it never changes speed, grip or physics."
    )


# Joins the plan's Art Direction line when the Garage is attached (see games.py _game_brief).
GARAGE_ART_LINE = (" The Art Direction names every vehicle: its Garage id or kart style, its size class and its theme (a preset or "
                   "a theme JSON from the palette), matched to the world.")


def garage_attachments(card: Any, first_index: int, prompt: str = "") -> tuple[list[dict[str, Any]], str, str] | None:
    """(attachments, contract, kind) or None. first_index = attachments already in the brief (the Engine numbers att1,
    att2... in list order). None when the game has no vehicles or a bundle is missing: the Garage is a bonus, never a
    reason to fail a build."""
    if not garage_fits(card):
        return None
    kind = garage_kind(card, prompt)
    if kind is None:
        return None
    try:
        kit = (ASSETS / "garage.tar.gz").read_bytes()
        files = (ASSETS / "garage-models.tar.gz").read_bytes()
    except OSError:
        return None
    kit_id, files_id = f"att{first_index + 1}", f"att{first_index + 2}"
    atts = [
        {"url": _data_url("application/gzip", kit), "filename": "garage.tar.gz", "alt": "Garage vehicle kit (JavaScript, three.js)",
         "role": "code to extract into src/lib/garage and use as is"},
        {"url": _data_url("application/gzip", files), "filename": "garage-models.tar.gz", "alt": "Garage vehicle models (CC0 GLB)",
         "role": "vehicle models to extract into public (public/garage)"},
    ]
    contract = garage_contract(kit_id, files_id, kind)
    if getattr(card, "stage", None) != "room":
        contract += look_contract()
    return atts, contract, kind
