/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
import { getSpriteRows } from "../graphics/rowEncoding";
import { Application, Container, Sprite, Texture } from "pixi.js";
import {
  applyPixelArtTexture,
  buildSpriteCanvasSurface,
  buildTileCanvasSurface,
  createSolidCanvas,
  destroyCachedTexture,
} from "./pixiSurfaceHelpers";
import {
  buildChunkSignature,
  redrawChunkEntrySurface,
} from "./pixiChunkSurfaceHelpers";
import {
  rebuildHeightOverlay,
  resetSceneRoots,
  syncHeightFocusEffect,
} from "./pixiHeightOverlayHelpers";
import {
  rebuildSceneFromRoomLayers,
  rebuildSceneFromWorldChunks,
} from "./pixiSceneRebuildHelpers";

function buildLayerChunkKey(layerNumber, chunkX, chunkY) {
  return `${Number(layerNumber) || 0}:${Number(chunkX) || 0}:${Number(chunkY) || 0}`;
}

export function createPixiTileStageController(scope, options) {
  const tileImages = options?.tileImages || {};
  const recordMetric = typeof options?.recordMetric === "function" ? options.recordMetric : null;
  const state = {
    app: null,
    hostEl: null,
    viewportLayerEl: null,
    worldContainer: null,
    baseWorldRoot: null,
    backgroundSprite: null,
    heightOverlayRoot: null,
    heightFocusBlurFilter: null,
    heightFocusBlurStrength: 0,
    heightFocusCacheResolution: 1,
    heightFocusCacheEnabled: false,
    heightFocusCacheDirty: true,
    ready: false,
    failed: false,
    initPromise: null,
    dirty: true,
    chunkSize: 32,
    tileSurfaceCache: new Map(),
    textureCache: new Map(),
    npcTextureCache: new Map(),
    emptyNpcTexture: null,
    layerRoots: new Map(),
    entityLayerRoots: new Map(),
    chunkEntries: new Map(),
    npcSpritesById: new Map(),
    heightPatchEntries: new Map(),
    lastHeightLayersRef: null,
    lastHeightOverlayMode: "",
    lastHeightActiveId: "",
    lastHeightTileSize: 0,
  };

  function getEmptyNpcTexture() {
    if (state.emptyNpcTexture && !state.emptyNpcTexture.destroyed) {
      return state.emptyNpcTexture;
    }
    const texture = Texture.from(createSolidCanvas(1, 1, "#00000000"), true);
    applyPixelArtTexture(texture);
    state.emptyNpcTexture = texture;
    return texture;
  }

  function isReady() {
    return state.ready && !!state.app && !!state.worldContainer;
  }

  function ensureHost() {
    if (state.hostEl && state.hostEl.isConnected) {
      return state.hostEl;
    }
    const viewportLayerEl = scope.pixiHost?.parentElement || scope.canvas?.parentElement || null;
    state.viewportLayerEl = viewportLayerEl;
    if (!viewportLayerEl) {
      return null;
    }
    let hostEl = scope.pixiHost;
    if (!hostEl) {
      hostEl = document.createElement("div");
      hostEl.id = "pixiHost";
      hostEl.className = "pixi-host";
      viewportLayerEl.insertBefore(hostEl, scope.canvas || null);
    }
    hostEl.setAttribute("aria-hidden", "true");
    state.hostEl = hostEl;
    return hostEl;
  }

  async function initialize() {
    if (state.failed) {
      return false;
    }
    if (state.initPromise) {
      return state.initPromise;
    }
    state.initPromise = (async () => {
      const hostEl = ensureHost();
      if (!hostEl) {
        state.failed = true;
        return false;
      }
      const app = new Application();
      await app.init({
        width: Math.max(1, Number(scope.viewport?.clientWidth) || 1),
        height: Math.max(1, Number(scope.viewport?.clientHeight) || 1),
        preference: "webgl",
        antialias: false,
        backgroundAlpha: 0,
        clearBeforeRender: true,
        autoStart: false,
        sharedTicker: false,
        autoDensity: true,
        resolution: Math.max(1, Number(window.devicePixelRatio) || 1),
        roundPixels: true,
      });
      const pixiCanvas = app.canvas;
      pixiCanvas.classList.add("pixi-stage-canvas");
      pixiCanvas.style.position = "absolute";
      pixiCanvas.style.inset = "0";
      pixiCanvas.style.width = "100%";
      pixiCanvas.style.height = "100%";
      pixiCanvas.style.pointerEvents = "none";
      pixiCanvas.style.imageRendering = "pixelated";
      hostEl.replaceChildren(pixiCanvas);

      const worldContainer = new Container();
      worldContainer.sortableChildren = true;
      worldContainer.roundPixels = true;
      app.stage.sortableChildren = true;
      app.stage.roundPixels = true;
      app.stage.addChild(worldContainer);

      const baseWorldRoot = new Container();
      baseWorldRoot.label = "base_world_root";
      baseWorldRoot.zIndex = 0;
      baseWorldRoot.sortableChildren = true;
      baseWorldRoot.roundPixels = true;
      worldContainer.addChild(baseWorldRoot);

      const backgroundSprite = new Sprite(Texture.WHITE);
      backgroundSprite.zIndex = -1;
      backgroundSprite.roundPixels = true;
      baseWorldRoot.addChild(backgroundSprite);

      const heightOverlayRoot = new Container();
      heightOverlayRoot.label = "height_overlay_root";
      heightOverlayRoot.zIndex = 100000;
      worldContainer.addChild(heightOverlayRoot);

      state.app = app;
      state.worldContainer = worldContainer;
      state.baseWorldRoot = baseWorldRoot;
      state.backgroundSprite = backgroundSprite;
      state.heightOverlayRoot = heightOverlayRoot;
      state.ready = true;
      state.dirty = true;
      state.heightFocusCacheDirty = true;
      return true;
    })().catch((error) => {
      state.failed = true;
      console.error("Pixi tile stage failed to initialize.", error);
      return false;
    });
    return state.initPromise;
  }

  function getTileTexture(symbol) {
    const safeSymbol = String(symbol || "").charAt(0);
    if (!safeSymbol) {
      return Texture.WHITE;
    }
    if (state.textureCache.has(safeSymbol)) {
      return state.textureCache.get(safeSymbol);
    }
    const texture = Texture.from(getTileSurface(safeSymbol), true);
    applyPixelArtTexture(texture);
    state.textureCache.set(safeSymbol, texture);
    return texture;
  }

  function getTileSurface(symbol) {
    const safeSymbol = String(symbol || "").charAt(0);
    if (!safeSymbol) {
      return createSolidCanvas(1, 1, "#00000000");
    }
    if (state.tileSurfaceCache.has(safeSymbol)) {
      return state.tileSurfaceCache.get(safeSymbol);
    }
    const tileEntry = scope.getTileEntry(safeSymbol) || {};
    const img = tileImages[safeSymbol];
    let surface;
    if (getSpriteRows(tileEntry).length > 0) {
      surface = buildTileCanvasSurface(tileEntry, tileEntry.color || scope.defaultTileColor || "#7AA7FF");
    } else if (img && img.complete && img.naturalWidth > 0) {
      surface = img;
    } else {
      surface = createSolidCanvas(1, 1, String(tileEntry.color || scope.defaultTileColor || "#7AA7FF"));
    }
    state.tileSurfaceCache.set(safeSymbol, surface);
    return surface;
  }

  function getTileOpacity(symbol) {
    const safeSymbol = String(symbol || "").charAt(0);
    if (!safeSymbol) {
      return 1;
    }
    const tileEntry = scope.getTileEntry(safeSymbol) || null;
    const opacity = Number(tileEntry?.opacity);
    return Number.isFinite(opacity) ? Math.max(0, Math.min(1, opacity)) : 1;
  }

  function invalidateTileTexture(symbol) {
    const safeSymbol = String(symbol || "").charAt(0);
    if (!safeSymbol) {
      return false;
    }
    state.tileSurfaceCache.delete(safeSymbol);
    destroyCachedTexture(state.textureCache, safeSymbol);
    state.dirty = true;
    return true;
  }

  function invalidateAllGraphicsCaches() {
    state.tileSurfaceCache.clear();
    state.textureCache.forEach((texture) => {
      try {
        texture?.destroy(true);
      } catch {
        // Ignore stale texture cleanup issues and let Pixi rebuild on demand.
      }
    });
    state.textureCache.clear();
    state.npcTextureCache.forEach((texture) => {
      try {
        texture?.destroy(true);
      } catch {
        // Ignore stale texture cleanup issues and let Pixi rebuild on demand.
      }
    });
    state.npcTextureCache.clear();
    state.chunkEntries.forEach((entry) => {
      if (entry && typeof entry === "object") {
        entry.signature = "";
      }
    });
    state.heightFocusCacheDirty = true;
  }

  function getNpcTexture(npc) {
    const cacheKey = String(npc?.dataUrl || npc?.spriteId || npc?.id || "").trim();
    const spritePayload = scope.documentScope?.ensureDocumentContentPayload?.("sprites", { schemaVersion: 1, sprites: [] }) || { schemaVersion: 1, sprites: [] };
    const spriteRecords = Array.isArray(spritePayload?.sprites)
      ? spritePayload.sprites
      : [];
    const spriteRecord = spriteRecords.find((entry) => String(entry?.id || "").trim() === String(npc?.spriteId || "").trim()) || null;
    const spriteCanvas = spriteRecord ? buildSpriteCanvasSurface(spriteRecord) : null;
    const hasSpriteDataUrl = !!(npc?.dataUrl && String(npc.dataUrl || "").trim());
    if (!cacheKey || (!spriteCanvas && !hasSpriteDataUrl)) {
      return getEmptyNpcTexture();
    }
    if (state.npcTextureCache.has(cacheKey)) {
      const cachedTexture = state.npcTextureCache.get(cacheKey);
      if (cachedTexture && !cachedTexture.destroyed) {
        return cachedTexture;
      }
      state.npcTextureCache.delete(cacheKey);
    }
    const img = npc?.dataUrl && typeof scope.getCachedImage === "function"
      ? scope.getCachedImage(`__pixi_npc__:${cacheKey}`, String(npc.dataUrl || ""))
      : null;
    if (img && !img.complete) {
      img.addEventListener("load", () => {
        state.npcTextureCache.delete(cacheKey);
        state.dirty = true;
        scope.draw?.();
      }, { once: true });
    }
    let texture;
    try {
      texture = spriteCanvas
        ? Texture.from(spriteCanvas, true)
        : (img && img.complete && img.naturalWidth > 0
          ? Texture.from(img, true)
          : (hasSpriteDataUrl ? Texture.from(String(npc.dataUrl || ""), true) : getEmptyNpcTexture()));
    } catch {
      return getEmptyNpcTexture();
    }
    if (!texture || texture.destroyed) {
      return getEmptyNpcTexture();
    }
    applyPixelArtTexture(texture);
    try {
      texture?.source?.on?.("update", () => {
        scope.draw?.();
      });
    } catch {
      return getEmptyNpcTexture();
    }
    state.npcTextureCache.set(cacheKey, texture);
    return texture;
  }

  function resolveStoredTileSymbol(layerObj, tileX, tileY) {
    const layerNumber = Number(layerObj?.layer) || 0;
    const fillChar = layerNumber === 0 ? "." : " ";
    const rows = Array.isArray(layerObj?.rows) ? layerObj.rows : [];
    const row = String(rows[tileY] || "");
    let symbol = row.charAt(tileX) || fillChar;
    if (layerNumber === 0 && symbol === "." && scope.backgroundTileId) {
      symbol = scope.getBackgroundTileSymbol() || ".";
    }
    if (layerNumber === 0 && symbol === " ") {
      return "";
    }
    if (layerNumber > 0 && symbol === " ") {
      return "";
    }
    if (symbol === ".") {
      return "";
    }
    return String(symbol || "").charAt(0);
  }

  function isWorldChunkModeActive() {
    const visibleChunks = typeof scope.getVisibleWorldChunkPayloads === "function"
      ? scope.getVisibleWorldChunkPayloads()
      : [];
    return !!scope.isWorldModeActive?.() && visibleChunks.length > 0;
  }

  function getWorldChunkRenderContext() {
    const visibleChunks = typeof scope.getVisibleWorldChunkPayloads === "function"
      ? scope.getVisibleWorldChunkPayloads()
      : [];
    if (!scope.isWorldModeActive?.() || visibleChunks.length <= 0) {
      return null;
    }
    return {
      chunks: visibleChunks,
      chunkWidth: Math.max(1, Number(scope.worldChunkWidth) || state.chunkSize),
      chunkHeight: Math.max(1, Number(scope.worldChunkHeight) || state.chunkSize),
      originChunkX: Math.floor(Number(scope.worldOriginChunkX) || 0),
      originChunkY: Math.floor(Number(scope.worldOriginChunkY) || 0),
      tileOffsetX: Math.floor(Number(scope.worldTileOffsetX) || 0),
      tileOffsetY: Math.floor(Number(scope.worldTileOffsetY) || 0),
    };
  }

  function resolveWorldChunkLayerSymbol(chunk, layerObj, tileX, tileY) {
    const layerNumber = Number(layerObj?.layer) || 0;
    const fillChar = layerNumber === 0 ? "." : " ";
    const rows = Array.isArray(layerObj?.rows) ? layerObj.rows : [];
    const row = String(rows[tileY] || "");
    let symbol = row.charAt(tileX) || fillChar;
    const backgroundTileId = String(chunk?.backgroundTileId || scope.backgroundTileId || "").trim();
    if (layerNumber === 0 && symbol === "." && backgroundTileId) {
      const backgroundTileEntry = scope.getBackgroundTileEntry?.() || null;
      const scopedBackgroundTileId = String(backgroundTileEntry?.id || "").trim();
      if (scopedBackgroundTileId && scopedBackgroundTileId === backgroundTileId) {
        symbol = scope.getBackgroundTileSymbol() || ".";
      } else {
        const fallbackEntry = scope.getTileEntryById?.(backgroundTileId) || null;
        symbol = String(fallbackEntry?.symbol || ".").charAt(0) || ".";
      }
    }
    if (layerNumber === 0 && symbol === " ") {
      return "";
    }
    if (layerNumber > 0 && symbol === " ") {
      return "";
    }
    if (symbol === ".") {
      return "";
    }
    return String(symbol || "").charAt(0);
  }

  function getOrCreateLayerRoot(layerNumber) {
    const safeLayerNumber = Number(layerNumber) || 0;
    let layerRoot = state.layerRoots.get(safeLayerNumber);
    if (layerRoot) {
      if (layerRoot.parent !== state.baseWorldRoot) {
        state.baseWorldRoot?.addChild(layerRoot);
      }
      return layerRoot;
    }
    layerRoot = new Container();
    layerRoot.label = `layer_${safeLayerNumber}`;
    layerRoot.zIndex = safeLayerNumber;
    layerRoot.sortableChildren = true;
    layerRoot.roundPixels = true;
    state.baseWorldRoot?.addChild(layerRoot);
    state.layerRoots.set(safeLayerNumber, layerRoot);
    return layerRoot;
  }

  function getOrCreateEntityLayerRoot(layerNumber) {
    const safeLayerNumber = Number(layerNumber) || 0;
    let entityRoot = state.entityLayerRoots.get(safeLayerNumber);
    if (entityRoot) {
      if (entityRoot.parent !== getOrCreateLayerRoot(safeLayerNumber)) {
        getOrCreateLayerRoot(safeLayerNumber).addChild(entityRoot);
      }
      return entityRoot;
    }
    entityRoot = new Container();
    entityRoot.label = `layer_${safeLayerNumber}_entities`;
    entityRoot.zIndex = 1;
    entityRoot.sortableChildren = true;
    entityRoot.roundPixels = true;
    getOrCreateLayerRoot(safeLayerNumber).addChild(entityRoot);
    state.entityLayerRoots.set(safeLayerNumber, entityRoot);
    return entityRoot;
  }

  function getChunkPixelSize(tileWidth, tileHeight) {
    const pixelTileSize = Math.max(1, Number(scope.baseTileSize) || Number(scope.tileSize) || 32);
    return {
      pixelTileSize,
      pixelWidth: Math.max(1, Math.max(1, Number(tileWidth) || state.chunkSize) * pixelTileSize),
      pixelHeight: Math.max(1, Math.max(1, Number(tileHeight) || state.chunkSize) * pixelTileSize),
    };
  }

  function rebuildChunkEntryTexture(entry) {
    const nextTexture = Texture.from(entry.canvas, true);
    applyPixelArtTexture(nextTexture);
    const previousTexture = entry.texture || null;
    entry.texture = nextTexture;
    if (entry.sprite) {
      entry.sprite.texture = nextTexture;
    }
    if (previousTexture && previousTexture !== nextTexture) {
      try {
        previousTexture.destroy(true);
      } catch {
        // Ignore stale chunk texture replacement cleanup issues.
      }
    }
  }

  function syncChunkEntryDimensions(entry) {
    const { pixelWidth, pixelHeight } = getChunkPixelSize(entry.tileWidth, entry.tileHeight);
    if (entry.canvas.width !== pixelWidth || entry.canvas.height !== pixelHeight) {
      entry.canvas.width = pixelWidth;
      entry.canvas.height = pixelHeight;
      entry.ctx.imageSmoothingEnabled = false;
      rebuildChunkEntryTexture(entry);
    }
    entry.sprite.x = Number(entry.tileX) || 0;
    entry.sprite.y = Number(entry.tileY) || 0;
    entry.sprite.width = Math.max(1, Number(entry.tileWidth) || state.chunkSize);
    entry.sprite.height = Math.max(1, Number(entry.tileHeight) || state.chunkSize);
    entry.sprite.roundPixels = true;
  }

  function createChunkEntry(layerNumber, chunkX, chunkY, options) {
    const layerRoot = getOrCreateLayerRoot(layerNumber);
    const tileWidth = Math.max(1, Number(options?.tileWidth) || state.chunkSize);
    const tileHeight = Math.max(1, Number(options?.tileHeight) || state.chunkSize);
    const { pixelWidth, pixelHeight } = getChunkPixelSize(tileWidth, tileHeight);
    const canvas = document.createElement("canvas");
    canvas.width = pixelWidth;
    canvas.height = pixelHeight;
    const ctx = canvas.getContext("2d");
    if (!ctx) {
      throw new Error("Failed to create chunk surface canvas.");
    }
    ctx.imageSmoothingEnabled = false;
    const texture = Texture.from(canvas, true);
    applyPixelArtTexture(texture);
    const chunkSprite = new Sprite(texture);
    chunkSprite.label = `chunk_${layerNumber}_${chunkX}_${chunkY}`;
    chunkSprite.zIndex = 0;
    chunkSprite.roundPixels = true;
    layerRoot.addChild(chunkSprite);
    const entry = {
      layerNumber,
      chunkX,
      chunkY,
      tileX: Number(options?.tileX) || 0,
      tileY: Number(options?.tileY) || 0,
      tileWidth,
      tileHeight,
      signature: "",
      canvas,
      ctx,
      texture,
      sprite: chunkSprite,
      lastRenderedAt: 0,
    };
    syncChunkEntryDimensions(entry);
    state.chunkEntries.set(buildLayerChunkKey(layerNumber, chunkX, chunkY), entry);
    return entry;
  }

  function getOrCreateChunkEntry(layerNumber, chunkX, chunkY, options) {
    const key = buildLayerChunkKey(layerNumber, chunkX, chunkY);
    return state.chunkEntries.get(key) || createChunkEntry(layerNumber, chunkX, chunkY, options);
  }

  function destroyChunkEntry(entry) {
    if (!entry) {
      return;
    }
    state.chunkEntries.delete(buildLayerChunkKey(entry.layerNumber, entry.chunkX, entry.chunkY));
    entry.sprite?.removeFromParent();
    entry.sprite?.destroy();
    try {
      entry.texture?.destroy(true);
    } catch {
      // Ignore stale chunk texture cleanup issues during removal.
    }
    if (entry.canvas) {
      entry.canvas.width = 1;
      entry.canvas.height = 1;
    }
  }

  function syncChunkVisibility() {
    if (!isReady()) {
      return;
    }
    const viewportLeftTiles = (Number(scope.viewport?.scrollLeft) || 0) / Math.max(1, Number(scope.tileSize) || 1);
    const viewportTopTiles = (Number(scope.viewport?.scrollTop) || 0) / Math.max(1, Number(scope.tileSize) || 1);
    const viewportWidthTiles = (Number(scope.viewport?.clientWidth) || 0) / Math.max(1, Number(scope.tileSize) || 1);
    const viewportHeightTiles = (Number(scope.viewport?.clientHeight) || 0) / Math.max(1, Number(scope.tileSize) || 1);
    const minChunkX = Math.floor(viewportLeftTiles / state.chunkSize) - 1;
    const maxChunkX = Math.ceil((viewportLeftTiles + viewportWidthTiles) / state.chunkSize) + 1;
    const minChunkY = Math.floor(viewportTopTiles / state.chunkSize) - 1;
    const maxChunkY = Math.ceil((viewportTopTiles + viewportHeightTiles) / state.chunkSize) + 1;
    state.chunkEntries.forEach((entry) => {
      const layerRoot = state.layerRoots.get(entry.layerNumber);
      const entryLeft = Number(entry.tileX) || 0;
      const entryTop = Number(entry.tileY) || 0;
      const entryRight = entryLeft + Math.max(1, Number(entry.tileWidth) || state.chunkSize);
      const entryBottom = entryTop + Math.max(1, Number(entry.tileHeight) || state.chunkSize);
      const inViewport = entryRight > minChunkX * state.chunkSize
        && entryLeft < (maxChunkX + 1) * state.chunkSize
        && entryBottom > minChunkY * state.chunkSize
        && entryTop < (maxChunkY + 1) * state.chunkSize;
      entry.sprite.visible = inViewport && !!layerRoot?.visible;
    });
  }

  function destroyHeightPatchEntry(entry) {
    if (!entry) {
      return;
    }
    state.heightPatchEntries.delete(String(entry.id || "").trim());
    entry.container.removeFromParent();
    entry.container.destroy({ children: true });
  }

  function syncNpcLayer() {
    if (!isReady()) {
      return;
    }
    const visibleNpcSource = scope.getVisibleNpcOverlays?.();
    const visibleNpcs = Array.isArray(visibleNpcSource) ? visibleNpcSource : [];
    const visibleIds = new Set(visibleNpcs.map((npc) => String(npc?.id || "").trim()).filter(Boolean));
    Array.from(state.npcSpritesById.keys()).forEach((id) => {
      if (!visibleIds.has(id)) {
        const sprite = state.npcSpritesById.get(id);
        sprite?.removeFromParent();
        sprite?.destroy();
        state.npcSpritesById.delete(id);
      }
    });

    visibleNpcs.forEach((npc) => {
      const npcId = String(npc?.id || "").trim();
      if (!npcId) {
        return;
      }
      const layerNumber = Number(npc.layer) || 0;
      const entityLayerRoot = getOrCreateEntityLayerRoot(layerNumber);
      let sprite = state.npcSpritesById.get(npcId) || null;
      if (sprite?.destroyed) {
        state.npcSpritesById.delete(npcId);
        sprite = null;
      }
      if (!sprite) {
        sprite = new Sprite(getNpcTexture(npc));
        sprite.label = `npc_${npcId}`;
        sprite.zIndex = 1;
        entityLayerRoot.addChild(sprite);
        state.npcSpritesById.set(npcId, sprite);
      } else {
        if (sprite.parent !== entityLayerRoot) {
          entityLayerRoot.addChild(sprite);
        }
      }
      sprite.texture = getNpcTexture(npc);
      sprite.x = Number(npc.x) || 0;
      sprite.y = Number(npc.y) || 0;
      sprite.width = Math.max(0.25, (Number(npc.spriteWidth) || Number(scope.baseTileSize) || 32) / Math.max(1, Number(scope.baseTileSize) || 32));
      sprite.height = Math.max(0.25, (Number(npc.spriteHeight) || Number(scope.baseTileSize) || 32) / Math.max(1, Number(scope.baseTileSize) || 32));
      sprite.zIndex = (Number(npc.y) || 0) + (sprite.height || 0);
      sprite.alpha = Number.isFinite(Number(npc.opacity)) ? Math.max(0, Math.min(1, Number(npc.opacity))) : 1;
      sprite.visible = true;
      sprite.tint = 0xFFFFFF;
      sprite.roundPixels = true;
      const draggingNpc = scope.draggingNpc;
      if (draggingNpc) {
        const draggingOverlay = scope.npcOverlays[Number(draggingNpc.index) || 0];
        if (draggingOverlay && String(draggingOverlay.id || "").trim() === npcId) {
          sprite.visible = false;
        }
      }
    });
  }

  function rebuildScene() {
    if (!isReady()) {
      return false;
    }
    const rebuildStartedAt = performance.now();
    state.dirty = false;
    if (isWorldChunkModeActive()) {
      rebuildSceneFromWorldChunks(state, scope, {
        buildLayerChunkKey,
        createChunkEntry,
        destroyChunkEntry,
        getOrCreateLayerRoot,
        getTileOpacity,
        getWorldChunkRenderContext,
        getTileSurface,
        resetSceneRoots: () => resetSceneRoots(state, scope),
        resolveWorldChunkLayerSymbol,
        syncChunkEntryDimensions,
      });
    } else {
      rebuildSceneFromRoomLayers(state, scope, {
        destroyChunkEntry,
        getOrCreateChunkEntry,
        getOrCreateLayerRoot,
        getTileOpacity,
        getTileSurface,
        resetSceneRoots: () => resetSceneRoots(state, scope),
        resolveStoredTileSymbol,
        syncChunkEntryDimensions,
      });
    }
    syncChunkVisibility();
    state.lastHeightLayersRef = null;
    rebuildHeightOverlay(state, scope, {
      Container,
      destroyHeightPatchEntry,
      getTileTexture,
      isReady,
    });
    syncNpcLayer();
    state.heightFocusCacheDirty = true;
    syncHeightFocusEffect(state, scope);
    if (recordMetric) {
      recordMetric("tileSurfaceRefresh", performance.now() - rebuildStartedAt);
    }
    return true;
  }

  function syncRendererCanvasSize() {
    if (!isReady()) {
      return false;
    }
    const nextWidth = Math.max(1, Math.ceil(Number(scope.viewport?.clientWidth) || 0));
    const nextHeight = Math.max(1, Math.ceil(Number(scope.viewport?.clientHeight) || 0));
    if (state.app.renderer.width !== nextWidth || state.app.renderer.height !== nextHeight) {
      state.app.renderer.resize(nextWidth, nextHeight);
    }
    return true;
  }

  function syncStageTransform() {
    if (!isReady()) {
      return false;
    }
    syncRendererCanvasSize();
    state.worldContainer.scale.set(Math.max(1, Number(scope.tileSize) || 1));
    state.worldContainer.position.set(
      -Math.round(Number(scope.viewport?.scrollLeft) || 0),
      -Math.round(Number(scope.viewport?.scrollTop) || 0),
    );
    state.layerRoots.forEach((layerRoot, layerNumber) => {
      layerRoot.visible = scope.isLayerRendered(Number(layerNumber) || 0);
    });
    syncChunkVisibility();
    rebuildHeightOverlay(state, scope, {
      Container,
      destroyHeightPatchEntry,
      getTileTexture,
      isReady,
    });
    syncNpcLayer();
    syncHeightFocusEffect(state, scope);
    return true;
  }

  function render(forceRebuild) {
    if (!isReady()) {
      return false;
    }
    if (forceRebuild || state.dirty) {
      rebuildScene();
    }
    syncStageTransform();
    state.app.render();
    return true;
  }

  function patchTileAt(tileX, tileY) {
    const normalizedTileX = Math.floor(Number(tileX) || 0);
    const normalizedTileY = Math.floor(Number(tileY) || 0);
    if (!isReady()) {
      return false;
    }
    if (normalizedTileX < 0 || normalizedTileY < 0 || normalizedTileX >= scope.width || normalizedTileY >= scope.height) {
      return false;
    }
    const patchStartedAt = performance.now();
    const localChunkX = Math.floor(normalizedTileX / state.chunkSize);
    const localChunkY = Math.floor(normalizedTileY / state.chunkSize);
    let touched = false;

    if (isWorldChunkModeActive()) {
      const worldChunkX = Math.floor(Number(scope.worldOriginChunkX) || 0) + localChunkX;
      const worldChunkY = Math.floor(Number(scope.worldOriginChunkY) || 0) + localChunkY;
      const visibleChunks = Array.isArray(scope.getVisibleWorldChunkPayloads?.()) ? scope.getVisibleWorldChunkPayloads() : [];
      const targetChunk = visibleChunks.find((entry) => (
        Math.floor(Number(entry?.chunkX) || 0) === worldChunkX
        && Math.floor(Number(entry?.chunkY) || 0) === worldChunkY
      )) || null;
      if (!targetChunk) {
        state.dirty = true;
      } else {
        const roomLayers = Array.isArray(targetChunk.roomLayers) ? targetChunk.roomLayers : [];
        roomLayers.forEach((layerObj) => {
          const layerNumber = Number(layerObj?.layer) || 0;
          const chunkKey = buildLayerChunkKey(layerNumber, worldChunkX, worldChunkY);
          let chunkEntry = state.chunkEntries.get(chunkKey) || null;
          if (!chunkEntry) {
            chunkEntry = getOrCreateChunkEntry(layerNumber, worldChunkX, worldChunkY, {
              tileX: (worldChunkX - Math.floor(Number(scope.worldOriginChunkX) || 0)) * Math.max(1, Number(scope.worldChunkWidth) || state.chunkSize),
              tileY: (worldChunkY - Math.floor(Number(scope.worldOriginChunkY) || 0)) * Math.max(1, Number(scope.worldChunkHeight) || state.chunkSize),
              tileWidth: Math.max(1, Number(scope.worldChunkWidth) || state.chunkSize),
              tileHeight: Math.max(1, Number(scope.worldChunkHeight) || state.chunkSize),
            });
          }
          const hasContent = redrawChunkEntrySurface(
            chunkEntry,
            (cellX, cellY) => resolveWorldChunkLayerSymbol(targetChunk, layerObj, cellX, cellY),
            {
              baseTileSize: scope.baseTileSize,
              getTileSurface,
              syncChunkEntryDimensions,
              tileSize: scope.tileSize,
            },
          );
          chunkEntry.signature = buildChunkSignature(
            (cellX, cellY) => resolveWorldChunkLayerSymbol(targetChunk, layerObj, cellX, cellY),
            chunkEntry.tileWidth,
            chunkEntry.tileHeight,
            String(targetChunk?.backgroundTileId || ""),
          );
          if (!hasContent && layerNumber > 0) {
            destroyChunkEntry(chunkEntry);
          }
          touched = true;
        });
        if (!roomLayers.some((layerObj) => Number(layerObj?.layer) === 0)) {
          state.dirty = true;
        }
      }
      if (touched || state.dirty) {
        state.heightFocusCacheDirty = true;
        syncStageTransform();
        state.app.render();
        if (recordMetric) {
          recordMetric("tileSurfacePatch", performance.now() - patchStartedAt);
        }
      }
      return touched || state.dirty;
    }

    (Array.isArray(scope.roomLayers) ? scope.roomLayers : []).forEach((layerObj) => {
      const layerNumber = Number(layerObj?.layer) || 0;
      const chunkEntryKey = buildLayerChunkKey(layerNumber, localChunkX, localChunkY);
      let chunkEntry = state.chunkEntries.get(chunkEntryKey) || null;
      if (!chunkEntry) {
        const baseTileX = localChunkX * state.chunkSize;
        const baseTileY = localChunkY * state.chunkSize;
        chunkEntry = getOrCreateChunkEntry(layerNumber, localChunkX, localChunkY, {
          tileX: baseTileX,
          tileY: baseTileY,
          tileWidth: Math.max(1, Math.min(state.chunkSize, Math.max(0, Number(scope.width) || 0) - baseTileX)),
          tileHeight: Math.max(1, Math.min(state.chunkSize, Math.max(0, Number(scope.height) || 0) - baseTileY)),
        });
      }
      const baseTileX = localChunkX * state.chunkSize;
      const baseTileY = localChunkY * state.chunkSize;
      const hasContent = redrawChunkEntrySurface(
        chunkEntry,
        (cellX, cellY) => resolveStoredTileSymbol(layerObj, baseTileX + cellX, baseTileY + cellY),
        {
          baseTileSize: scope.baseTileSize,
          getTileSurface,
          syncChunkEntryDimensions,
          tileSize: scope.tileSize,
        },
      );
      chunkEntry.signature = buildChunkSignature(
        (cellX, cellY) => resolveStoredTileSymbol(layerObj, baseTileX + cellX, baseTileY + cellY),
        chunkEntry.tileWidth,
        chunkEntry.tileHeight,
        String(layerNumber),
      );
      if (!hasContent) {
        destroyChunkEntry(chunkEntry);
      }
      touched = true;
    });

    if (!touched) {
      return false;
    }
    state.heightFocusCacheDirty = true;
    syncStageTransform();
    state.app.render();
    if (recordMetric) {
      recordMetric("tileSurfacePatch", performance.now() - patchStartedAt);
    }
    return true;
  }

  function invalidateAll(options) {
    const config = options && typeof options === "object" ? options : {};
    if (config.refreshTileImages === true || config.refreshGraphics === true) {
      invalidateAllGraphicsCaches();
    }
    state.dirty = true;
  }

  function getDebugSnapshot() {
    const visibleChunkCount = Array.from(state.chunkEntries.values()).reduce(
      (total, entry) => total + (entry?.sprite?.visible ? 1 : 0),
      0,
    );
    return {
      ready: state.ready,
      failed: state.failed,
      dirty: state.dirty,
      chunkSize: state.chunkSize,
      chunkCount: state.chunkEntries.size,
      visibleChunkCount,
      layerRootCount: state.layerRoots.size,
      textureCacheSize: state.textureCache.size,
      npcTextureCacheSize: state.npcTextureCache.size,
      npcSpriteCount: state.npcSpritesById.size,
      heightPatchCount: state.heightPatchEntries.size,
      rendererWidth: Number(state.app?.renderer?.width) || 0,
      rendererHeight: Number(state.app?.renderer?.height) || 0,
      rendererResolution: Number(state.app?.renderer?.resolution) || 0,
    };
  }

  function destroy() {
    state.heightPatchEntries.forEach((entry) => {
      destroyHeightPatchEntry(entry);
    });
    state.heightPatchEntries.clear();
    state.chunkEntries.forEach((entry) => {
      destroyChunkEntry(entry);
    });
    state.chunkEntries.clear();
    state.layerRoots.clear();
    state.entityLayerRoots.clear();
    state.npcSpritesById.forEach((sprite) => {
      sprite?.removeFromParent();
      sprite?.destroy();
    });
    state.npcSpritesById.clear();
    state.textureCache.forEach((texture) => {
      try {
        texture?.destroy(true);
      } catch {
        // Ignore stale texture cleanup issues during shutdown.
      }
    });
    state.textureCache.clear();
    state.tileSurfaceCache.clear();
    state.npcTextureCache.forEach((texture) => {
      try {
        texture?.destroy(true);
      } catch {
        // Ignore stale texture cleanup issues during shutdown.
      }
    });
    state.npcTextureCache.clear();
    if (state.emptyNpcTexture) {
      try {
        state.emptyNpcTexture.destroy(true);
      } catch {
        // Ignore stale texture cleanup issues during shutdown.
      }
      state.emptyNpcTexture = null;
    }
    if (state.app) {
      state.app.destroy(true, { children: true });
    }
    if (state.hostEl) {
      state.hostEl.replaceChildren();
    }
    state.app = null;
    state.worldContainer = null;
    state.baseWorldRoot = null;
    state.backgroundSprite = null;
    state.heightOverlayRoot = null;
    state.heightFocusCacheResolution = 1;
    state.heightFocusCacheEnabled = false;
    state.heightFocusCacheDirty = true;
    state.ready = false;
  }

  return {
    initialize,
    isReady,
    render,
    patchTileAt,
    invalidateTileTexture,
    invalidateAll,
    getDebugSnapshot,
    destroy,
  };
}
