#!/usr/bin/env python3
"""
Generate AtlasLootClassic_TBCA_BIS/data.lua from 4.1 item CSVs.

Supported item source shapes:
1. Phase-expanded rows:
   Spec,Phase,Slot,Rank,ItemID,...
2. Explicit normalized rows:
   Spec,Slot,ItemID,...,Phase1ItemRank..Phase5ItemRank
3. Auto-rank rows:
   Spec,Slot,ItemID,ItemPhase,EPV,HEPV

Usage:
  python AtlasLootClassic_TBCA_BIS/tools/csv_to_atlasloot.py ITEMS_CSV CLASS_MAP_CSV OUTPUT_LUA
"""

from __future__ import annotations

import argparse
import csv
from collections import defaultdict
from pathlib import Path


SLOT_ORDER = [
    "Head",
    "Neck",
    "Shoulders",
    "Back",
    "Chest",
    "Wrist",
    "Hands",
    "Waist",
    "Legs",
    "Feet",
    "Ring",
    "Trinket",
    "Main Hand",
    "Off Hand",
    "One Hand",
    "Two Hand",
    "Ranged",
]

SLOT_AL_KEY = {
    "Head": "Head",
    "Neck": "Neck",
    "Shoulders": "Shoulders",
    "Back": "Back",
    "Chest": "Chest",
    "Wrist": "Wrist",
    "Hands": "Hands",
    "Waist": "Waist",
    "Legs": "Legs",
    "Feet": "Feet",
    "Ring": "Ring",
    "Trinket": "Trinket",
    "Main Hand": "Main Hand",
    "Off Hand": "Off Hand",
    "One Hand": "One Hand",
    "Two Hand": "Two Hand",
    "Ranged": "Ranged",
}


def parse_number(value: str):
    if value is None:
        return None
    value = value.strip()
    if value == "" or value.lower() in {"na", "n/a", "nan"}:
        return None
    try:
        num = float(value)
    except ValueError:
        return None
    if num.is_integer():
        return int(num)
    return num


def parse_phase(value: str):
    if value is None:
        return None
    value = value.strip().upper()
    if value.startswith("P"):
        value = value[1:]
    num = parse_number(value)
    if not isinstance(num, int) or num < 1 or num > 5:
        return None
    return num


def phase_rank_headers(fieldnames: list[str] | None):
    if not fieldnames:
        return []
    out = []
    for phase in range(1, 6):
        name = f"Phase{phase}ItemRank"
        if name in fieldnames:
            out.append((phase, name))
    return out


def load_class_map(path: Path):
    by_source = {}
    atlas_to_display = {}
    with path.open(newline="", encoding="utf-8-sig") as f:
        reader = csv.DictReader(f)
        required = {"SourceSpecCode", "DisplayName", "AtlasLootKey"}
        if not required.issubset(reader.fieldnames or []):
            raise SystemExit(f"Missing required columns in class map: {path}")

        for row in reader:
            source = (row.get("SourceSpecCode") or "").strip()
            display = (row.get("DisplayName") or "").strip()
            atlas_key = (row.get("AtlasLootKey") or "").strip()
            if source and atlas_key:
                by_source[source] = atlas_key
                atlas_to_display[atlas_key] = display

    if not by_source:
        raise SystemExit(f"No class mappings found in {path}")
    return by_source, atlas_to_display


def canonical_item_rows(path: Path, source_to_atlas: dict[str, str]):
    with path.open(newline="", encoding="utf-8-sig") as f:
        reader = csv.DictReader(f)
        fieldnames = reader.fieldnames or []
        normalized_phase_cols = phase_rank_headers(fieldnames)
        normalized_mode = bool(normalized_phase_cols)
        expanded_mode = "Phase" in fieldnames and "Rank" in fieldnames
        auto_rank_mode = not normalized_mode and not expanded_mode

        if auto_rank_mode:
            required = {"Spec", "Slot", "ItemID", "ItemPhase", "EPV"}
        elif normalized_mode:
            required = {"Spec", "Slot", "ItemID", "ItemPhase", "EPV"}
        else:
            required = {"Spec", "Phase", "Slot", "Rank", "ItemID", "EPV"}

        if not required.issubset(fieldnames):
            raise SystemExit(f"Missing required columns in items CSV: {path}")

        canonical_rows = []
        for row in reader:
            item_id = parse_number(row.get("ItemID") or "")
            if not isinstance(item_id, int):
                continue

            source_spec = (row.get("Spec") or "").strip()
            atlas_key = source_to_atlas.get(source_spec)
            slot = (row.get("Slot") or "").strip()
            item_phase = parse_phase(row.get("ItemPhase") or "") or 1
            epv = parse_number(row.get("EPV") or "")
            if not atlas_key or slot not in SLOT_AL_KEY or epv is None:
                continue

            base = {
                "atlas_key": atlas_key,
                "slot": slot,
                "item_id": item_id,
                "item_phase": item_phase,
                "epv": epv,
            }

            if auto_rank_mode:
                canonical_rows.append(base)
                continue

            if normalized_mode:
                phase_ranks = {}
                for phase, header in normalized_phase_cols:
                    rank = parse_number(row.get(header) or "")
                    if isinstance(rank, int):
                        phase_ranks[phase] = rank
                if not phase_ranks:
                    canonical_rows.append(base)
                    continue
                row_copy = base.copy()
                row_copy["phase_ranks"] = phase_ranks
                canonical_rows.append(row_copy)
                continue

            phase = parse_phase(row.get("Phase") or "")
            rank = parse_number(row.get("Rank") or "")
            if phase is None or not isinstance(rank, int):
                continue
            row_copy = base.copy()
            row_copy["phase_ranks"] = {phase: rank}
            canonical_rows.append(row_copy)

    return canonical_rows, normalized_mode or expanded_mode


