#!/usr/bin/env python3
"""
从 Unity Levels*.cs 导出 Cocos LevelRegistry 数据。
用法:
  python3 tools/export_unity_levels.py \
    --input "/path/to/Levels600.cs" \
    --output assets/scripts/level/levels-600.generated.ts \
    --border-cache assets/scripts/level/border-cache.json
"""
from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path

from export_unity_prefab_maps import parse_level_prefab

DIR = {
    "Direction.North": "Direction.North",
    "Direction.East": "Direction.East",
    "Direction.South": "Direction.South",
    "Direction.West": "Direction.West",
}

LEVEL_RE = re.compile(
    r"\{\s*(\d+)\s*,\s*new Level\(\)\s*\{\s*LevelID\s*=\s*\d+\s*,\s*spawns\s*=\s*new List<Spawn>\(\)\s*\{",
    re.MULTILINE,
)

SPAWN_RE = re.compile(
    r"new Spawn\(\)\{([^}]+(?:\{[^}]*\})?)\}",
    re.MULTILINE,
)

POS_RE = re.compile(r"position\s*=\s*new Vector3Int\((-?\d+),(-?\d+),-?\d+\)")
PATH_RE = re.compile(r'path\s*=\s*"?([^",\s]+)"?')
PDIR_RE = re.compile(r"playerDirection\s*=\s*(Direction\.\w+)")
VDIR_RE = re.compile(r"vehicleDirection\s*=\s*(Direction\.\w+)")
BOUND_RE = re.compile(r"boundary\s*=\s*new Vector3Int\((\d+),(\d+),0\)")


def kind_from_path(path: str) -> str:
    p = path.lower()
    if "player" in p and "multplay" not in p:
        return "player"
    if "vehicle" in p:
        return "vehicle"
    if "enemy" in p:
        return "enemy"
    if "prop" in p or "nrop" in p:
        return "prop"
    return "prop_decor"


def prop_placement_from_path(path: str) -> str | None:
    pl = path.lower()
    if "nprop" in pl or "nrop" in pl:
        return "ground"
    if "prop" in pl:
        return "block"
    return None


def infer_theme_from_level(level_id: int, spawn_paths: list[str]) -> str:
    """按主站关卡批次分配主题；道具路径可覆盖（snow/sanxing）。"""
    if 91601 <= level_id <= 91900:
        return "silu"
    if 91901 <= level_id <= 92200:
        return "redarmy"
    if 92201 <= level_id <= 92500:
        return "numMan"
    if 92501 <= level_id <= 92800:
        return "chinese"
    if 92801 <= level_id <= 93100:
        return "snow"
    if 93101 <= level_id <= 93700:
        return "sanxing"
    joined = " ".join(spawn_paths).lower()
    if "prop_sanxing" in joined or "nprop_sanxing" in joined:
        return "sanxing"
    if "prop_snow" in joined or "nprop_snow" in joined:
        return "snow"
    if "chinese" in joined or "panda" in joined:
        return "chinese"
    if "redarmy" in joined or "red_army" in joined:
        return "redarmy"
    if "numman" in joined:
        return "numMan"
    return "silu"


def parse_spawns(block: str) -> tuple[list[dict], list[str]]:
    spawns: list[dict] = []
    paths: list[str] = []
    for m in SPAWN_RE.finditer(block):
        body = m.group(1)
        pm = POS_RE.search(body)
        if not pm:
            continue
        path_m = PATH_RE.search(body)
        path = path_m.group(1) if path_m else ""
        if path:
            paths.append(path)
        item: dict = {
            "x": int(pm.group(1)),
            "y": int(pm.group(2)),
            "kind": kind_from_path(path),
        }
        placement = prop_placement_from_path(path)
        if placement and item["kind"] == "prop":
            item["propPlacement"] = placement
        pdir = PDIR_RE.search(body)
        vdir = VDIR_RE.search(body)
        if pdir:
            item["playerDirection"] = pdir.group(1)
        if vdir:
            item["vehicleDirection"] = vdir.group(1)
        spawns.append(item)
    return spawns, paths


def ring_border_key(bx: int, by: int) -> str:
    return f"{bx},{by}"


def parse_file(text: str) -> dict[int, dict]:
    levels: dict[int, dict] = {}
    matches = list(LEVEL_RE.finditer(text))
    for i, m in enumerate(matches):
        lid = int(m.group(1))
        start = m.end()
        end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
        chunk = text[start:end]
        bound = BOUND_RE.search(chunk)
        bx, by = (10, 10)
        if bound:
            bx, by = int(bound.group(1)), int(bound.group(2))
        spawns, spawn_paths = parse_spawns(chunk)
        levels[lid] = {
            "levelID": lid,
            "boundary": {"x": bx, "y": by},
            "borderKey": ring_border_key(bx, by),
            "spawns": spawns,
            "theme": infer_theme_from_level(lid, spawn_paths),
        }
    return levels


