#!/usr/bin/env python3
"""
Build map/mode info JSON for tank missions with planes + capture zones.

Output shape:
{
  "<map_name>": {
    "<mode>": [
      {
        "info": { ... }
      }
    ]
  }
}
"""

from __future__ import annotations

import argparse
import json
from pathlib import Path
from typing import Any


SCRIPT_DIR = Path(__file__).resolve().parent
BOTS_DIR = SCRIPT_DIR.parent.parent

DATAMINE_LEVELS_DIR = BOTS_DIR / "War-Thunder-Datamine" / "aces.vromfs.bin_u" / "levels"
DATAMINE_MISSIONS_DIR = BOTS_DIR / "War-Thunder-Datamine" / "mis.vromfs.bin_u"
MISSION_ROOT = DATAMINE_MISSIONS_DIR / "gamedata" / "missions" / "cta" / "tanks"

SHARED_LEVELS_DIR = BOTS_DIR / "SHARED" / "MAPS" / "LEVELS"
SHARED_MISSIONS_DIR = SHARED_LEVELS_DIR / "MISSIONS"

SKIP_PARTS = {"mainareas", "battleareas", "spawnareas"}


def _clean_map_key(raw: str) -> str:
    key = Path(raw).name.strip().rstrip("*")
    if key.endswith(".png"):
        key = key[:-4]
    return key


def _normalize_rel(path: str) -> str:
    rel = str(path or "").strip().replace("\\", "/").lstrip("/")
    if rel[:9].lower() == "gamedata/":
        rel = "gamedata/" + rel[9:]
    return rel.lower()


def _load_json(path: Path, cache: dict[Path, Any]) -> Any:
    if path in cache:
        return cache[path]
    try:
        cache[path] = json.loads(path.read_text())
    except Exception:
        cache[path] = None
    return cache[path]


def _repo_rel(path: Path) -> str:
    try:
        return str(path.resolve().relative_to(BOTS_DIR.resolve())).replace("\\", "/")
    except Exception:
        return str(path)


def _datamine_level_path(level_path: str) -> str:
    rel = str(level_path or "").replace("\\", "/").lstrip("/")
    if rel.lower().startswith("levels/"):
        return f"War-Thunder-Datamine/aces.vromfs.bin_u/{rel}"
    return f"War-Thunder-Datamine/{rel}" if rel else rel


def _resolve_mission_file(rel: str) -> Path | None:
    norm = _normalize_rel(rel)
    if not norm:
        return None
    for root in (SHARED_MISSIONS_DIR, DATAMINE_MISSIONS_DIR):
        raw = root / norm
        if raw.suffix.lower() == ".blk":
            cands = [raw.with_suffix(".blkx"), raw]
        elif raw.suffix.lower() == ".blkx":
            cands = [raw, raw.with_suffix(".blk")]
        else:
            cands = [raw, raw.with_suffix(".blkx"), raw.with_suffix(".blk")]
        for p in cands:
            if p.exists():
                return p
    return None


def _import_paths(mission_def: dict) -> list[str]:
    imports = mission_def.get("imports")
    if not isinstance(imports, dict):
        return []
    import_record = imports.get("import_record")
    records = import_record if isinstance(import_record, list) else [import_record]
    out: list[str] = []
    for rec in records:
        if not isinstance(rec, dict):
            continue
        f = rec.get("file")
        if isinstance(f, str) and f.strip():
            out.append(f)
    return out


def _collect_defs(root_file: Path, cache: dict[Path, Any]) -> tuple[dict[str, dict], list[str], list[str]]:
    root_def = _load_json(root_file, cache)
    if not isinstance(root_def, dict):
        return {}, [], []

    areas: dict[str, dict] = {}
    files_used: list[str] = []
    imports_used: list[str] = []
    queue: list[Path] = [root_file]
    seen: set[Path] = set()

    while queue:
        cur_file = queue.pop(0).resolve()
        if cur_file in seen:
            continue
        seen.add(cur_file)
        files_used.append(str(cur_file))

        cur_def = _load_json(cur_file, cache)
        if not isinstance(cur_def, dict):
            continue

        cur_areas = cur_def.get("areas")
        if isinstance(cur_areas, dict):
            for name, area in cur_areas.items():
                if isinstance(name, str) and isinstance(area, dict) and name not in areas:
                    areas[name] = area

        for rel in _import_paths(cur_def):
            imports_used.append(_normalize_rel(rel))
            p = _resolve_mission_file(rel)
            if p is not None:
                queue.append(p)

    return areas, files_used, sorted(set(imports_used))


