"""Generate the first-pass NWN tileset definition `twu01.set` from tiles.py.

    python make_set.py [out.set]      # default: twu01.set

Pure Python, no Blender. Emits a valid, DOS/CRLF `.set` (AGENTS.md §4/§9: Unix
line endings crash the toolset). Corners/heights/crossers derive from each tile's
edge grammar; this is a DRAFT to load and hand-tune in the toolset, not a final
set. See CAVEATS below.

Every NON-paint tile gets a 1x1 [GROUP#] (a "feature") so it is click-placeable
from the palette (AGENTS.md §2.3) — the .itp Features folder points its leaves at
these groups by first-tile resref, and Name must match tiles.group_name().

PAINT tiles (tiles.is_paint: cobble fill CP1/2/3, canal fill CN1, canal edges
CE_*) are deliberately NOT grouped: a grouped tile is excluded from the toolset's
terrain-paint pool, so leaving them ungrouped IS what makes cobble/canal
paintable and the eraser work. Grouping every tile empties the fill pool and the
New Area wizard crashes (verified vs stock tcn01, 2026-07-04). Transition tiles
expand into 4 orientations (tiles.placements) so the corner matrix is complete;
make_set.main() hard-fails if tiles.canal_paint_matrix() is missing any pattern.

CAVEATS (first pass — tune in the toolset):
  * PathNode is a placeholder ('A' walkable / 'N' non-walkable). Real pathnode
    letters govern creature routing and must be verified against the toolset.
  * ImageMap2D references minimaps (twu01_<code>.tga) that are not rendered yet.
"""

import re
import sys

import tiles

PREFIX = tiles.PREFIX


def tile_block(i, spec, orient, corners, edges):
    tl, tr, bl, br = corners
    n, e, s, w = edges
    ref = tiles.resref(spec)
    walkable = spec["kind"] != "canal"
    # Paint tiles connect by CORNER match only (like stock wut01 terrains) — blank
    # their edge crossers so the brush isn't over-constrained; features keep theirs.
    cr = (lambda edge: "") if tiles.is_paint(spec) else tiles.nwn_crosser
    # Field order/values mirror the stock BioWare sets (the toolset's .set reader
    # is picky); WalkMesh=msb01 is the universal legacy label, not a file.
    L = [f"[TILE{i}]"]
    L += [
        f"Model={ref}",
        "WalkMesh=msb01",
        f"TopLeft={tl[0]}", f"TopLeftHeight={tl[1]}",
        f"TopRight={tr[0]}", f"TopRightHeight={tr[1]}",
        f"BottomLeft={bl[0]}", f"BottomLeftHeight={bl[1]}",
        f"BottomRight={br[0]}", f"BottomRightHeight={br[1]}",
        f"Top={cr(n)}",
        f"Right={cr(e)}",
        f"Bottom={cr(s)}",
        f"Left={cr(w)}",
        "MainLight1=1", "MainLight2=1", "SourceLight1=1", "SourceLight2=1",
        "AnimLoop1=1", "AnimLoop2=1", "AnimLoop3=1",
        "Doors=0",
        "Sounds=0",
        f"PathNode={'A' if walkable else 'N'}",   # placeholder — verify in toolset
        f"Orientation={orient}",                  # 0/90/180/270 rotations of one model
        "VisibilityNode=A",
        "VisibilityOrientation=0",
        f"ImageMap2D={ref}",              # minimap resref (no .tga yet; blank in toolset)
    ]
    return L


def build():
    L = []
    L += [
        "[GENERAL]",
        f"Name={PREFIX}",
        "Type=SET",
        "Interior=0",
        "HasHeightTransition=0",          # MVP paints flat cobble only; matches stock
        "Transition=1.5",                 # metres per height step = Z=1.5 raised tier (unused while HHT=0)
        "Border=cobble",
        "Default=cobble",
        "Floor=cobble",
        "DisplayName=-1",
        "UnlocalizedName=Westgate Urban Foundation",
        "Version=V1.0",
        "EnvMap=",
        "SelectorHeight=3",
        "",
        "[GRASS]",
        "Grass=0",
        "GrassTextureName=",
        "Density=0.0",
        "Height=0.0",
        "AmbientRed=0.0", "AmbientGreen=0.0", "AmbientBlue=0.0",
        "DiffuseRed=0.0", "DiffuseGreen=0.0", "DiffuseBlue=0.0",
        "",
        "[TERRAIN TYPES]",
        f"Count={len(tiles.TERRAINS)}",
    ]
    for idx, t in enumerate(tiles.TERRAINS):
        L += ["", f"[TERRAIN{idx}]", f"Name={t}", "StrRef=-1"]
    L += ["", "[CROSSER TYPES]", f"Count={len(tiles.CROSSERS)}"]
    for idx, cr in enumerate(tiles.CROSSERS):
        L += ["", f"[CROSSER{idx}]", f"Name={cr}", "StrRef=-1"]
    L += ["", "[PRIMARY RULES]", "Count=0",       # optional; wut01 paints 6 terrains w/ 0
          "", "[SECONDARY RULES]", "Count=0"]
    # Expand each spec into its placement(s): transition tiles emit 4 rotations, so
    # one model backs several [TILE#] entries. Track each spec's FIRST tile index so
    # its [GROUP#] (if any) points at the right tile.
    placed, first_idx = [], {}
    for spec in tiles.TILES:
        for pl in tiles.placements(spec):
            first_idx.setdefault(spec["code"], len(placed))
            placed.append((spec, pl))
    L += ["", "[TILES]", f"Count={len(placed)}"]
    for i, (spec, (orient, corners, edges)) in enumerate(placed):
        L.append("")
        L += tile_block(i, spec, orient, corners, edges)
    # One 1x1 group ("feature") per NON-paint tile so the palette can click-place it.
    # Paint tiles (fills + terrain transitions) stay ungrouped -> the brush pool.
    feats = [s for s in tiles.TILES if not tiles.is_paint(s)]
    L += ["", "[GROUPS]", f"Count={len(feats)}"]
    for gi, spec in enumerate(feats):
        L += ["", f"[GROUP{gi}]", f"Name={tiles.group_name(spec)}",
              "Rows=1", "Columns=1", f"Tile0={first_idx[spec['code']]}"]
    L += [""]
    return L


def main():
    _, _cov, missing = tiles.canal_paint_matrix()
    if missing:                                 # incomplete matrix = paint crash; never stage
        raise SystemExit(f"INCOMPLETE canal paint matrix — {len(missing)} pattern(s) "
                         f"have no tile: {sorted(missing)}")
    out = sys.argv[1] if len(sys.argv) > 1 else f"{PREFIX}.set"
    lines = build()
    with open(out, "w", newline="") as fh:      # newline='' + explicit \r\n = CRLF
        fh.write("\r\n".join(lines))
    ntiles = sum(1 for l in lines if re.fullmatch(r"\[TILE\d+\]", l))
    print(f"wrote {out}  ({ntiles} tile placements from {len(tiles.TILES)} models, "
          f"{len(tiles.TERRAINS)} terrains, {len(tiles.CROSSERS)} crossers, CRLF)")


if __name__ == "__main__":
    main()