def spawn_to_ts(s: dict, indent: str) -> str:
    parts = [
        f"x: {s['x']}",
        f"y: {s['y']}",
        f"kind: '{s['kind']}'",
    ]
    if "playerDirection" in s:
        parts.append(f"playerDirection: {s['playerDirection']}")
    if "vehicleDirection" in s:
        parts.append(f"vehicleDirection: {s['vehicleDirection']}")
    if s.get("propPlacement"):
        parts.append(f"propPlacement: '{s['propPlacement']}'")
    return indent + "{ " + ", ".join(parts) + " }"


def emit_ts(levels: dict[int, dict], export_const: str = "LEVELS_600") -> str:
    lines = [
        "/* AUTO-GENERATED by tools/export_unity_levels.py — DO NOT EDIT */",
        "/* spawns 来自 Levels*.cs；ground/border 来自 Assets/Prefabs/Level/LevelN.prefab Tilemap */",
        "import { Direction } from '../core/Define';",
        "import { LevelConfig } from './LevelTypes';",
        "",
        f"export const {export_const}: Record<number, LevelConfig> = {{",
    ]
    for lid in sorted(levels.keys()):
        L = levels[lid]
        lines.append(f"    {lid}: {{")
        lines.append(f"        levelID: {lid},")
        lines.append(
            f"        boundary: {{ x: {L['boundary']['x']}, y: {L['boundary']['y']} }},"
        )
        if L.get("ground"):
            lines.append(
                "        ground: "
                + json.dumps(L["ground"], ensure_ascii=False, separators=(",", ": "))
                + ","
            )
        if L.get("border"):
            lines.append(
                "        border: "
                + json.dumps(L["border"], ensure_ascii=False, separators=(",", ": "))
                + ","
            )
        lines.append("        spawns: [")
        for s in L["spawns"]:
            lines.append(spawn_to_ts(s, "            ") + ",")
        lines.append("        ],")
        lines.append("    },")
    lines.append("};")
    lines.append("")
    return "\n".join(lines)


def build_border_cache(levels: dict[int, dict]) -> dict[str, dict]:
    cache: dict[str, dict] = {}
    for L in levels.values():
        key = L["borderKey"]
        if key in cache:
            continue
        bx, by = L["boundary"]["x"], L["boundary"]["y"]
        border: dict[str, bool] = {}
        for x in range(-bx, bx + 1):
            for y in range(-by, by + 1):
                if abs(x) == bx or abs(y) == by:
                    border[f"{x},{y}"] = True
        cache[key] = border
    return cache


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--input", required=True, help="Unity Levels*.cs path")
    ap.add_argument("--output", required=True, help="Output .ts path")
    ap.add_argument("--border-cache", default="", help="Optional border cache json")
    ap.add_argument("--limit", type=int, default=0, help="Max levels to export (0 = all)")
    ap.add_argument("--export-const", default="LEVELS_600", help="TS export const name")
    ap.add_argument(
        "--prefab-dir",
        default="",
        help="Unity Assets/Prefabs/Level 目录，合并 LevelN.prefab 的 Tilemap",
    )
    args = ap.parse_args()

    text = Path(args.input).read_text(encoding="utf-8")
    levels = parse_file(text)
    if args.limit > 0:
        sorted_ids = sorted(levels.keys())[: args.limit]
        levels = {k: levels[k] for k in sorted_ids}
    if not levels:
        print("No levels parsed", file=sys.stderr)
        sys.exit(1)

    prefab_dir = Path(args.prefab_dir) if args.prefab_dir else None
    merged_prefab = 0
    if prefab_dir:
        for lid, L in levels.items():
            maps = parse_level_prefab(prefab_dir / f"Level{lid}.prefab")
            if maps:
                L["ground"] = maps.get("ground", {})
                L["border"] = maps.get("border", {})
                merged_prefab += 1
            else:
                ring = build_border_cache({lid: L})[L["borderKey"]]
                L["border"] = ring
        print(f"Merged Tilemap from {merged_prefab} prefabs")
    else:
        border_cache = build_border_cache(levels)
        for L in levels.values():
            L["border"] = border_cache[L["borderKey"]]

    out = Path(args.output)
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(emit_ts(levels, args.export_const), encoding="utf-8")

    if args.border_cache:
        cache = {str(lid): L.get("border", {}) for lid, L in levels.items()}
        Path(args.border_cache).write_text(json.dumps(cache, indent=2), encoding="utf-8")

    print(f"Exported {len(levels)} levels -> {out}")


if __name__ == "__main__":
    main()
