#!/usr/bin/env python3
"""
从 Unity 全量导出关卡到单一 JSON 数据库（方便后期增删查改）。

数据来源:
  - Assets/Scripts/Core/Levels*.cs  → spawns / boundary / levelPath
  - Assets/Prefabs/Level/LevelN.prefab → Ground / Border Tilemap

用法:
  python3 tools/export_all_levels.py \\
    --unity-root "/path/to/主站" \\
    --output assets/level-data/levels-database.json
"""
from __future__ import annotations

import argparse
import json
import re
import sys
from datetime import datetime, timezone
from pathlib import Path

from export_unity_levels import (
    LEVEL_RE,
    BOUND_RE,
    SPAWN_RE,
    POS_RE,
    PATH_RE,
    PDIR_RE,
    VDIR_RE,
    kind_from_path,
    parse_spawns,
    infer_theme_from_level,
    build_border_cache,
)
from export_unity_prefab_maps import parse_level_prefab

from level_id import LEVEL_ID_BASE, normalize_level_id, prefab_resource_path


LEVEL_PATH_RE = re.compile(
    r'levelPath\s*=\s*"?([^",\s]+Level\d+\.prefab)"?', re.I
)


def parse_level_block(chunk: str, lid: int) -> dict:
    bound = BOUND_RE.search(chunk)
    bx, by = (10, 10)
    if bound:
        bx, by = int(bound.group(1)), int(bound.group(2))
    path_m = LEVEL_PATH_RE.search(chunk)
    level_path = path_m.group(1) if path_m else f"Assets/Prefabs/Level/Level{lid}.prefab"
    ext_id = normalize_level_id(lid)
    spawns, spawn_paths = parse_spawns(chunk)
    return {
        "levelID": ext_id,
        "boundary": {"x": bx, "y": by},
        "spawns": spawns,
        "theme": infer_theme_from_level(ext_id, spawn_paths),
        "unityPrefab": level_path.replace("\\", "/"),
        "cocosPrefab": prefab_resource_path(ext_id),
    }


def parse_levels_cs_file(path: Path) -> dict[int, dict]:
    text = path.read_text(encoding="utf-8")
    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)
        levels[lid] = parse_level_block(text[start:end], lid)
    return levels


def parse_all_levels_cs(core_dir: Path) -> dict[int, dict]:
    merged: dict[int, dict] = {}
    for cs in sorted(core_dir.glob("Levels*.cs")):
        part = parse_levels_cs_file(cs)
        for lid, L in part.items():
            if lid in merged:
                print(f"warn: duplicate level id {lid} in {cs.name}, keep first", file=sys.stderr)
                continue
            merged[lid] = L
    return merged


def prefab_path_for_level(L: dict, prefab_dir: Path) -> Path | None:
    rel = L.get("unityPrefab", "")
    if rel:
        name = Path(rel).name
        p = prefab_dir / name
        if p.is_file():
            return p
    p = prefab_dir / f"Level{L['levelID']}.prefab"
    return p if p.is_file() else None


def merge_prefab_maps(L: dict, prefab_dir: Path) -> None:
    p = prefab_path_for_level(L, prefab_dir)
    if not p:
        bx, by = L["boundary"]["x"], L["boundary"]["y"]
        ring = build_border_cache({L["levelID"]: {**L, "borderKey": f"{bx},{by}"}})
        L["border"] = ring[f"{bx},{by}"]
        L["_mapSource"] = "boundary_ring"
        return
    maps = parse_level_prefab(p)
    if maps.get("ground"):
        L["ground"] = maps["ground"]
    if maps.get("border"):
        L["border"] = maps["border"]
    L["_mapSource"] = "unity_prefab"


