#!/usr/bin/env python3
from __future__ import annotations

import re
from pathlib import Path

ROOT = Path(__file__).resolve().parents[3]
STATIC_SCAN = ROOT / "wow-addon/DecorDrops/StaticScanData.lua"
INSTANCE_LOCATIONS = Path("/Applications/World of Warcraft/_retail_/Interface/AddOns/HandyNotes_MapNotes_Instances/Nodes/InstanceLocations.lua")
OUT_WAYPOINTS = ROOT / "wow-addon/DecorDrops/StaticInstanceWaypoints.lua"
OUT_OPENMAP = ROOT / "wow-addon/DecorDrops/StaticInstanceOpenMapIDs.lua"

MIDNIGHT_ZONE_MAP_IDS = {2393, 2395, 2405, 2413, 2424, 2437, 2444, 2537}
CONTINENT_MAP_IDS = {12, 13, 101, 113, 424, 572, 619, 875, 876, 946, 947, 1550, 1978, 2274}

START_MARKER = "-- BEGIN MIDNIGHT IMPORT (auto-generated)"
END_MARKER = "-- END MIDNIGHT IMPORT"


def parse_midnight_instances_from_scan(path: Path) -> dict[int, dict[str, str]]:
    text = path.read_text(encoding="utf-8")
    instances: dict[int, dict[str, str]] = {}

    block_re = re.compile(r"\{\s*\n(.*?)\n\}\s*,?", re.DOTALL)
    for block in block_re.finditer(text):
        body = block.group(1)
        if '"expansion"] = "Midnight"' not in body:
            continue
        m_id = re.search(r'\["instanceID"\]\s*=\s*(\d+)', body)
        m_name = re.search(r'\["name"\]\s*=\s*"([^"]+)"', body)
        m_type = re.search(r'\["type"\]\s*=\s*"([^"]+)"', body)
        if not (m_id and m_name and m_type):
            continue
        iid = int(m_id.group(1))
        if m_type.group(1) not in {"Dungeon", "Raid"}:
            continue
        instances[iid] = {"name": m_name.group(1), "type": m_type.group(1)}

    return instances


def decode_packed_coord(raw: str) -> tuple[float, float]:
    raw = re.sub(r"\D", "", raw)
    if len(raw) < 8:
        raw = raw.zfill(8)
    x = int(raw[:4]) / 100.0
    y = int(raw[4:8]) / 100.0
    return x, y


def parse_midnight_waypoints(instance_locations: Path, wanted_ids: set[int]) -> dict[int, dict[str, float | int]]:
    out: dict[int, list[dict[str, float | int]]] = {}

    entry_re = re.compile(
        r"nodes\[(\d+)\]\[(\d+)\]\s*=\s*\{([^}]*)\}",
        re.DOTALL,
    )
    type_re = re.compile(r'type\s*=\s*"(Dungeon|Raid)"')
    id_re = re.compile(r"\bid\s*=\s*(\d+)")

    text = instance_locations.read_text(encoding="utf-8", errors="ignore")
    for m in entry_re.finditer(text):
        map_id = int(m.group(1))
        packed = m.group(2)
        body = m.group(3)

        m_type = type_re.search(body)
        m_id = id_re.search(body)
        if not (m_type and m_id):
            continue

        instance_id = int(m_id.group(1))
        if instance_id not in wanted_ids:
            continue

        x, y = decode_packed_coord(packed)
        out.setdefault(instance_id, []).append(
            {
                "mapID": map_id,
                "x": round(x, 1),
                "y": round(y, 1),
            }
        )

    chosen: dict[int, dict[str, float | int]] = {}
    for iid, candidates in out.items():
        def score(c: dict[str, float | int]) -> int:
            map_id = int(c["mapID"])
            s = 0
            if map_id in MIDNIGHT_ZONE_MAP_IDS:
                s += 100
            if map_id in CONTINENT_MAP_IDS:
                s -= 50
            if 2300 <= map_id <= 2600:
                s += 25
            return s

        candidates.sort(key=score, reverse=True)
        chosen[iid] = candidates[0]

    return chosen


def inject_generated_block(path: Path, block: str, dict_table: bool) -> None:
    text = path.read_text(encoding="utf-8")

    marker_block_re = re.compile(
        re.escape(START_MARKER) + r".*?" + re.escape(END_MARKER) + r"\n?",
        re.DOTALL,
    )
    text = marker_block_re.sub("", text)

    close_index = text.rfind("}")
    if close_index == -1:
        raise RuntimeError(f"Could not find closing table brace in {path}")

    insertion = []
    insertion.append("\n")
    insertion.append(f"  {START_MARKER}\n") if dict_table else insertion.append(f"  {START_MARKER}\n")
    insertion.append(block)
    insertion.append(f"  {END_MARKER}\n")

    text = text[:close_index] + "".join(insertion) + text[close_index:]
    path.write_text(text, encoding="utf-8")


def build_waypoint_block(instances: dict[int, dict[str, str]], coords: dict[int, dict[str, float | int]]) -> str:
    lines: list[str] = []
    for iid in sorted(coords.keys(), key=lambda k: instances[k]["name"]):
        info = instances[iid]
        c = coords[iid]
        lines.append(
            f'  {{ name = "{info["name"]}", type = "{info["type"]}", mapID = {int(c["mapID"])}, x = {c["x"]:.1f}, y = {c["y"]:.1f} }},\n'
        )
    return "".join(lines)


def build_openmap_block(instances: dict[int, dict[str, str]], coords: dict[int, dict[str, float | int]]) -> str:
    lines: list[str] = []
    for iid in sorted(coords.keys(), key=lambda k: instances[k]["name"]):
        info = instances[iid]
        c = coords[iid]
        lines.append(f'  ["{info["name"]}"] = {int(c["mapID"])},\n')
    return "".join(lines)


def main() -> None:
    instances = parse_midnight_instances_from_scan(STATIC_SCAN)
    if not instances:
        raise RuntimeError("No Midnight instances found in StaticScanData.lua")

    coords = parse_midnight_waypoints(INSTANCE_LOCATIONS, set(instances.keys()))
    if not coords:
        raise RuntimeError("No Midnight instance coordinates found in InstanceLocations.lua")

    # Keep only instance IDs we could actually resolve to coordinates.
    instances = {iid: instances[iid] for iid in coords.keys()}

    waypoint_block = build_waypoint_block(instances, coords)
    openmap_block = build_openmap_block(instances, coords)

    inject_generated_block(OUT_WAYPOINTS, waypoint_block, dict_table=False)
    inject_generated_block(OUT_OPENMAP, openmap_block, dict_table=True)

    print(f"Imported {len(instances)} Midnight instance waypoint/open-map entries.")


if __name__ == "__main__":
    main()
