# tiled_demo.py - Visual demo of Tiled integration
# Shows premade maps, Wang auto-tiling, procgen terrain, and edge-type path overlay
#
# Usage:
#   Headless:     cd build && ./mcrogueface --headless --exec ../tests/demo/screens/tiled_demo.py
#   Interactive:  cd build && ./mcrogueface --exec ../tests/demo/screens/tiled_demo.py

import mcrfpy
from mcrfpy import automation
import sys
import math
import random

# -- Asset Paths -------------------------------------------------------
PUNY_BASE = "/home/john/Development/7DRL2026_Liber_Noster_jmccardle/assets_sources/PUNY_WORLD_v1/PUNY_WORLD_v1"
TSX_PATH = PUNY_BASE + "/Tiled/punyworld-overworld-tiles.tsx"

# -- Load Shared Assets ------------------------------------------------
print("Loading Puny World tileset...")
tileset = mcrfpy.TileSetFile(TSX_PATH)
texture = tileset.to_texture()
overworld_ws = tileset.wang_set("overworld")
Terrain = overworld_ws.terrain_enum()

print(f"  Tileset: {tileset.name}")
print(f"  Tiles: {tileset.tile_count} ({tileset.columns} cols, {tileset.tile_width}x{tileset.tile_height}px)")
print(f"  Wang set: {overworld_ws.name} ({overworld_ws.type}, {overworld_ws.color_count} colors)")
print(f"  Terrain enum members: {[t.name for t in Terrain]}")

# -- Helper: Iterative terrain expansion ----------------------------------
def iterative_terrain(hm, wang_set, width, height, passes):
    """Build a DiscreteMap by iteratively splitting terrains outward from
    a valid binary map. Each pass splits one terrain on each end of the
    chain, validates with wang_set.resolve(), and reverts invalid cells
    to their previous value.

    hm:     HeightMap (normalized 0-1)
    passes: list of (threshold, lo_old, lo_new, hi_old, hi_new) tuples.
            Each pass says: cells currently == lo_old with height < threshold
            become lo_new; cells currently == hi_old with height >= threshold
            become hi_new.

    Returns (DiscreteMap, stats_dict).
    """
    dm = mcrfpy.DiscreteMap((width, height))

    # Pass 0: binary split - everything is one of two terrains
    p0 = passes[0]
    thresh, lo_terrain, hi_terrain = p0
    for y in range(height):
        for x in range(width):
            if hm.get(x, y) < thresh:
                dm.set(x, y, int(lo_terrain))
            else:
                dm.set(x, y, int(hi_terrain))

    # Validate pass 0 and fix any invalid cells (rare edge cases like
    # checkerboard patterns at the binary boundary)
    results = wang_set.resolve(dm)
    inv = sum(1 for r in results if r == -1)
    if inv > 0:
        # Fix by flipping invalid cells to the other terrain
        for y in range(height):
            for x in range(width):
                if results[y * width + x] == -1:
                    val = dm.get(x, y)
                    if val == int(lo_terrain):
                        dm.set(x, y, int(hi_terrain))
                    else:
                        dm.set(x, y, int(lo_terrain))
        results2 = wang_set.resolve(dm)
        inv = sum(1 for r in results2 if r == -1)
    stats = {"pass0_invalid": inv}

    # Subsequent passes: split outward
    for pi, (thresh, lo_old, lo_new, hi_old, hi_new) in enumerate(passes[1:], 1):
        # Save current state so we can revert invalid cells
        prev = [dm.get(x, y) for y in range(height) for x in range(width)]

        # Track which cells were changed this pass
        changed = set()
        for y in range(height):
            for x in range(width):
                val = dm.get(x, y)
                h = hm.get(x, y)
                if val == int(lo_old) and h < thresh:
                    dm.set(x, y, int(lo_new))
                    changed.add((x, y))
                elif val == int(hi_old) and h >= thresh:
                    dm.set(x, y, int(hi_new))
                    changed.add((x, y))

        # Iteratively revert changed cells that cause invalid tiles.
        # A changed cell should be reverted if:
        #   - It is itself invalid, OR
        #   - It is a neighbor of an invalid UN-changed cell (it broke
        #     a pre-existing valid cell by being placed next to it)
        dirs8 = [(-1,-1),(0,-1),(1,-1),(-1,0),(1,0),(-1,1),(0,1),(1,1)]
        total_reverted = 0
        for revert_round in range(30):
            results = wang_set.resolve(dm)
            to_revert = set()
            for y in range(height):
                for x in range(width):
                    if results[y * width + x] != -1:
                        continue
                    if (x, y) in changed:
                        # This changed cell is invalid - revert it
                        to_revert.add((x, y))
                    else:
                        # Pre-existing cell is now invalid - revert its
                        # changed neighbors to restore it
                        for dx, dy in dirs8:
                            nx, ny = x+dx, y+dy
                            if (nx, ny) in changed:
                                to_revert.add((nx, ny))

            if not to_revert:
                break

            for (x, y) in to_revert:
                dm.set(x, y, prev[y * width + x])
                changed.discard((x, y))
                total_reverted += 1

        results_final = wang_set.resolve(dm)
        remaining = sum(1 for r in results_final if r == -1)
        stats[f"pass{pi}_kept"] = len(changed)
        stats[f"pass{pi}_reverted"] = total_reverted
        stats[f"pass{pi}_remaining"] = remaining

    return dm, stats