def build_phase_lists(canonical_rows: list[dict], source_has_ranks: bool):
    data = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))

    if source_has_ranks:
        for row in canonical_rows:
            phase_ranks = row.get("phase_ranks") or {}
            for phase, rank in sorted(phase_ranks.items()):
                data[row["atlas_key"]][row["slot"]][phase].append((rank, row["item_id"]))
        return data

    phase_groups = defaultdict(list)
    for row in canonical_rows:
        for phase in range(row["item_phase"], 6):
            phase_groups[(row["atlas_key"], row["slot"], phase)].append(row)

    for (atlas_key, slot, phase), rows in phase_groups.items():
        ordered = sorted(rows, key=lambda r: (-float(r["epv"]), r["item_id"]))
        for idx, row in enumerate(ordered, start=1):
            data[atlas_key][slot][phase].append((idx, row["item_id"]))

    return data


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("items_csv", type=Path)
    parser.add_argument("class_map_csv", type=Path)
    parser.add_argument("lua_path", type=Path)
    args = parser.parse_args()

    for path in (args.items_csv, args.class_map_csv):
        if not path.exists():
            raise SystemExit(f"CSV not found: {path}")

    source_to_atlas, atlas_to_display = load_class_map(args.class_map_csv)
    canonical_rows, source_has_ranks = canonical_item_rows(args.items_csv, source_to_atlas)
    data = build_phase_lists(canonical_rows, source_has_ranks)

    args.lua_path.parent.mkdir(parents=True, exist_ok=True)
    with args.lua_path.open("w", encoding="utf-8", newline="\n") as out:
        out.write("-----------------------------------------------------------------------\n")
        out.write("-- Auto-generated. Do not edit by hand.\n")
        out.write("-----------------------------------------------------------------------\n\n")
        out.write("local _G = getfenv(0)\n")
        out.write("local select = _G.select\n")
        out.write("local string = _G.string\n")
        out.write("local format = string.format\n\n")
        out.write("local addonname = ...\n")
        out.write("local AtlasLoot = _G.AtlasLoot\n")
        out.write("local data = AtlasLoot.ItemDB:Add(addonname, 1)\n\n")
        out.write("local AL = AtlasLoot.Locales\n")
        out.write("local ALIL = AtlasLoot.IngameLocales\n\n")
        out.write("local GetForVersion = AtlasLoot.ReturnForGameVersion\n\n")
        out.write("local P1_DIFF = data:AddDifficulty(\"Phase 1\", \"p1\", 1, nil, true)\n")
        out.write("local P2_DIFF = data:AddDifficulty(\"Phase 2\", \"p2\", 2, nil, true)\n")
        out.write("local P3_DIFF = data:AddDifficulty(\"Phase 3\", \"p3\", 3, nil, true)\n")
        out.write("local P4_DIFF = data:AddDifficulty(\"Phase 4\", \"p4\", 4, nil, true)\n")
        out.write("local P5_DIFF = data:AddDifficulty(\"Phase 5\", \"p5\", 5, nil, true)\n\n")
        out.write("local NORMAL_ITTYPE = data:AddItemTableType(\"Item\", \"Item\")\n")
        out.write("local SET_ITTYPE = data:AddItemTableType(\"Set\", \"Item\")\n\n")
        out.write("local QUEST_EXTRA_ITTYPE = data:AddExtraItemTableType(\"Quest\")\n")
        out.write("local PRICE_EXTRA_ITTYPE = data:AddExtraItemTableType(\"Price\")\n")
        out.write("local SET_EXTRA_ITTYPE = data:AddExtraItemTableType(\"Set\")\n\n")
        out.write("local VENDOR_CONTENT = data:AddContentType(AL[\"Vendor\"], ATLASLOOT_DUNGEON_COLOR)\n")
        out.write("local SET_CONTENT = data:AddContentType(AL[\"Sets\"], ATLASLOOT_PVP_COLOR)\n")
        out.write("local COLLECTIONS_CONTENT = data:AddContentType(AL[\"Collections\"], ATLASLOOT_COLLECTIONS_COLOR)\n")
        out.write("local WORLD_EVENT_CONTENT = data:AddContentType(AL[\"World Events\"], ATLASLOOT_SEASONALEVENTS_COLOR)\n\n")

        for atlas_key in sorted(data.keys()):
            display_name = (atlas_to_display.get(atlas_key) or atlas_key) + " BiS"

            out.write(f"data[\"{atlas_key}\"] = {{\n")
            out.write(f"\tname = \"{display_name}\",\n")
            out.write("\tContentType = SET_CONTENT,\n")
            out.write("\titems = {\n")

            for slot in SLOT_ORDER:
                if slot not in data[atlas_key]:
                    continue
                out.write(f"\t\t{{ -- {slot}\n")
                al_key = SLOT_AL_KEY.get(slot, slot)
                out.write(f"\t\t\tname = AL[\"{al_key}\"],\n")

                for phase, diff_name in ((1, "P1_DIFF"), (2, "P2_DIFF"), (3, "P3_DIFF"), (4, "P4_DIFF"), (5, "P5_DIFF")):
                    items = data[atlas_key][slot].get(phase, [])
                    if not items:
                        continue
                    items.sort(key=lambda x: x[0])
                    out.write(f"\t\t\t[{diff_name}] = {{\n")
                    idx = 1
                    for _, item_id in items:
                        out.write(f"\t\t\t\t{{ {idx}, {item_id} }},\n")
                        idx += 1
                    out.write("\t\t\t},\n")

                out.write("\t\t},\n")

            out.write("\t},\n")
            out.write("}\n\n")


if __name__ == "__main__":
    main()