def _radius_from_tm(tm: list) -> float:
    try:
        ax = float(tm[0][0])
        az = float(tm[0][2])
        bx = float(tm[2][0])
        bz = float(tm[2][2])
        ra = (ax * ax + az * az) ** 0.5
        rb = (bx * bx + bz * bz) ** 0.5
        return max(ra, rb)
    except Exception:
        return 0.0


def _capture_prefixes(mission_type: str, mode_key: str) -> list[str]:
    m = (mission_type or "").lower()
    k = (mode_key or "").lower()
    if "conq" in m or "conq" in k:
        return ["conq_capture_area_"]
    if "dom" in m or "dom" in k:
        return ["dom_capture_area_"]
    if "bttl" in m or "bttl" in k:
        return ["bttl_t1_capture_area_", "bttl_t2_capture_area_"]
    return ["capture_area_"]


def _capture_sort(name: str) -> tuple[int, str]:
    digits = "".join(ch for ch in name if ch.isdigit())
    return (int(digits) if digits else 999, name)


def _pick_capture_areas(areas: dict[str, dict], mission_type: str, mode_key: str) -> list[dict]:
    prefixes = _capture_prefixes(mission_type, mode_key)
    names: list[str] = []
    for name, area in areas.items():
        if not isinstance(area, dict):
            continue
        lname = name.lower()
        if "capture_area" not in lname or lname.startswith("briefing_"):
            continue
        if not any(lname.startswith(p) for p in prefixes):
            continue
        names.append(name)

    if not names:
        return []

    suffix_prio = ["_arcade", "_realistic", "_hardcore", "_simulator", ""]
    groups: dict[str, dict[str, str]] = {}
    for name in names:
        lname = name.lower()
        suffix = ""
        base = lname
        for sfx in suffix_prio[:-1]:
            if lname.endswith(sfx):
                suffix = sfx
                base = lname[:-len(sfx)]
                break
        groups.setdefault(base, {})[suffix] = name

    chosen: list[str] = []
    for base in sorted(groups.keys(), key=_capture_sort):
        variants = groups[base]
        for sfx in suffix_prio:
            if sfx in variants:
                chosen.append(variants[sfx])
                break

    out: list[dict] = []
    for name in chosen:
        area = areas.get(name)
        if not isinstance(area, dict):
            continue
        tm = area.get("tm")
        if not isinstance(tm, list) or len(tm) < 4 or not isinstance(tm[3], list):
            continue
        try:
            cx = float(tm[3][0])
            cz = float(tm[3][2])
        except Exception:
            continue
        out.append({
            "name": name,
            "type": str(area.get("type", "")),
            "x": cx,
            "z": cz,
            "radius": _radius_from_tm(tm),
        })
    return out


def _battle_areas(areas: dict[str, dict]) -> list[dict]:
    out: list[dict] = []
    for name, area in areas.items():
        if not isinstance(area, dict):
            continue
        lname = name.lower()
        if "battle_area" not in lname or lname.startswith("briefing_"):
            continue
        tm = area.get("tm")
        if not isinstance(tm, list) or len(tm) < 4 or not isinstance(tm[3], list):
            continue
        try:
            cx = float(tm[3][0])
            cz = float(tm[3][2])
        except Exception:
            continue
        out.append({
            "name": name,
            "type": str(area.get("type", "")),
            "x": cx,
            "z": cz,
            "radius": _radius_from_tm(tm),
        })
    out.sort(key=lambda x: x["name"])
    return out