# -- Helper: Info Panel -------------------------------------------------
def make_info_panel(scene, lines, x=560, y=60, w=220, h=None):
    """Create a semi-transparent info panel with text lines."""
    if h is None:
        h = len(lines) * 22 + 20
    panel = mcrfpy.Frame(pos=(x, y), size=(w, h),
                         fill_color=mcrfpy.Color(20, 20, 30, 220),
                         outline_color=mcrfpy.Color(80, 80, 120),
                         outline=1.5)
    scene.children.append(panel)
    for i, text in enumerate(lines):
        cap = mcrfpy.Caption(text=text, pos=(10, 10 + i * 22))
        cap.fill_color = mcrfpy.Color(200, 200, 220)
        panel.children.append(cap)
    return panel


# ======================================================================
# SCREEN 1: Premade Tiled Map
# ======================================================================
print("\nSetting up Screen 1: Premade Map...")
scene1 = mcrfpy.Scene("tiled_premade")

bg1 = mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(10, 10, 15))
scene1.children.append(bg1)

title1 = mcrfpy.Caption(text="Premade Tiled Map (50x50, 3 layers)", pos=(20, 10))
title1.fill_color = mcrfpy.Color(255, 255, 255)
scene1.children.append(title1)

# Load samplemap1
tm1 = mcrfpy.TileMapFile(PUNY_BASE + "/Tiled/samplemap1.tmj")
print(f"  Map: {tm1.width}x{tm1.height}, layers: {tm1.tile_layer_names}")

grid1 = mcrfpy.Grid(grid_size=(tm1.width, tm1.height),
                     pos=(20, 50), size=(520, 520), layers=[])
grid1.fill_color = mcrfpy.Color(30, 30, 50)

# Add a tile layer for each map layer, bottom-up z ordering
layer_names_1 = tm1.tile_layer_names
for i, name in enumerate(layer_names_1):
    z = -(len(layer_names_1) - i)
    layer = mcrfpy.TileLayer(name=name, z_index=z, texture=texture)
    grid1.add_layer(layer)
    tm1.apply_to_tile_layer(layer, name, tileset_index=0)
    print(f"  Applied layer '{name}' (z_index={z})")

