#!/usr/bin/env python3
"""
Build a trimmed item cache for the MoonWellClient EncounterJournal addon.

Reads one or more donor cache files (Sirus-style Generated_ItemsCache*.lua,
each holding rows like `[id] = { "name_en", "name_ru", quality, ..., "icon",
... }`), intersects them with the item ids referenced by
EncounterJournalData.json (the file produced by .ej export) and emits a
Lua file containing only the matching rows.

Output schema (single global, Lua):

    MoonWellEncounterJournalItemCache = {
        [<item_id>] = { name_en, name_ru, quality, icon },
        ...
    }

Multiple donor files are merged left-to-right; later files override earlier
ones for duplicate ids. Items missing from every donor are reported on
stderr and skipped (the client falls back to GetItemInfo for those).
"""

from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path
from typing import Dict, Iterable, Set, Tuple

# Row layout per Sirus convention:
#   [id] = { "name_en", "name_ru", quality, classId, subClass,
#            inventoryType, slot, stackable, idx, "icon", spellId },
ROW_RE = re.compile(
    r"""^\s*\[(?P<id>\d+)\]\s*=\s*\{\s*
        "(?P<en>(?:[^"\\]|\\.)*)"\s*,\s*
        "(?P<ru>(?:[^"\\]|\\.)*)"\s*,\s*
        (?P<quality>-?\d+)\s*,\s*
        -?\d+\s*,\s*    # field 4
        -?\d+\s*,\s*    # field 5
        -?\d+\s*,\s*    # field 6
        -?\d+\s*,\s*    # field 7
        -?\d+\s*,\s*    # field 8
        -?\d+\s*,\s*    # field 9
        "(?P<icon>(?:[^"\\]|\\.)*)"\s*,\s*
        -?\d+\s*
        \}\s*,?\s*$""",
    re.VERBOSE,
)


def parse_donor(path: Path) -> Dict[int, Tuple[str, str, int, str]]:
    """Returns {id: (name_en, name_ru, quality, icon)} from one donor file."""
    out: Dict[int, Tuple[str, str, int, str]] = {}
    bad = 0
    with path.open("r", encoding="utf-8") as f:
        for line in f:
            m = ROW_RE.match(line)
            if not m:
                stripped = line.strip()
                if stripped and not stripped.startswith(
                        ("--", "Items", "ItemsCache", "}", "{")
                ):
                    bad += 1
                continue
            out[int(m.group("id"))] = (
                m.group("en"),
                m.group("ru"),
                int(m.group("quality")),
                m.group("icon"),
            )
    if bad:
        print(f"[{path.name}] skipped {bad} unparseable lines", file=sys.stderr)
    return out


def collect_needed_ids(journal_json: Path) -> Set[int]:
    """Returns the set of item ids referenced in EncounterJournalData.json."""
    with journal_json.open("r", encoding="utf-8") as f:
        data = json.load(f)
    needed: Set[int] = set()
    for rows in data.get("items", {}).values():
        for row in rows:
            # items[encounterID] row layout per docs/encounter_journal_format.md:
            # [item_id, encounter_id, difficulty_mask, faction_mask, flags,
            #  id, class_mask]
            if row:
                needed.add(int(row[0]))
    return needed


def lua_str(s: str) -> str:
    s = s.replace("\\", "\\\\").replace("'", "\\'")
    s = s.replace("\n", "\\n").replace("\r", "\\r")
    return f"'{s}'"


def write_lua(out_path: Path, global_name: str,
              cache: Dict[int, Tuple[str, str, int, str]]) -> None:
    with out_path.open("w", encoding="utf-8") as f:
        f.write("-- Generated by mod-encounter-journal/tools/"
                "build_item_cache.py.\n")
        f.write("-- Trimmed donor item cache for items referenced by "
                "EncounterJournalData.\n\n")
        f.write(f"{global_name} = {{\n")
        for item_id in sorted(cache.keys()):
            en, ru, quality, icon = cache[item_id]
            f.write(
                f"    [{item_id}] = {{ {lua_str(en)}, {lua_str(ru)}, "
                f"{quality}, {lua_str(icon)} }},\n"
            )
        f.write("}\n")


def merge_donors(donor_paths: Iterable[Path]
                 ) -> Dict[int, Tuple[str, str, int, str]]:
    merged: Dict[int, Tuple[str, str, int, str]] = {}
    for p in donor_paths:
        rows = parse_donor(p)
        print(f"[{p.name}] parsed {len(rows)} rows", file=sys.stderr)
        merged.update(rows)
    return merged


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--journal", "-j", required=True, type=Path,
                        help="EncounterJournalData.json (from .ej export)")
    parser.add_argument("--donor", "-d", required=True, type=Path, nargs="+",
                        help="One or more Generated_ItemsCache*.lua files")
    parser.add_argument("--output", "-o", required=True, type=Path,
                        help="Where to write EncounterJournalItemCache.lua")
    parser.add_argument("--global", dest="global_name",
                        default="MoonWellEncounterJournalItemCache",
                        help="Lua global name (default: %(default)s)")
    args = parser.parse_args()

    needed = collect_needed_ids(args.journal)
    print(f"EJ references {len(needed)} unique item ids", file=sys.stderr)

    donors = merge_donors(args.donor)
    print(f"donor cache holds {len(donors)} unique item ids", file=sys.stderr)

    trimmed = {i: donors[i] for i in needed if i in donors}
    missing = sorted(needed - donors.keys())

    write_lua(args.output, args.global_name, trimmed)

    print(
        f"wrote {len(trimmed)} rows to {args.output} "
        f"(missing in donor: {len(missing)})",
        file=sys.stderr,
    )
    if missing:
        sample = ", ".join(str(i) for i in missing[:15])
        more = "" if len(missing) <= 15 else f" ... and {len(missing) - 15} more"
        print(f"missing item ids (client will fall back to GetItemInfo): "
              f"{sample}{more}", file=sys.stderr)
    return 0


if __name__ == "__main__":
    sys.exit(main())