def strip_internal(L: dict) -> dict:
    out = {
        "levelID": L["levelID"],
        "boundary": L["boundary"],
        "spawns": L["spawns"],
    }
    if L.get("unityPrefab"):
        out["unityPrefab"] = L["unityPrefab"]
    if L.get("cocosPrefab"):
        out["cocosPrefab"] = L["cocosPrefab"]
    if L.get("ground"):
        out["ground"] = L["ground"]
    if L.get("border"):
        out["border"] = L["border"]
    if L.get("theme"):
        out["theme"] = L["theme"]
    return out


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--unity-root", required=True, help="Unity 项目根目录（含 Assets）")
    ap.add_argument("--output", required=True, help="输出 levels-database.json")
    ap.add_argument("--merge-into", default="", help="合并到已有 JSON（保留范围外关卡）")
    ap.add_argument("--min-level-id", type=int, default=0, help="仅导出 levelID >= 此值")
    ap.add_argument("--max-level-id", type=int, default=0, help="仅导出 levelID <= 此值")
    ap.add_argument("--limit", type=int, default=0, help="仅导出前 N 个关卡（调试用）")
    ap.add_argument("--skip-prefab-maps", action="store_true", help="不解析 Unity prefab 瓦片（快，地图由 Cocos 预制体承担）")
    args = ap.parse_args()

    unity = Path(args.unity_root)
    core = unity / "Assets/Scripts/Core"
    prefab_dir = unity / "Assets/Prefabs/Level"
    if not core.is_dir():
        print(f"Core dir not found: {core}", file=sys.stderr)
        sys.exit(1)

    print("Parsing Levels*.cs …")
    levels = parse_all_levels_cs(core)
    ids = sorted(levels.keys())
    if args.min_level_id > 0:
        ids = [i for i in ids if i >= args.min_level_id]
    if args.max_level_id > 0:
        ids = [i for i in ids if i <= args.max_level_id]
    if args.limit > 0:
        ids = ids[: args.limit]
    levels = {k: levels[k] for k in ids}
    print(f"Level definitions: {len(levels)} ({ids[0] if ids else '?'}-{ids[-1] if ids else '?'})")

    from_prefab = 0
    from_ring = 0
    if args.skip_prefab_maps:
        print("Skipping prefab Tilemaps (--skip-prefab-maps)")
    else:
        print("Merging prefab Tilemaps …")
        for i, lid in enumerate(ids):
            if (i + 1) % 200 == 0:
                print(f"  … {i + 1}/{len(ids)}")
            merge_prefab_maps(levels[lid], prefab_dir)
            if levels[lid].get("_mapSource") == "unity_prefab":
                from_prefab += 1
            else:
                from_ring += 1

    exported = {str(levels[lid]["levelID"]): strip_internal(levels[lid]) for lid in ids}

    if args.merge_into:
        merge_path = Path(args.merge_into)
        if merge_path.is_file():
            print(f"Merging into {merge_path} …")
            base = json.loads(merge_path.read_text(encoding="utf-8"))
            base_levels = base.get("levels", {})
            base_levels.update(exported)
            payload = {
                **base,
                "version": 2,
                "generatedAt": datetime.now(timezone.utc).isoformat(),
                "source": "Unity Levels*.cs + Assets/Prefabs/Level/*.prefab (merged)",
                "stats": {
                    "total": len(base_levels),
                    "withPrefabTilemap": from_prefab,
                    "withBoundaryRing": from_ring,
                    "exportedRange": len(exported),
                },
                "levelIdBase": LEVEL_ID_BASE,
                "levels": base_levels,
            }
        else:
            print(f"warn: merge target missing, writing export only: {merge_path}", file=sys.stderr)
            payload = {
                "version": 2,
                "generatedAt": datetime.now(timezone.utc).isoformat(),
                "source": "Unity Levels*.cs + Assets/Prefabs/Level/*.prefab",
                "stats": {
                    "total": len(exported),
                    "withPrefabTilemap": from_prefab,
                    "withBoundaryRing": from_ring,
                },
                "levelIdBase": LEVEL_ID_BASE,
                "levels": exported,
            }
    else:
        payload = {
            "version": 2,
            "generatedAt": datetime.now(timezone.utc).isoformat(),
            "source": "Unity Levels*.cs + Assets/Prefabs/Level/*.prefab",
            "stats": {
                "total": len(exported),
                "withPrefabTilemap": from_prefab,
                "withBoundaryRing": from_ring,
            },
            "levelIdBase": LEVEL_ID_BASE,
            "levels": exported,
        }

    out = Path(args.output)
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"Wrote {out} ({out.stat().st_size // 1024} KB)")
    print(f"  prefab tilemap: {from_prefab}, fallback ring: {from_ring}")


if __name__ == "__main__":
    main()