# Center camera on map center (pixels = tiles * tile_size)
grid1.center = (tm1.width * tileset.tile_width // 2,
                tm1.height * tileset.tile_height // 2)
scene1.children.append(grid1)

make_info_panel(scene1, [
    f"Tileset: {tileset.name}",
    f"Tile size: {tileset.tile_width}x{tileset.tile_height}",
    f"Tile count: {tileset.tile_count}",
    f"Map size: {tm1.width}x{tm1.height}",
    "",
    "Layers:",
] + [f"  {name}" for name in layer_names_1] + [
    "",
    "Wang sets:",
    f"  {overworld_ws.name} ({overworld_ws.type})",
    f"  pathways (edge)",
])

nav1 = mcrfpy.Caption(text="[1] Premade  [2] Procgen  [3] Side-by-Side  [ESC] Quit", pos=(20, 740))
nav1.fill_color = mcrfpy.Color(120, 120, 150)
scene1.children.append(nav1)


# ======================================================================
# SCREEN 2: Procedural Wang Auto-Tile (3-layer: terrain + trees + paths)
# ======================================================================
print("\nSetting up Screen 2: Procgen Wang Terrain (3-layer)...")
scene2 = mcrfpy.Scene("tiled_procgen")

bg2 = mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(10, 10, 15))
scene2.children.append(bg2)

title2 = mcrfpy.Caption(text="Procgen Wang Auto-Tile (60x60, 3 layers)", pos=(20, 10))
title2.fill_color = mcrfpy.Color(255, 255, 255)
scene2.children.append(title2)

W, H = 60, 60
T = Terrain  # shorthand

# Generate terrain heightmap using NoiseSource
noise = mcrfpy.NoiseSource(dimensions=2, seed=42)
hm = noise.sample(size=(W, H), mode="fbm", octaves=4, world_size=(4.0, 4.0))
hm.normalize(0.0, 1.0)

# -- Base terrain: iterative expansion from binary map --
# Pass 0: binary split at median -> SEAWATER_LIGHT / SAND
# Pass 1: split outward -> SEAWATER_MEDIUM from LIGHT, GRASS from SAND
# Pass 2: split outward -> SEAWATER_DEEP from MEDIUM, CLIFF from GRASS
base_passes = [
    # Pass 0: (threshold, lo_terrain, hi_terrain)
    (0.45, T.SEAWATER_LIGHT, T.SAND),
    # Pass 1+: (threshold, lo_old, lo_new, hi_old, hi_new)
    (0.30, T.SEAWATER_LIGHT, T.SEAWATER_MEDIUM, T.SAND, T.GRASS),
    (0.20, T.SEAWATER_MEDIUM, T.SEAWATER_DEEP, T.GRASS, T.CLIFF),
]
base_dm, base_stats = iterative_terrain(hm, overworld_ws, W, H, base_passes)
base_dm.enum_type = T
print(f"  Base terrain stats: {base_stats}")

# -- Tree overlay: separate noise, binary TREES/AIR --
tree_noise = mcrfpy.NoiseSource(dimensions=2, seed=999)
tree_hm = tree_noise.sample(size=(W, H), mode="fbm", octaves=3, world_size=(6.0, 6.0))
tree_hm.normalize(0.0, 1.0)

overlay_dm = mcrfpy.DiscreteMap((W, H))
overlay_dm.enum_type = T
for y in range(H):
    for x in range(W):
        base_val = base_dm.get(x, y)
        tree_h = tree_hm.get(x, y)
        # Trees only on GRASS, driven by separate noise
        if base_val == int(T.GRASS) and tree_h > 0.45:
            overlay_dm.set(x, y, int(T.TREES))
        else:
            overlay_dm.set(x, y, int(T.AIR))

# Validate overlay and revert invalid to AIR
overlay_results = overworld_ws.resolve(overlay_dm)
overlay_reverted = 0
for y in range(H):
    for x in range(W):
        if overlay_results[y * W + x] == -1:
            overlay_dm.set(x, y, int(T.AIR))
            overlay_reverted += 1