def _level_info(level_stem: str, cache: dict[Path, Any]) -> dict:
    cands = [
        DATAMINE_LEVELS_DIR / f"{level_stem}.blkx",
        SHARED_LEVELS_DIR / f"{level_stem}.blkx",
        SHARED_LEVELS_DIR / "DATAMINE" / f"{level_stem}.blkx",
    ]
    for p in cands:
        if not p.exists():
            continue
        data = _load_json(p, cache)
        if not isinstance(data, dict):
            continue
        return {
            "file": _repo_rel(p),
            "tankMapCoord0": data.get("tankMapCoord0"),
            "tankMapCoord1": data.get("tankMapCoord1"),
            "aiTanksMapCoord0": data.get("aiTanksMapCoord0"),
            "aiTanksMapCoord1": data.get("aiTanksMapCoord1"),
            "customLevelTankMap": data.get("customLevelTankMap"),
            "customLevelMap": data.get("customLevelMap"),
        }
    return {}


def _has_planes(allowed: dict | None, imports_used: list[str]) -> bool:
    if isinstance(allowed, dict):
        val = allowed.get("isAirplanesAllowed")
        if isinstance(val, bool):
            return val
    return any("air_spawns" in p for p in imports_used)


def _has_tanks(allowed: dict | None) -> bool:
    if isinstance(allowed, dict):
        val = allowed.get("isTanksAllowed")
        if isinstance(val, bool):
            return val
    return True


def _mode_key(mission_cfg: dict, mission_file: Path) -> str:
    postfix = mission_cfg.get("postfix")
    if isinstance(postfix, str) and postfix.strip():
        return postfix.lstrip("_")
    mtype = mission_cfg.get("type")
    if isinstance(mtype, str) and mtype.strip():
        return mtype
    return mission_file.stem


def should_skip_mission_file(path: Path) -> bool:
    if path.name.startswith("template_"):
        return True
    parts = {p.lower() for p in path.parts}
    if SKIP_PARTS & parts:
        return True
    return False


def build_index() -> dict[str, dict[str, list[dict[str, Any]]]]:
    cache: dict[Path, Any] = {}
    output: dict[str, dict[str, list[dict[str, Any]]]] = {}

    for mission_file in sorted(MISSION_ROOT.rglob("*.blkx")):
        if should_skip_mission_file(mission_file):
            continue

        mission_def = _load_json(mission_file, cache)
        if not isinstance(mission_def, dict):
            continue
        mission_settings = mission_def.get("mission_settings")
        if not isinstance(mission_settings, dict):
            continue
        mission_cfg = mission_settings.get("mission")
        if not isinstance(mission_cfg, dict):
            continue

        level_path = mission_cfg.get("level", "")
        if not isinstance(level_path, str) or not level_path.strip():
            continue
        level_stem = Path(level_path).stem
        mode = _mode_key(mission_cfg, mission_file)
        mission_type = str(mission_cfg.get("type", ""))
        allowed = mission_cfg.get("allowedUnitTypes")

        areas, files_used, imports_used = _collect_defs(mission_file, cache)
        captures = _pick_capture_areas(areas, mission_type, mode)
        if not captures:
            continue

        has_tanks = _has_tanks(allowed if isinstance(allowed, dict) else None)
        has_planes = _has_planes(allowed if isinstance(allowed, dict) else None, imports_used)
        if not (has_tanks and has_planes):
            continue

        battle_areas = _battle_areas(areas)
        info = {
            "mission_file": _repo_rel(mission_file),
            "level_path": _datamine_level_path(level_path),
            "mission_type": mission_type,
            "mode_postfix": mission_cfg.get("postfix", ""),
            "allowed_unit_types": allowed if isinstance(allowed, dict) else {},
            "useAlternativeMapCoord": mission_cfg.get("useAlternativeMapCoord"),
            "capture_areas": captures,
            "battle_areas": battle_areas,
            "level_def": _level_info(level_stem, cache),
            "imports_count": len(imports_used),
            "files_used_count": len(files_used),
        }

        output.setdefault(level_stem, {}).setdefault(mode, []).append({"info": info})

    return output


def main() -> None:
    parser = argparse.ArgumentParser(description="Build map_name -> mode -> [info] JSON")
    parser.add_argument(
        "--out",
        type=Path,
        default=SCRIPT_DIR.parent / "map_mode_info.json",
        help="Output JSON path",
    )
    args = parser.parse_args()

    data = build_index()
    args.out.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")

    maps = len(data)
    modes = sum(len(v) for v in data.values())
    entries = sum(len(items) for v in data.values() for items in v.values())
    print(f"Wrote {args.out} (maps={maps}, modes={modes}, entries={entries})")


if __name__ == "__main__":
    main()