print(f"  Overlay: {overlay_reverted} tree cells reverted to AIR")

# Count terrain distribution
terrain_counts = {}
for t in T:
    if t == T.NONE:
        continue
    c = base_dm.count(int(t))
    if c > 0:
        terrain_counts[t.name] = c
tree_count = overlay_dm.count(int(T.TREES))
terrain_counts["TREES(overlay)"] = tree_count

print(f"  Terrain distribution: {terrain_counts}")

# -- Path overlay: bezier curves with noise-offset control points --
# Uses the "pathways" EDGE-type Wang set for thin directional path tiles
print("  Generating path network...")
pathways_ws = tileset.wang_set("pathways")
PathT = pathways_ws.terrain_enum()

# 1. Find land cells for POI placement
land_terrains = {int(T.GRASS), int(T.SAND), int(T.CLIFF)}
land_cells = []
for y in range(H):
    for x in range(W):
        if base_dm.get(x, y) in land_terrains:
            land_cells.append((x, y))

# 2. Place POIs across the map using a grid to ensure good distribution
random.seed(42)
poi_grid_n = 4
zone_w, zone_h = W // poi_grid_n, H // poi_grid_n
pois = []
for gx in range(poi_grid_n):
    for gy in range(poi_grid_n):
        zone_cells = [(x, y) for x, y in land_cells
                      if gx * zone_w <= x < (gx + 1) * zone_w
                      and gy * zone_h <= y < (gy + 1) * zone_h]
        if zone_cells:
            pois.append(random.choice(zone_cells))

print(f"    POIs placed: {len(pois)}")

# 3. Build a minimum spanning tree to connect all POIs, then add extra edges for forks
edges = []
if len(pois) > 1:
    connected = {0}
    unconnected = set(range(1, len(pois)))
    while unconnected:
        best_dist = float('inf')
        best_pair = None
        for c in connected:
            for u in unconnected:
                dx = pois[c][0] - pois[u][0]
                dy = pois[c][1] - pois[u][1]
                d = math.hypot(dx, dy)
                if d < best_dist:
                    best_dist = d
                    best_pair = (c, u)
        if best_pair:
            edges.append(best_pair)
            connected.add(best_pair[1])
            unconnected.discard(best_pair[1])

    # Add extra edges for interesting forks (non-MST shortcuts)
    edge_set = set(edges) | {(b, a) for a, b in edges}
    for _ in range(len(pois) // 2):
        i = random.randint(0, len(pois) - 1)
        j = random.randint(0, len(pois) - 1)
        if i != j and (i, j) not in edge_set:
            edges.append((i, j))
            edge_set.add((i, j))
            edge_set.add((j, i))

print(f"    Path edges: {len(edges)} ({len(edges) - len(pois) + 1} extra forks)")

# 4. Dig bezier paths with noise-offset control points for organic curves
path_hm = mcrfpy.HeightMap((W, H))
path_hm.fill(1.0)
path_ctrl_noise = mcrfpy.NoiseSource(dimensions=1, seed=7071)

for idx, (i, j) in enumerate(edges):
    p0 = pois[i]
    p3 = pois[j]
    mx = (p0[0] + p3[0]) / 2.0
    my = (p0[1] + p3[1]) / 2.0
    dx = p3[0] - p0[0]
    dy = p3[1] - p0[1]
    length = max(math.hypot(dx, dy), 1.0)

    # Perpendicular direction for curve offset
    nx, ny = -dy / length, dx / length

    # Noise-driven offset makes each path curve uniquely
    offset1 = path_ctrl_noise.get((float(idx * 2),)) * length * 0.4
    offset2 = path_ctrl_noise.get((float(idx * 2 + 1),)) * length * 0.4

    # Two control points offset along the perpendicular
    # dig_bezier requires integer coordinates (libtcod uses int arrays)
    cx1 = int(round(max(0, min(W - 1, (p0[0] + mx) / 2 + nx * offset1))))
    cy1 = int(round(max(0, min(H - 1, (p0[1] + my) / 2 + ny * offset1))))
    cx2 = int(round(max(0, min(W - 1, (mx + p3[0]) / 2 + nx * offset2))))
    cy2 = int(round(max(0, min(H - 1, (my + p3[1]) / 2 + ny * offset2))))

    path_hm.dig_bezier(
        points=(p0, (cx1, cy1), (cx2, cy2), p3),
        start_radius=0.5, end_radius=0.5,
        start_height=0.0, end_height=0.0
    )

# 5. Convert heightmap to DiscreteMap: low cells = DIRT_PATHS, only on land
path_dm = mcrfpy.DiscreteMap((W, H))
for y in range(H):
    for x in range(W):
        if path_hm.get(x, y) < 0.5 and base_dm.get(x, y) in land_terrains:
            path_dm.set(x, y, int(PathT.DIRT_PATHS))

path_cell_count = path_dm.count(int(PathT.DIRT_PATHS))
print(f"    Path cells: {path_cell_count}")

# Create grid with 3 layers and apply Wang auto-tiling
grid2 = mcrfpy.Grid(grid_size=(W, H), pos=(20, 50), size=(520, 520), layers=[])
grid2.fill_color = mcrfpy.Color(30, 30, 50)

base_layer2 = mcrfpy.TileLayer(name="base", z_index=-3, texture=texture)
grid2.add_layer(base_layer2)
overworld_ws.apply(base_dm, base_layer2)

overlay_layer2 = mcrfpy.TileLayer(name="trees", z_index=-2, texture=texture)
grid2.add_layer(overlay_layer2)
overworld_ws.apply(overlay_dm, overlay_layer2)

# Post-process overlay: AIR resolves to an opaque tile, set to -1 (transparent)
for y in range(H):
    for x in range(W):
        if overlay_dm.get(x, y) == int(T.AIR):
            overlay_layer2.set((x, y), -1)

# 6. Apply pathways edge-type Wang set to path layer
path_layer2 = mcrfpy.TileLayer(name="paths", z_index=-1, texture=texture)
grid2.add_layer(path_layer2)
pathways_ws.apply(path_dm, path_layer2)

# Post-process: only keep tiles on actual path cells (remove neighbor spillover)
# and clear non-path cells to transparent
for y in range(H):
    for x in range(W):
        if path_dm.get(x, y) != int(PathT.DIRT_PATHS):
            path_layer2.set((x, y), -1)

grid2.center = (W * tileset.tile_width // 2, H * tileset.tile_height // 2)
scene2.children.append(grid2)

# Info panel
info_lines = [
    "Iterative terrain expansion",
    f"Seed: 42 (base), 999 (trees)",
    f"Grid: {W}x{H}, 3 layers",
    "",
    "Base (3 passes):",
]
for name in ["SEAWATER_DEEP", "SEAWATER_MEDIUM", "SEAWATER_LIGHT",
             "SAND", "GRASS", "CLIFF"]:
    count = terrain_counts.get(name, 0)
    info_lines.append(f"  {name}: {count}")
info_lines.append("")
info_lines.append("Tree Overlay:")
info_lines.append(f"  TREES: {tree_count}")
info_lines.append(f"  reverted: {overlay_reverted}")
info_lines.append("")
info_lines.append("Path Overlay (edge Wang):")
info_lines.append(f"  POIs: {len(pois)}")
info_lines.append(f"  Edges: {len(edges)}")
info_lines.append(f"  Path cells: {path_cell_count}")

make_info_panel(scene2, info_lines)

nav2 = mcrfpy.Caption(text="[1] Premade  [2] Procgen  [3] Side-by-Side  [ESC] Quit", pos=(20, 740))
nav2.fill_color = mcrfpy.Color(120, 120, 150)
scene2.children.append(nav2)


# ======================================================================
# SCREEN 3: Side-by-Side Comparison
# ======================================================================
print("\nSetting up Screen 3: Side-by-Side...")
scene3 = mcrfpy.Scene("tiled_compare")

bg3 = mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(10, 10, 15))
scene3.children.append(bg3)

title3 = mcrfpy.Caption(text="Premade vs Procedural", pos=(20, 10))
title3.fill_color = mcrfpy.Color(255, 255, 255)
scene3.children.append(title3)

# Left: Premade map (samplemap2, 30x30)
tm2 = mcrfpy.TileMapFile(PUNY_BASE + "/Tiled/samplemap2.tmj")
print(f"  Map2: {tm2.width}x{tm2.height}, layers: {tm2.tile_layer_names}")

left_label = mcrfpy.Caption(text="Premade (samplemap2)", pos=(20, 38))
left_label.fill_color = mcrfpy.Color(180, 220, 255)
scene3.children.append(left_label)

grid_left = mcrfpy.Grid(grid_size=(tm2.width, tm2.height),
                         pos=(20, 60), size=(380, 380), layers=[])
grid_left.fill_color = mcrfpy.Color(30, 30, 50)

for i, name in enumerate(tm2.tile_layer_names):
    z = -(len(tm2.tile_layer_names) - i)
    layer = mcrfpy.TileLayer(name=name, z_index=z, texture=texture)
    grid_left.add_layer(layer)
    tm2.apply_to_tile_layer(layer, name, tileset_index=0)

grid_left.center = (tm2.width * tileset.tile_width // 2,
                     tm2.height * tileset.tile_height // 2)
scene3.children.append(grid_left)

# Right: Procgen island
right_label = mcrfpy.Caption(text="Procgen Island (2-layer Wang)", pos=(420, 38))
right_label.fill_color = mcrfpy.Color(180, 255, 220)
scene3.children.append(right_label)

IW, IH = 30, 30
island_noise = mcrfpy.NoiseSource(dimensions=2, seed=7777)
island_hm = island_noise.sample(size=(IW, IH), mode="fbm", octaves=3, world_size=(3.0, 3.0))
island_hm.normalize(0.0, 1.0)

# Create island shape: attenuate edges with radial gradient
for y in range(IH):
    for x in range(IW):
        dx = (x - IW / 2.0) / (IW / 2.0)
        dy = (y - IH / 2.0) / (IH / 2.0)
        dist = (dx * dx + dy * dy) ** 0.5
        falloff = max(0.0, 1.0 - dist * 1.2)
        h = island_hm.get(x, y) * falloff
        island_hm[x, y] = h

island_hm.normalize(0.0, 1.0)

# Iterative base terrain expansion (same technique as Screen 2)
island_passes_def = [
    (0.40, T.SEAWATER_LIGHT, T.SAND),
    (0.25, T.SEAWATER_LIGHT, T.SEAWATER_MEDIUM, T.SAND, T.GRASS),
    (0.15, T.SEAWATER_MEDIUM, T.SEAWATER_DEEP, T.GRASS, T.CLIFF),
]
island_base_dm, island_stats = iterative_terrain(
    island_hm, overworld_ws, IW, IH, island_passes_def)
island_base_dm.enum_type = T
print(f"  Island base stats: {island_stats}")

# Tree overlay with separate noise
island_tree_noise = mcrfpy.NoiseSource(dimensions=2, seed=8888)
island_tree_hm = island_tree_noise.sample(
    size=(IW, IH), mode="fbm", octaves=3, world_size=(4.0, 4.0))
island_tree_hm.normalize(0.0, 1.0)

island_overlay_dm = mcrfpy.DiscreteMap((IW, IH))
island_overlay_dm.enum_type = T
for y in range(IH):
    for x in range(IW):
        base_val = island_base_dm.get(x, y)
        tree_h = island_tree_hm.get(x, y)
        if base_val == int(T.GRASS) and tree_h > 0.50:
            island_overlay_dm.set(x, y, int(T.TREES))
        else:
            island_overlay_dm.set(x, y, int(T.AIR))

# Validate overlay
island_ov_results = overworld_ws.resolve(island_overlay_dm)
for y in range(IH):
    for x in range(IW):
        if island_ov_results[y * IW + x] == -1:
            island_overlay_dm.set(x, y, int(T.AIR))

grid_right = mcrfpy.Grid(grid_size=(IW, IH),
                          pos=(420, 60), size=(380, 380), layers=[])
grid_right.fill_color = mcrfpy.Color(30, 30, 50)

island_base_layer = mcrfpy.TileLayer(name="island_base", z_index=-2, texture=texture)
grid_right.add_layer(island_base_layer)
overworld_ws.apply(island_base_dm, island_base_layer)

island_overlay_layer = mcrfpy.TileLayer(name="island_trees", z_index=-1, texture=texture)
grid_right.add_layer(island_overlay_layer)
overworld_ws.apply(island_overlay_dm, island_overlay_layer)

# Post-process: make AIR cells transparent
for y in range(IH):
    for x in range(IW):
        if island_overlay_dm.get(x, y) == int(T.AIR):
            island_overlay_layer.set((x, y), -1)

grid_right.center = (IW * tileset.tile_width // 2, IH * tileset.tile_height // 2)
scene3.children.append(grid_right)

# Info for both
make_info_panel(scene3, [
    "Left: Premade Map",
    f"  samplemap2.tmj",
    f"  {tm2.width}x{tm2.height}, {len(tm2.tile_layer_names)} layers",
    "",
    "Right: Procgen Island",
    f"  {IW}x{IH}, seed=7777",
    "  Iterative terrain expansion",
    "  2-layer Wang auto-tile",
    "",
    "Same tileset, same engine",
    "Different workflows",
], x=200, y=460, w=400, h=None)

nav3 = mcrfpy.Caption(text="[1] Premade  [2] Procgen  [3] Side-by-Side  [ESC] Quit", pos=(20, 740))
nav3.fill_color = mcrfpy.Color(120, 120, 150)
scene3.children.append(nav3)


# ======================================================================
# Navigation & Screenshots
# ======================================================================
scenes = [scene1, scene2, scene3]
scene_names = ["premade", "procgen", "compare"]

# Keyboard navigation (all scenes share the same handler)
def on_key(key, action):
    if action != mcrfpy.InputState.PRESSED:
        return
    if key == mcrfpy.Key.NUM_1:
        mcrfpy.current_scene = scene1
    elif key == mcrfpy.Key.NUM_2:
        mcrfpy.current_scene = scene2
    elif key == mcrfpy.Key.NUM_3:
        mcrfpy.current_scene = scene3
    elif key == mcrfpy.Key.ESCAPE:
        mcrfpy.exit()

for s in scenes:
    s.on_key = on_key

# Detect headless mode and take screenshots synchronously
is_headless = False
try:
    win = mcrfpy.Window.get()
    is_headless = "headless" in str(win).lower()
except:
    is_headless = True

if is_headless:
    # Headless: use step() to advance simulation and take screenshots directly
    for i, (sc, name) in enumerate(zip(scenes, scene_names)):
        mcrfpy.current_scene = sc
        # Step a few frames to let the scene render
        for _ in range(3):
            mcrfpy.step(0.016)
        fname = f"tiled_demo_{name}.png"
        automation.screenshot(fname)
        print(f"  Screenshot: {fname}")
    print("\nAll screenshots captured. Done!")
    sys.exit(0)
else:
    # Interactive: start on screen 1
    mcrfpy.current_scene = scene1
    print("\nTiled Demo ready!")
    print("Press [1] [2] [3] to switch screens, [ESC] to quit")
