/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck

import {
  buildSpritePreviewDataUrl,
  getSpritePalette,
} from "../editorCore";
import {
  buildSpritesPayloadFromImagesPayload,
  buildTilesPayloadFromImagesPayload,
  normalizeImagePlayback,
  normalizeImageRecordForSave,
} from "../contentTransforms/graphicsPayload";
import {
  normalizeEditorTagValue,
  normalizeEditorTags,
  parseImportedEditorTags,
  serializeEditorTags,
} from "./tagUtils";
import {
  confirmDiscardChanges,
  copyTextWithClipboardFallback,
  promptForImportText,
} from "./textTransferUtils";
import { clampFloatingWindowRect } from "./floatingWindowUtils";
import { appendContextMenuItems, menuItem, menuSubmenu, openContextMenuAtPoint } from "./contextMenuSchema";

const TILE_ART_WINDOW_KEY = "tileArtEditor";
const TILE_ART_SIZE = 16;
const GRID_CELL_SIZE = 21;
const MIN_WIDTH = 452;
const MIN_HEIGHT = 628;
const DEFAULT_WIDTH = 520;
const DEFAULT_HEIGHT = 676;
const LOCKED_WINDOW_WIDTH = DEFAULT_WIDTH;
const ANIMATION_PREVIEW_WIDTH = 252;
const ANIMATION_PREVIEW_HEIGHT = 288;
const TAGS_TAB_MIN_HEIGHT = 220;
const TOOL_MENU_TAG_PREFIX = "tile-art-tool-menu:";
const SHORTCUT_HELP_TOOLTIP_TAG = "tile-art-shortcut-help";
const ANIMATION_SPEED_TOOLTIP_TAG = "tile-art-animation-speed";
const ANIMATION_PLAYBACK_TOOLTIP_TAG = "tile-art-animation-playback";
const EYEDROPPER_CURSOR = `url("data:image/svg+xml,${encodeURIComponent(
  `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
    <path d="M20.8 4.2c1.6-1.6 4.3-1.6 5.9 0s1.6 4.3 0 5.9l-3.1 3.1-5.9-5.9 3.1-3.1z" fill="#eef6ff" stroke="#08111d" stroke-width="1.5"/>
    <path d="M10.8 14.1l6.9-6.9 6.1 6.1-6.9 6.9-2.7.9-.9 2.7-3 3a2.2 2.2 0 0 1-3.1 0l-1-1a2.2 2.2 0 0 1 0-3.1l3-3 2.7-.9.9-2.7z" fill="#7ee8c6" stroke="#08111d" stroke-width="1.5" stroke-linejoin="round"/>
    <circle cx="8.4" cy="23.6" r="2" fill="#ff5f6d"/>
  </svg>`,
)}") 4 28, crosshair`;

function cloneValue(value) {
  if (typeof structuredClone === "function") {
    return structuredClone(value);
  }
  return value == null ? value : JSON.parse(JSON.stringify(value));
}

function clampWindowRect(layerRect, left, top, width, height) {
  return clampFloatingWindowRect(layerRect, left, top, width, height, MIN_WIDTH, MIN_HEIGHT, DEFAULT_WIDTH, DEFAULT_HEIGHT);
}

function normalizeRoleList(value) {
  if (!Array.isArray(value)) {
    return [];
  }
  return Array.from(new Set(
    value
      .map((entry) => String(entry || "").trim().toLowerCase())
      .filter((entry) => entry === "tile" || entry === "sprite"),
  ));
}

function normalizeTimelineRows(rows) {
  return Array.from({ length: TILE_ART_SIZE }, (_entry, rowIndex) => {
    const row = Array.isArray(rows) ? String(rows[rowIndex] || "") : "";
    return row.padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE);
  });
}

function normalizeWorkingFrames(record) {
  const rawFrames = Array.isArray(record?.frames) ? record.frames.filter((entry) => entry && typeof entry === "object" && !Array.isArray(entry)) : [];
  const normalizedFrames = rawFrames.map((entry, index) => ({
    ...cloneValue(entry),
    id: String(entry.id || `frame_${index}`).trim() || `frame_${index}`,
    enabled: entry.enabled !== false,
    index: Number.isFinite(Number(entry.index)) ? Math.max(0, Math.floor(Number(entry.index))) : index,
    rows: normalizeTimelineRows(entry.rows),
  }));
  if (normalizedFrames.length > 0) {
    return normalizedFrames;
  }
  return [{
    id: "frame_0",
    enabled: true,
    index: 0,
    rows: normalizeTimelineRows(record?.rows),
  }];
}

function sortWorkingFrames(frames) {
  return frames
    .map((frame, sourceIndex) => ({
      frame,
      sourceIndex,
      sortIndex: Number.isFinite(Number(frame?.index)) ? Number(frame.index) : sourceIndex,
    }))
    .sort((left, right) => (
      left.sortIndex !== right.sortIndex
        ? left.sortIndex - right.sortIndex
        : left.sourceIndex - right.sourceIndex
    ))
    .map((entry) => entry.frame);
}

function normalizeWorkingGraphicRecord(recordType, record) {
  const source = cloneValue(record) || {};
  const roles = normalizeRoleList(source.roles);
  const nextRoles = recordType === "tile"
    ? Array.from(new Set([...roles, "tile"]))
    : (
      recordType === "sprite"
        ? Array.from(new Set([...roles, "sprite"]))
        : roles.filter((entry) => entry !== "sprite")
    );
  const frames = normalizeWorkingFrames(source).map((frame, index) => ({
    ...frame,
    index,
  }));
  const requestedDefaultFrameId = String(source.defaultFrame || "").trim();
  const defaultFrameId = String(
    frames.find((frame) => String(frame.id || "").trim() === requestedDefaultFrameId)?.id
      || frames[0]?.id
      || "frame_0",
  ).trim() || "frame_0";
  const workingRows = normalizeTimelineRows(
    Array.isArray(source.rows) && source.rows.length > 0
      ? source.rows
      : (frames.find((frame) => String(frame.id || "").trim() === defaultFrameId)?.rows || frames[0]?.rows || [])
  );
  return {
    ...source,
    id: String(source.id || `${recordType === "tile" ? "tile" : "sprite"}_${Date.now()}`).trim(),
    name: typeof source.name === "string" ? source.name : "",
    description: typeof source.description === "string" ? source.description : "",
    width: TILE_ART_SIZE,
    height: TILE_ART_SIZE,
    pixelScale: Math.max(1, Number(source.pixelScale) || 2),
    opacity: Number.isFinite(Number(source.opacity)) ? Math.max(0, Math.min(1, Number(source.opacity))) : 1,
    tags: normalizeEditorTags(source.tags),
    roles: nextRoles,
    tileSymbol: nextRoles.includes("tile")
      ? (String(source.tileSymbol ?? source.symbol ?? source.id ?? "T").trim().charAt(0) || "T")
      : "",
    defaultFrame: defaultFrameId,
    speed: Number.isFinite(Number(source.speed)) && Number(source.speed) >= 0 ? Number(source.speed) : 0,
    playback: normalizeImagePlayback(source.playback),
    frames,
    rows: workingRows,
  };
}

function normalizeOpacityValue(value, fallback = 1) {
  const parsed = Number(value);
  if (!Number.isFinite(parsed)) {
    return fallback;
  }
  return Math.max(0, Math.min(1, parsed));
}

function formatOpacityValue(value) {
  const normalized = normalizeOpacityValue(value, 1);
  return normalized.toFixed(2).replace(/\.?0+$/, "");
}

function buildRowsPreviewRecord(rows) {
  return {
    width: TILE_ART_SIZE,
    height: TILE_ART_SIZE,
    rows: cloneRows(rows),
  };
}

function formatPlaybackLabel(value) {
  const normalized = normalizeImagePlayback(value);
  if (normalized === "rewind") {
    return "Rewind";
  }
  if (normalized === "stop") {
    return "Stop";
  }
  return "Normal";
}

function cloneRows(rows) {
  return Array.isArray(rows)
    ? rows.map((row) => String(row || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE))
    : Array.from({ length: TILE_ART_SIZE }, () => ".".repeat(TILE_ART_SIZE));
}

function getWorkingCellSymbol(record, x, y) {
  const rows = Array.isArray(record?.rows) ? record.rows : [];
  const row = String(rows[y] || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE);
  return String(row.charAt(x) || ".").charAt(0) || ".";
}

function paintWorkingRowsCell(rows, x, y, symbol) {
  const nextRows = cloneRows(rows);
  const nextSymbol = String(symbol || ".").charAt(0) || ".";
  const targetRow = String(nextRows[y] || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE);
  nextRows[y] = `${targetRow.slice(0, x)}${nextSymbol}${targetRow.slice(x + 1)}`;
  return nextRows;
}

function getRowsMatrix(rows) {
  return cloneRows(rows).map((row) => Array.from(row));
}

function buildRowsFromMatrix(matrix) {
  return Array.from({ length: TILE_ART_SIZE }, (_entry, rowIndex) => {
    const sourceRow = Array.isArray(matrix?.[rowIndex]) ? matrix[rowIndex] : [];
    return sourceRow.join("").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE);
  });
}

function getAlternatePaintSymbol(record, preferredSymbol) {
  const normalizedPreferred = String(preferredSymbol || "").charAt(0) || ".";
  const palette = getSpritePalette(record || undefined);
  const nextSymbol = Object.keys(palette)
    .map((symbol) => String(symbol || "").charAt(0))
    .find((symbol) => symbol && symbol !== normalizedPreferred && symbol !== ".");
  return nextSymbol || ".";
}

function shiftRows(rows, offsetX, offsetY) {
  const nextRows = Array.from({ length: TILE_ART_SIZE }, () => ".".repeat(TILE_ART_SIZE).split(""));
  const sourceRows = cloneRows(rows);
  for (let y = 0; y < TILE_ART_SIZE; y += 1) {
    const row = sourceRows[y] || ".".repeat(TILE_ART_SIZE);
    for (let x = 0; x < TILE_ART_SIZE; x += 1) {
      const nextX = x + offsetX;
      const nextY = y + offsetY;
      if (nextX < 0 || nextX >= TILE_ART_SIZE || nextY < 0 || nextY >= TILE_ART_SIZE) {
        continue;
      }
      nextRows[nextY][nextX] = String(row.charAt(x) || ".").charAt(0) || ".";
    }
  }
  return buildRowsFromMatrix(nextRows);
}

function flipRowsHorizontally(rows) {
  return cloneRows(rows).map((row) => row.split("").reverse().join(""));
}

function flipRowsVertically(rows) {
  return cloneRows(rows).slice().reverse();
}

function rotateRowsClockwise(rows) {
  const matrix = getRowsMatrix(rows);
  const nextMatrix = Array.from({ length: TILE_ART_SIZE }, () => Array.from({ length: TILE_ART_SIZE }, () => "."));
  for (let y = 0; y < TILE_ART_SIZE; y += 1) {
    for (let x = 0; x < TILE_ART_SIZE; x += 1) {
      nextMatrix[x][TILE_ART_SIZE - 1 - y] = String(matrix[y]?.[x] || ".").charAt(0) || ".";
    }
  }
  return buildRowsFromMatrix(nextMatrix);
}

function rotateRowsCounterClockwise(rows) {
  const matrix = getRowsMatrix(rows);
  const nextMatrix = Array.from({ length: TILE_ART_SIZE }, () => Array.from({ length: TILE_ART_SIZE }, () => "."));
  for (let y = 0; y < TILE_ART_SIZE; y += 1) {
    for (let x = 0; x < TILE_ART_SIZE; x += 1) {
      nextMatrix[TILE_ART_SIZE - 1 - x][y] = String(matrix[y]?.[x] || ".").charAt(0) || ".";
    }
  }
  return buildRowsFromMatrix(nextMatrix);
}

function buildShapeFillMask(shapeKind, startX, startY, endX, endY) {
  const minX = Math.max(0, Math.min(startX, endX));
  const maxX = Math.min(TILE_ART_SIZE - 1, Math.max(startX, endX));
  const minY = Math.max(0, Math.min(startY, endY));
  const maxY = Math.min(TILE_ART_SIZE - 1, Math.max(startY, endY));
  const fillMask = new Set();
  const shape = shapeKind === "circle" || shapeKind === "triangle" ? shapeKind : "rectangle";
  const width = Math.max(1, (maxX - minX) + 1);
  const height = Math.max(1, (maxY - minY) + 1);
  const centerX = minX + (width / 2);
  const centerY = minY + (height / 2);
  const denomX = Math.max(0.5, width / 2);
  const denomY = Math.max(0.5, height / 2);
  const triangleAx = minX + (width - 1) / 2;
  const triangleAy = minY;
  const triangleBx = minX;
  const triangleBy = maxY;
  const triangleCx = maxX;
  const triangleCy = maxY;
  const triangleDenominator = ((triangleBy - triangleCy) * (triangleAx - triangleCx)) + ((triangleCx - triangleBx) * (triangleAy - triangleCy));
  for (let y = minY; y <= maxY; y += 1) {
    for (let x = minX; x <= maxX; x += 1) {
      let include;
      const sampleX = x + 0.5;
      const sampleY = y + 0.5;
      if (shape === "rectangle") {
        include = true;
      } else if (shape === "circle") {
        const normX = (sampleX - centerX) / denomX;
        const normY = (sampleY - centerY) / denomY;
        include = (normX * normX) + (normY * normY) <= 1;
      } else if (triangleDenominator !== 0) {
        const a = (((triangleBy - triangleCy) * (sampleX - triangleCx)) + ((triangleCx - triangleBx) * (sampleY - triangleCy))) / triangleDenominator;
        const b = (((triangleCy - triangleAy) * (sampleX - triangleCx)) + ((triangleAx - triangleCx) * (sampleY - triangleCy))) / triangleDenominator;
        const c = 1 - a - b;
        include = a >= 0 && b >= 0 && c >= 0;
      } else {
        include = x === Math.round(triangleAx) && y >= minY && y <= maxY;
      }
      if (include === true) {
        fillMask.add(`${x}:${y}`);
      }
    }
  }
  return fillMask;
}

function buildOutlineMask(fillMask) {
  const outlineMask = new Set();
  fillMask.forEach((key) => {
    const [xText, yText] = String(key || "").split(":");
    const x = Number(xText);
    const y = Number(yText);
    const neighbors = [
      `${x - 1}:${y}`,
      `${x + 1}:${y}`,
      `${x}:${y - 1}`,
      `${x}:${y + 1}`,
    ];
    if (neighbors.some((neighbor) => !fillMask.has(neighbor))) {
      outlineMask.add(key);
    }
  });
  return outlineMask;
}

function applyMaskToRows(baseRows, mask, symbol) {
  const matrix = getRowsMatrix(baseRows);
  mask.forEach((key) => {
    const [xText, yText] = String(key || "").split(":");
    const x = Number(xText);
    const y = Number(yText);
    if (x < 0 || x >= TILE_ART_SIZE || y < 0 || y >= TILE_ART_SIZE) {
      return;
    }
    matrix[y][x] = String(symbol || ".").charAt(0) || ".";
  });
  return buildRowsFromMatrix(matrix);
}

function getLineRows(baseRows, startX, startY, endX, endY, symbol) {
  const normalizedSymbol = String(symbol || ".").charAt(0) || ".";
  const matrix = getRowsMatrix(baseRows);
  let x0 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(startX) || 0));
  let y0 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(startY) || 0));
  const x1 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(endX) || 0));
  const y1 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(endY) || 0));
  const deltaX = Math.abs(x1 - x0);
  const deltaY = Math.abs(y1 - y0);
  const stepX = x0 < x1 ? 1 : -1;
  const stepY = y0 < y1 ? 1 : -1;
  let error = deltaX - deltaY;
  while (true) {
    matrix[y0][x0] = normalizedSymbol;
    if (x0 === x1 && y0 === y1) {
      break;
    }
    const nextError = error * 2;
    if (nextError > -deltaY) {
      error -= deltaY;
      x0 += stepX;
    }
    if (nextError < deltaX) {
      error += deltaX;
      y0 += stepY;
    }
  }
  return buildRowsFromMatrix(matrix);
}

function buildShapeOptionIconMarkup(shapeKind, variant, tone = "draw") {
  const normalizedShape = shapeKind === "circle" || shapeKind === "triangle" ? shapeKind : "rectangle";
  const normalizedVariant = variant === "outline" || variant === "two-tone" ? variant : "fill";
  const normalizedTone = tone === "erase" ? "erase" : "draw";
  return ""
    + `<span class="tile-art-menu-shape-icon is-${normalizedShape} is-${normalizedVariant} is-${normalizedTone}" aria-hidden="true">`
    + "<span class=\"tile-art-menu-shape-outline\"></span>"
    + "<span class=\"tile-art-menu-shape-fill\"></span>"
    + "</span>";
}

function buildLineOptionIconMarkup(tone = "draw") {
  const normalizedTone = tone === "erase" ? "erase" : "draw";
  return ""
    + `<span class="tile-art-menu-line-icon is-${normalizedTone}" aria-hidden="true">`
    + "<span class=\"tile-art-menu-line-stroke\"></span>"
    + "</span>";
}

function buildCurrentShapeToolIconMarkup(state) {
  if (state?.activeTool === "line" || String(state?.activeShapeMenuId || "").trim() === "line") {
    return buildLineOptionIconMarkup("draw");
  }
  return buildShapeOptionIconMarkup(
    state?.activeShapeKind || "rectangle",
    state?.activeShapeVariant || "outline",
    "draw",
  );
}

function buildCurrentEraseToolIconMarkup(state) {
  return buildShapeOptionIconMarkup(
    state?.activeEraseKind || "rectangle",
    "fill",
    "erase",
  );
}

function buildTransformCategoryIconMarkup(kind) {
  const normalizedKind = kind === "flip" ? "flip" : "rotate";
  return ""
    + `<span class="tile-art-menu-transform-icon is-${normalizedKind}" aria-hidden="true">`
    + "<span class=\"tile-art-menu-transform-part part-a\"></span>"
    + "<span class=\"tile-art-menu-transform-part part-b\"></span>"
    + "</span>";
}

function buildTransformOptionIconMarkup(kind) {
  const normalizedKind = [
    "rotate-cw",
    "rotate-ccw",
    "flip-h",
    "flip-v",
  ].includes(String(kind || "").trim()) ? String(kind || "").trim() : "rotate-cw";
  return ""
    + `<span class="tile-art-menu-transform-icon is-${normalizedKind}" aria-hidden="true">`
    + "<span class=\"tile-art-menu-transform-part part-a\"></span>"
    + "<span class=\"tile-art-menu-transform-part part-b\"></span>"
    + "</span>";
}

export function createTileArtEditorWindowController(scope) {
  let initialized = false;
  const uiScope = scope.uiScope || scope;
  const sessionScope = scope.sessionScope || scope;
  const persistedState = typeof sessionScope.getPersistedToolWindowState === "function"
    ? sessionScope.getPersistedToolWindowState(TILE_ART_WINDOW_KEY)
    : null;
  const state = {
    visible: persistedState?.visible === true,
    x: Number(persistedState?.x) || 42,
    y: Number(persistedState?.y) || 42,
    width: Number(persistedState?.width) || DEFAULT_WIDTH,
    height: Number(persistedState?.height) || DEFAULT_HEIGHT,
    recordType: "tile",
    recordId: "",
    workingRecord: null,
    activePaintSymbol: "",
    activeSecondarySymbol: ".",
    previewBackgroundSymbol: ".",
    activeTool: "pencil",
    previousToolBeforeEyedropper: "pencil",
    eyedropperActive: false,
    suppressEyedropperUntilAltRelease: false,
    activeShapeKind: "rectangle",
    activeShapeVariant: "outline",
    activeShapeMenuId: "rectangle",
    activeEraseKind: "rectangle",
    openMenu: "",
    activeTab: "design",
    animationsVisible: false,
    activeFrameId: "",
    frameDragId: "",
    frameDropTargetId: "",
    frameDropPosition: "",
    animationSpeedMenuOpen: false,
    animationPlaybackMenuOpen: false,
    animationPreviewVisible: false,
    animationPreviewX: Number(persistedState?.previewX) || 580,
    animationPreviewY: Number(persistedState?.previewY) || 96,
    animationPreviewFrameId: "",
    animationPreviewPlaybackKey: "",
    animationPreviewDirection: 1,
    animationPreviewTimerId: 0,
    dirty: false,
    saving: false,
    savedRecordSnapshot: "",
    localHistoryRows: [],
    localHistoryIndex: -1,
    pendingHistoryCommit: false,
    isPointerPainting: false,
    dragSession: null,
    activePointerId: null,
    activePointerCellKey: "",
    pointerPaintSymbol: ".",
    shellEl: null,
    bodyEl: null,
    titlebarEl: null,
    titleEl: null,
    titleEditBtnEl: null,
    subtitleEl: null,
    nameInputEl: null,
    editingName: false,
    tabButtonsEl: null,
    shortcutHelpBtnEl: null,
    designTabBtnEl: null,
    animationsToggleBtnEl: null,
    tagsTabBtnEl: null,
    designPaneEl: null,
    animationPaneEl: null,
    animationControlsEl: null,
    animationSpeedBtnEl: null,
    animationPlaybackBtnEl: null,
    animationPreviewBtnEl: null,
    animationSpeedMenuHostEl: null,
    animationPlaybackMenuHostEl: null,
    animationSpeedRangeEl: null,
    animationSpeedNumberEl: null,
    animationTimelineEl: null,
    animationTimelineAddHostEl: null,
    animationPreviewShellEl: null,
    animationPreviewTitleEl: null,
    animationPreviewBodyEl: null,
    animationPreviewImageEl: null,
    animationPreviewMetaEl: null,
    animationPreviewFrameLabelEl: null,
    animationPreviewEmptyEl: null,
    animationPreviewCloseBtnEl: null,
    tagsPaneEl: null,
    tagInputEl: null,
    exportTagsBtnEl: null,
    importTagsBtnEl: null,
    tagsSaveBtnEl: null,
    tagListEl: null,
    currentSwatchEl: null,
    secondarySwatchEl: null,
    opacitySummaryEl: null,
    toolButtonsEl: null,
    pencilToolBtnEl: null,
    bucketToolBtnEl: null,
    shapeToolBtnEl: null,
    eraserToolBtnEl: null,
    transformToolBtnEl: null,
    opacityToolBtnEl: null,
    shiftToolBtnEl: null,
    toolMenuHostEl: null,
    opacityRangeEl: null,
    opacityNumberEl: null,
    undoBtnEl: null,
    redoBtnEl: null,
    swatchesEl: null,
    usedSwatchesEl: null,
    previewHintEl: null,
    gridStageEl: null,
    gridEl: null,
    gridWrapEl: null,
    templateDragDepth: 0,
    saveBtnEl: null,
    footerEl: null,
    closeBtnEl: null,
    statusEl: null,
    resizeEl: null,
    nextZIndex: 110,
    pendingTabLayoutSync: false,
  };

  function getWorkingFrames(record = state.workingRecord) {
    return sortWorkingFrames(normalizeWorkingFrames(record)).map((frame, index) => ({
      ...frame,
      index,
    }));
  }

  function getFrameById(record, frameId) {
    const normalizedId = String(frameId || "").trim();
    return getWorkingFrames(record).find((frame) => String(frame.id || "").trim() === normalizedId) || null;
  }

  function getDefaultFrameId(record = state.workingRecord) {
    const frames = getWorkingFrames(record);
    const requestedId = String(record?.defaultFrame || "").trim();
    return String(
      frames.find((frame) => String(frame.id || "").trim() === requestedId)?.id
      || frames[0]?.id
      || "frame_0",
    ).trim() || "frame_0";
  }

  function getAnimationPreviewFrames(record = state.workingRecord) {
    const frames = getWorkingFrames(record);
    if (frames.length <= 0) {
      return [];
    }
    const enabledFrames = frames.filter((frame) => frame.enabled !== false);
    const playbackFrames = enabledFrames.length > 0 ? enabledFrames : frames;
    const defaultFrameId = getDefaultFrameId(record);
    const defaultFrameIndex = playbackFrames.findIndex((frame) => String(frame.id || "").trim() === defaultFrameId);
    if (defaultFrameIndex > 0) {
      return playbackFrames.slice(defaultFrameIndex).concat(playbackFrames.slice(0, defaultFrameIndex));
    }
    return playbackFrames;
  }

  function getPlaybackMode(record = state.workingRecord) {
    return normalizeImagePlayback(record?.playback);
  }

  function getActiveFrameId(record = state.workingRecord) {
    const frames = getWorkingFrames(record);
    const requestedId = String(state.activeFrameId || "").trim();
    return String(
      frames.find((frame) => String(frame.id || "").trim() === requestedId)?.id
      || getDefaultFrameId(record)
      || frames[0]?.id
      || "frame_0",
    ).trim() || "frame_0";
  }

  function getNextFrameId(record = state.workingRecord) {
    const knownIds = new Set(getWorkingFrames(record).map((frame) => String(frame.id || "").trim()).filter(Boolean));
    let nextIndex = knownIds.size;
    while (knownIds.has(`frame_${nextIndex}`)) {
      nextIndex += 1;
    }
    return `frame_${nextIndex}`;
  }

  function buildRecordWithActiveFrameRows(baseRecord, nextRows, frameId = state.activeFrameId) {
    const targetFrameId = String(frameId || getActiveFrameId(baseRecord)).trim();
    const normalizedRows = normalizeTimelineRows(nextRows);
    const nextFrames = getWorkingFrames(baseRecord).map((frame) => (
      String(frame.id || "").trim() === targetFrameId
        ? { ...frame, rows: normalizedRows }
        : frame
    ));
    return normalizeWorkingGraphicRecord(state.recordType, {
      ...baseRecord,
      frames: nextFrames,
      rows: normalizedRows,
    });
  }

  function selectAnimationFrame(frameId, options = {}) {
    if (!state.workingRecord) {
      return false;
    }
    const frame = getFrameById(state.workingRecord, frameId);
    if (!frame) {
      return false;
    }
    state.activeFrameId = String(frame.id || "").trim();
    state.workingRecord = normalizeWorkingGraphicRecord(state.recordType, {
      ...state.workingRecord,
      rows: cloneRows(frame.rows),
    });
    refreshDirtyState();
    if (options.resetHistory !== false) {
      resetLocalHistory(state.workingRecord);
    }
    refreshHeader();
    renderGrid();
    renderAnimationTimeline();
    if (options.statusMessage) {
      updateStatus(options.statusMessage, false);
    }
    return true;
  }

  function applyFrameSet(nextFrames, options = {}) {
    if (!state.workingRecord) {
      return false;
    }
    const normalizedFrames = sortWorkingFrames(
      (Array.isArray(nextFrames) ? nextFrames : []).map((frame, index) => ({
        ...cloneValue(frame),
        id: String(frame?.id || `frame_${index}`).trim() || `frame_${index}`,
        enabled: frame?.enabled !== false,
        index,
        rows: normalizeTimelineRows(frame?.rows),
      })),
    ).map((frame, index) => ({ ...frame, index }));
    if (normalizedFrames.length <= 0) {
      return false;
    }
    const requestedDefaultFrameId = String(options.defaultFrameId || state.workingRecord.defaultFrame || "").trim();
    const nextDefaultFrameId = String(
      normalizedFrames.find((frame) => String(frame.id || "").trim() === requestedDefaultFrameId)?.id
        || normalizedFrames[0]?.id
        || "frame_0",
    ).trim() || "frame_0";
    const requestedActiveFrameId = String(options.activeFrameId || state.activeFrameId || "").trim();
    const nextActiveFrameId = String(
      normalizedFrames.find((frame) => String(frame.id || "").trim() === requestedActiveFrameId)?.id
        || nextDefaultFrameId,
    ).trim() || nextDefaultFrameId;
    const activeFrame = normalizedFrames.find((frame) => String(frame.id || "").trim() === nextActiveFrameId) || normalizedFrames[0];
    state.activeFrameId = nextActiveFrameId;
    state.workingRecord = normalizeWorkingGraphicRecord(state.recordType, {
      ...state.workingRecord,
      defaultFrame: nextDefaultFrameId,
      frames: normalizedFrames,
      rows: cloneRows(activeFrame?.rows || []),
    });
    refreshDirtyState();
    if (options.resetHistory !== false) {
      resetLocalHistory(state.workingRecord);
    }
    refreshHeader();
    renderGrid();
    renderAnimationTimeline();
    if (options.statusMessage) {
      updateStatus(options.statusMessage, false);
    }
    return true;
  }

  function getLayerRect() {
    return uiScope.toolWindowLayerEl?.getBoundingClientRect() || uiScope.editorBodyEl?.getBoundingClientRect() || {
      left: 0,
      top: 0,
      width: DEFAULT_WIDTH,
      height: DEFAULT_HEIGHT,
    };
  }

  function persistState() {
    if (typeof sessionScope.setPersistedToolWindowState === "function") {
      sessionScope.setPersistedToolWindowState(TILE_ART_WINDOW_KEY, {
        visible: state.visible === true,
        mode: "floating",
        x: state.x,
        y: state.y,
        width: LOCKED_WINDOW_WIDTH,
        height: state.height,
        previewX: state.animationPreviewX,
        previewY: state.animationPreviewY,
        order: 998,
      });
    }
    scope.persistPopupSessionLayout?.();
    sessionScope.persistPopupSessionLayout?.();
  }

  function focusWindow() {
    if (!state.shellEl || state.visible !== true) {
      return;
    }
    state.nextZIndex += 1;
    state.shellEl.style.zIndex = String(state.nextZIndex);
    state.shellEl.classList.add("is-focused");
  }

  function clearFocus() {
    state.shellEl?.classList.remove("is-focused");
  }

  function applyWindowRect() {
    if (!state.shellEl) {
      return;
    }
    state.width = LOCKED_WINDOW_WIDTH;
    state.shellEl.style.left = Math.round(state.x) + "px";
    state.shellEl.style.top = Math.round(state.y) + "px";
    state.shellEl.style.width = Math.round(LOCKED_WINDOW_WIDTH) + "px";
    state.shellEl.style.height = Math.round(state.height) + "px";
  }

  function focusAnimationPreviewWindow() {
    if (!state.animationPreviewShellEl || state.animationPreviewVisible !== true) {
      return;
    }
    state.nextZIndex += 1;
    state.animationPreviewShellEl.style.zIndex = String(state.nextZIndex);
    state.animationPreviewShellEl.classList.add("is-focused");
  }

  function clearAnimationPreviewFocus() {
    state.animationPreviewShellEl?.classList.remove("is-focused");
  }

  function applyAnimationPreviewRect() {
    if (!state.animationPreviewShellEl) {
      return;
    }
    const nextRect = clampFloatingWindowRect(
      getLayerRect(),
      state.animationPreviewX,
      state.animationPreviewY,
      ANIMATION_PREVIEW_WIDTH,
      ANIMATION_PREVIEW_HEIGHT,
      ANIMATION_PREVIEW_WIDTH,
      ANIMATION_PREVIEW_HEIGHT,
      ANIMATION_PREVIEW_WIDTH,
      ANIMATION_PREVIEW_HEIGHT,
    );
    state.animationPreviewX = nextRect.left;
    state.animationPreviewY = nextRect.top;
    state.animationPreviewShellEl.style.left = Math.round(state.animationPreviewX) + "px";
    state.animationPreviewShellEl.style.top = Math.round(state.animationPreviewY) + "px";
    state.animationPreviewShellEl.style.width = Math.round(ANIMATION_PREVIEW_WIDTH) + "px";
    state.animationPreviewShellEl.style.height = Math.round(ANIMATION_PREVIEW_HEIGHT) + "px";
  }

  function clearAnimationPreviewTimer() {
    if (state.animationPreviewTimerId) {
      window.clearInterval(state.animationPreviewTimerId);
      state.animationPreviewTimerId = 0;
    }
    state.animationPreviewPlaybackKey = "";
    state.animationPreviewDirection = 1;
  }

  function syncAnimationPreviewFrameDisplay() {
    if (!state.animationPreviewVisible || !state.animationPreviewImageEl || !state.animationPreviewMetaEl || !state.animationPreviewFrameLabelEl || !state.animationPreviewEmptyEl) {
      return;
    }
    const playbackFrames = getAnimationPreviewFrames(state.workingRecord);
    if (playbackFrames.length <= 0) {
      state.animationPreviewImageEl.removeAttribute("src");
      state.animationPreviewImageEl.classList.add("hidden");
      state.animationPreviewEmptyEl.classList.remove("hidden");
      state.animationPreviewFrameLabelEl.textContent = "No frames";
      state.animationPreviewMetaEl.textContent = "Add frames to preview animation.";
      return;
    }
    const currentFrame = playbackFrames.find((frame) => String(frame.id || "").trim() === String(state.animationPreviewFrameId || "").trim()) || playbackFrames[0];
    state.animationPreviewFrameId = String(currentFrame?.id || "").trim();
    const previewUrl = buildSpritePreviewDataUrl(buildRowsPreviewRecord(currentFrame?.rows), 10);
    if (previewUrl) {
      state.animationPreviewImageEl.src = previewUrl;
      state.animationPreviewImageEl.classList.remove("hidden");
      state.animationPreviewEmptyEl.classList.add("hidden");
    } else {
      state.animationPreviewImageEl.removeAttribute("src");
      state.animationPreviewImageEl.classList.add("hidden");
      state.animationPreviewEmptyEl.classList.remove("hidden");
    }
    const speed = Math.max(0, Number(state.workingRecord?.speed) || 0);
    const playbackMode = getPlaybackMode(state.workingRecord);
    state.animationPreviewFrameLabelEl.textContent = String(currentFrame?.id || "frame");
    state.animationPreviewMetaEl.textContent = speed > 0 && playbackFrames.length > 1
      ? `${speed} fps | ${formatPlaybackLabel(playbackMode)} | ${playbackFrames.length} frames`
      : `${playbackFrames.length} frame${playbackFrames.length === 1 ? "" : "s"} | paused`;
  }

  function advanceAnimationPreviewFrame() {
    if (!state.animationPreviewVisible) {
      clearAnimationPreviewTimer();
      return;
    }
    const playbackFrames = getAnimationPreviewFrames(state.workingRecord);
    if (playbackFrames.length <= 1) {
      syncAnimationPreviewFrameDisplay();
      return;
    }
    const playbackMode = getPlaybackMode(state.workingRecord);
    const currentIndex = Math.max(0, playbackFrames.findIndex((frame) => String(frame.id || "").trim() === String(state.animationPreviewFrameId || "").trim()));
    let nextFrame;
    if (playbackMode === "stop") {
      if (currentIndex >= playbackFrames.length - 1) {
        clearAnimationPreviewTimer();
        syncAnimationPreviewFrameDisplay();
        return;
      }
      nextFrame = playbackFrames[currentIndex + 1] || playbackFrames[playbackFrames.length - 1];
    } else if (playbackMode === "rewind") {
      const direction = state.animationPreviewDirection === -1 ? -1 : 1;
      if (direction > 0) {
        if (currentIndex >= playbackFrames.length - 1) {
          state.animationPreviewDirection = -1;
          nextFrame = playbackFrames[Math.max(0, playbackFrames.length - 2)] || playbackFrames[0];
        } else {
          nextFrame = playbackFrames[currentIndex + 1] || playbackFrames[playbackFrames.length - 1];
        }
      } else if (currentIndex <= 0) {
        state.animationPreviewDirection = 1;
        nextFrame = playbackFrames[1] || playbackFrames[0];
      } else {
        nextFrame = playbackFrames[currentIndex - 1] || playbackFrames[0];
      }
    } else {
      nextFrame = playbackFrames[(currentIndex + 1) % playbackFrames.length] || playbackFrames[0];
    }
    state.animationPreviewFrameId = String(nextFrame?.id || "").trim();
    syncAnimationPreviewFrameDisplay();
  }

  function syncAnimationPreviewPlayback() {
    if (!state.animationPreviewVisible || !state.workingRecord) {
      clearAnimationPreviewTimer();
      return;
    }
    const playbackFrames = getAnimationPreviewFrames(state.workingRecord);
    if (playbackFrames.length <= 0) {
      clearAnimationPreviewTimer();
      syncAnimationPreviewFrameDisplay();
      return;
    }
    if (!playbackFrames.some((frame) => String(frame.id || "").trim() === String(state.animationPreviewFrameId || "").trim())) {
      state.animationPreviewFrameId = String(playbackFrames[0]?.id || "").trim();
    }
    syncAnimationPreviewFrameDisplay();
    const speed = Math.max(0, Number(state.workingRecord?.speed) || 0);
    const playbackMode = getPlaybackMode(state.workingRecord);
    const nextPlaybackKey = `${speed}|${playbackMode}|${playbackFrames.map((frame) => String(frame.id || "").trim()).join("|")}`;
    if (speed <= 0 || playbackFrames.length <= 1) {
      clearAnimationPreviewTimer();
      state.animationPreviewPlaybackKey = nextPlaybackKey;
      return;
    }
    if (state.animationPreviewTimerId && state.animationPreviewPlaybackKey === nextPlaybackKey) {
      return;
    }
    clearAnimationPreviewTimer();
    state.animationPreviewPlaybackKey = nextPlaybackKey;
    state.animationPreviewTimerId = window.setInterval(() => {
      advanceAnimationPreviewFrame();
    }, Math.max(50, Math.round(1000 / speed)));
  }

  function syncActiveTabWindowLayout() {
    if (!state.shellEl) {
      return;
    }
    const tagsActive = state.activeTab === "tags";
    state.shellEl.classList.toggle("is-tags-tab", tagsActive);
    const previousHeight = state.shellEl.style.height;
    const previousWidth = state.shellEl.style.width;
    state.shellEl.style.width = `${LOCKED_WINDOW_WIDTH}px`;
    state.shellEl.style.height = "auto";
    const measuredHeight = Math.ceil(state.shellEl.getBoundingClientRect().height || state.shellEl.scrollHeight || (tagsActive ? TAGS_TAB_MIN_HEIGHT : MIN_HEIGHT));
    state.shellEl.style.height = previousHeight;
    state.shellEl.style.width = previousWidth;
    const minimumHeight = tagsActive ? TAGS_TAB_MIN_HEIGHT : MIN_HEIGHT;
    const nextRect = clampFloatingWindowRect(
      getLayerRect(),
      state.x,
      state.y,
      LOCKED_WINDOW_WIDTH,
      Math.max(minimumHeight, measuredHeight),
      LOCKED_WINDOW_WIDTH,
      minimumHeight,
      DEFAULT_WIDTH,
      DEFAULT_HEIGHT,
    );
    state.x = nextRect.left;
    state.y = nextRect.top;
    state.width = LOCKED_WINDOW_WIDTH;
    state.height = nextRect.height;
    applyWindowRect();
  }

  function scheduleActiveTabWindowLayoutSync() {
    if (state.pendingTabLayoutSync) {
      return;
    }
    state.pendingTabLayoutSync = true;
    requestAnimationFrame(() => {
      state.pendingTabLayoutSync = false;
      syncActiveTabWindowLayout();
    });
  }

  function closeAnimationPreview(options = {}) {
    state.animationPreviewVisible = false;
    clearAnimationPreviewTimer();
    clearAnimationPreviewFocus();
    state.animationPreviewShellEl?.classList.add("hidden");
    if (options.persist !== false) {
      persistState();
    }
    refreshHeader();
    return true;
  }

  function ensureAnimationPreviewShell() {
    if (state.animationPreviewShellEl) {
      return state.animationPreviewShellEl;
    }
    const shellEl = document.createElement("div");
    shellEl.className = "tool-popout-window tile-art-preview-window hidden";
    shellEl.setAttribute("role", "dialog");
    shellEl.setAttribute("aria-label", "Animation Preview");

    const titlebarEl = document.createElement("div");
    titlebarEl.className = "tool-popout-titlebar";
    titlebarEl.innerHTML = ''
      + '<div class="tool-popout-title">Animation Preview</div>'
      + '<div class="tool-popout-hint">Live frame playback</div>'
      + '<button type="button" class="tool-popout-close-btn" title="Close animation preview" aria-label="Close animation preview">X</button>';

    const bodyEl = document.createElement("div");
    bodyEl.className = "tool-popout-body tile-art-preview-body";
    const cardEl = document.createElement("div");
    cardEl.className = "tile-art-preview-card";
    const stageEl = document.createElement("div");
    stageEl.className = "tile-art-preview-stage";
    const imageEl = document.createElement("img");
    imageEl.className = "tile-art-preview-image hidden";
    imageEl.alt = "";
    const emptyEl = document.createElement("div");
    emptyEl.className = "tile-art-preview-empty hidden";
    emptyEl.textContent = "No frames to preview";
    stageEl.appendChild(imageEl);
    stageEl.appendChild(emptyEl);
    const frameLabelEl = document.createElement("div");
    frameLabelEl.className = "tile-art-preview-frame-label";
    const metaEl = document.createElement("div");
    metaEl.className = "tile-art-preview-meta";
    cardEl.appendChild(stageEl);
    cardEl.appendChild(frameLabelEl);
    cardEl.appendChild(metaEl);
    bodyEl.appendChild(cardEl);

    const closeBtnEl = titlebarEl.querySelector(".tool-popout-close-btn");
    shellEl.appendChild(titlebarEl);
    shellEl.appendChild(bodyEl);

    shellEl.addEventListener("pointerdown", () => {
      focusAnimationPreviewWindow();
    });
    titlebarEl.addEventListener("pointerdown", (event) => {
      if (closeBtnEl && closeBtnEl.contains(event.target)) {
        return;
      }
      if (event.button !== 0) {
        return;
      }
      event.preventDefault();
      focusAnimationPreviewWindow();
      const layerRect = getLayerRect();
      const originLeft = Number(state.animationPreviewX) || 0;
      const originTop = Number(state.animationPreviewY) || 0;
      const startX = event.clientX;
      const startY = event.clientY;
      const move = (moveEvent) => {
        const nextRect = clampFloatingWindowRect(
          layerRect,
          originLeft + (moveEvent.clientX - startX),
          originTop + (moveEvent.clientY - startY),
          ANIMATION_PREVIEW_WIDTH,
          ANIMATION_PREVIEW_HEIGHT,
          ANIMATION_PREVIEW_WIDTH,
          ANIMATION_PREVIEW_HEIGHT,
          ANIMATION_PREVIEW_WIDTH,
          ANIMATION_PREVIEW_HEIGHT,
        );
        state.animationPreviewX = nextRect.left;
        state.animationPreviewY = nextRect.top;
        applyAnimationPreviewRect();
      };
      const up = () => {
        window.removeEventListener("pointermove", move);
        window.removeEventListener("pointerup", up);
        persistState();
      };
      window.addEventListener("pointermove", move);
      window.addEventListener("pointerup", up);
    });
    closeBtnEl?.addEventListener("click", (event) => {
      event.preventDefault();
      event.stopPropagation();
      closeAnimationPreview();
    });

    state.animationPreviewShellEl = shellEl;
    state.animationPreviewTitleEl = titlebarEl.querySelector(".tool-popout-title");
    state.animationPreviewBodyEl = bodyEl;
    state.animationPreviewImageEl = imageEl;
    state.animationPreviewMetaEl = metaEl;
    state.animationPreviewFrameLabelEl = frameLabelEl;
    state.animationPreviewEmptyEl = emptyEl;
    state.animationPreviewCloseBtnEl = closeBtnEl;
    uiScope.toolWindowLayerEl?.appendChild(shellEl);
    applyAnimationPreviewRect();
    shellEl.classList.toggle("hidden", state.animationPreviewVisible !== true);
    return shellEl;
  }

  function openAnimationPreview() {
    if (!state.workingRecord) {
      return false;
    }
    ensureAnimationPreviewShell();
    if (state.animationPreviewVisible !== true) {
      const preferredLeft = Number(state.x) + LOCKED_WINDOW_WIDTH + 18;
      const preferredTop = Number(state.y) + 52;
      const nextRect = clampFloatingWindowRect(
        getLayerRect(),
        preferredLeft,
        preferredTop,
        ANIMATION_PREVIEW_WIDTH,
        ANIMATION_PREVIEW_HEIGHT,
        ANIMATION_PREVIEW_WIDTH,
        ANIMATION_PREVIEW_HEIGHT,
        ANIMATION_PREVIEW_WIDTH,
        ANIMATION_PREVIEW_HEIGHT,
      );
      state.animationPreviewX = nextRect.left;
      state.animationPreviewY = nextRect.top;
      state.animationPreviewFrameId = String(getAnimationPreviewFrames(state.workingRecord)[0]?.id || getDefaultFrameId(state.workingRecord) || "");
    }
    state.animationPreviewDirection = 1;
    state.animationPreviewVisible = true;
    state.animationPreviewShellEl?.classList.remove("hidden");
    applyAnimationPreviewRect();
    focusAnimationPreviewWindow();
    syncAnimationPreviewPlayback();
    persistState();
    refreshHeader();
    return true;
  }

  function getRecordPayload(recordType) {
    const imagesPayload = scope.ensureDocumentContentPayload?.("images", { schemaVersion: 1, images: [] }) || { schemaVersion: 1, images: [] };
    if (recordType === "sprite" || recordType === "other") {
      return buildSpritesPayloadFromImagesPayload(imagesPayload);
    }
    return buildTilesPayloadFromImagesPayload(imagesPayload);
  }

  function getRecordPayloadKey(recordType) {
    return recordType === "sprite" || recordType === "other" ? "sprites" : "tiles";
  }

  function getRecordById(recordType, recordId) {
    const normalizedId = String(recordId || "").trim();
    const imagesPayload = scope.ensureDocumentContentPayload?.("images", { schemaVersion: 1, images: [] }) || { schemaVersion: 1, images: [] };
    const imageRecords = Array.isArray(imagesPayload.images) ? imagesPayload.images : [];
    const imageRecord = imageRecords.find((entry) => (
      entry
      && typeof entry === "object"
      && !Array.isArray(entry)
      && String(entry.id || "").trim() === normalizedId
    )) || null;
    if (imageRecord) {
      const roles = normalizeRoleList(imageRecord.roles);
      const matchesType = recordType === "tile"
        ? roles.includes("tile")
        : (
          recordType === "sprite"
            ? roles.includes("sprite")
            : (roles.length === 0 && !roles.includes("tile"))
        );
      if (matchesType) {
        return cloneValue(imageRecord);
      }
    }
    const payload = getRecordPayload(recordType);
    const records = Array.isArray(payload?.[getRecordPayloadKey(recordType)]) ? payload[getRecordPayloadKey(recordType)] : [];
    const matchedRecord = records.find((entry) => entry && typeof entry === "object" && !Array.isArray(entry) && String(entry.id || "").trim() === normalizedId) || null;
    if (matchedRecord) {
      return matchedRecord;
    }
    if (recordType === "tile") {
      const tileEntry = typeof scope.getTileEntryById === "function" ? scope.getTileEntryById(normalizedId) : null;
      if (tileEntry) {
        return {
          id: normalizedId,
          symbol: String(tileEntry.symbol || "").charAt(0),
          name: String(tileEntry.name || normalizedId),
          description: "",
          width: Math.max(1, Number(tileEntry.width) || 16),
          height: Math.max(1, Number(tileEntry.height) || 16),
          pixelScale: Math.max(1, Number(tileEntry.pixelScale) || 1),
          rows: Array.isArray(tileEntry.rows) ? tileEntry.rows.map((row) => String(row || "")) : Array.from({ length: 16 }, () => ".".repeat(16)),
          tags: Array.isArray(tileEntry.tags) ? tileEntry.tags.map((tag) => String(tag || "").trim()).filter(Boolean) : [],
        };
      }
    }
    return null;
  }

  function getImageRecordByIdFromPayload(imagesPayload, recordId) {
    const normalizedId = String(recordId || "").trim();
    if (!normalizedId) {
      return null;
    }
    const records = Array.isArray(imagesPayload?.images) ? imagesPayload.images : [];
    return records.find((entry) => (
      entry
      && typeof entry === "object"
      && !Array.isArray(entry)
      && String(entry.id || "").trim() === normalizedId
    )) || null;
  }

  function getDraggedGraphicPanelRecordType(panelKey) {
    const normalizedPanelKey = String(panelKey || "").trim();
    if (normalizedPanelKey === "tiles") {
      return "tile";
    }
    if (normalizedPanelKey === "graphicsOther") {
      return "other";
    }
    if (normalizedPanelKey === "graphicsSprites") {
      return "sprite";
    }
    return "";
  }

  function getDraggedGraphicDescriptor(event) {
    const directDescriptor = scope.organizedListDrag && typeof scope.organizedListDrag === "object"
      ? scope.organizedListDrag
      : null;
    const tryParseDescriptor = (rawValue) => {
      try {
        const parsed = JSON.parse(String(rawValue || ""));
        return parsed && typeof parsed === "object" ? parsed : null;
      } catch {
        return null;
      }
    };
    const textDescriptor = event?.dataTransfer?.getData?.("text/plain")
      ? tryParseDescriptor(event.dataTransfer.getData("text/plain"))
      : null;
    const descriptor = directDescriptor || textDescriptor;
    if (!descriptor || String(descriptor.kind || "").trim() !== "item") {
      return null;
    }
    const recordType = getDraggedGraphicPanelRecordType(descriptor.panelKey);
    const recordId = String(descriptor.id || "").trim();
    if (!recordType || !recordId) {
      return null;
    }
    return {
      panelKey: String(descriptor.panelKey || "").trim(),
      recordType,
      recordId,
    };
  }

  function setTemplateDragActive(isActive) {
    state.gridStageEl?.classList.toggle("is-template-drop-target", isActive === true);
  }

  function clearFrameDropCue() {
    state.frameDropTargetId = "";
    state.frameDropPosition = "";
    state.animationTimelineEl?.querySelectorAll(".is-drop-before, .is-drop-after").forEach((element) => {
      element.classList.remove("is-drop-before", "is-drop-after");
    });
  }

  function setFrameDropCue(targetId, position) {
    clearFrameDropCue();
    const normalizedTargetId = String(targetId || "").trim();
    const normalizedPosition = position === "after" ? "after" : "before";
    if (!normalizedTargetId) {
      return;
    }
    state.frameDropTargetId = normalizedTargetId;
    state.frameDropPosition = normalizedPosition;
    const selector = `[data-frame-id="${CSS.escape(normalizedTargetId)}"]`;
    const targetEl = state.animationTimelineEl?.querySelector(selector);
    targetEl?.classList.add(normalizedPosition === "after" ? "is-drop-after" : "is-drop-before");
  }

  function applyDraggedGraphicTemplate(recordType, recordId) {
    if (!state.workingRecord) {
      return false;
    }
    const sourceRecord = getRecordById(recordType, recordId);
    if (!sourceRecord) {
      updateStatus("Dragged graphic could not be found.", true);
      return false;
    }
    const normalizedSource = normalizeWorkingGraphicRecord(recordType, sourceRecord);
    const changed = applyRows(cloneRows(normalizedSource.rows), {
      statusMessage: `Applied ${String(normalizedSource.name || normalizedSource.id || recordId)} to the canvas.`,
    });
    if (!changed) {
      updateStatus("The canvas already matches that graphic.", false);
      return false;
    }
    return true;
  }

  function getPaletteEntries() {
    const palette = getSpritePalette(state.workingRecord || undefined);
    const opaqueEntries = Object.entries(palette)
      .filter(([symbol]) => String(symbol || "").charAt(0) && String(symbol || "").charAt(0) !== ".")
      .map(([symbol, color]) => ({
        symbol: String(symbol || "").charAt(0),
        color: String(color || "#ffffff"),
    }));
    return opaqueEntries.concat([{ symbol: ".", color: "#00000000" }]);
  }

  function getUsedPaletteEntries() {
    const usedSymbols = new Set();
    const rows = Array.isArray(state.workingRecord?.rows) ? state.workingRecord.rows : [];
    rows.forEach((rawRow) => {
      const row = String(rawRow || "");
      for (let index = 0; index < row.length; index += 1) {
        const symbol = String(row.charAt(index) || ".").charAt(0) || ".";
        usedSymbols.add(symbol);
      }
    });
    return getPaletteEntries().filter((entry) => usedSymbols.has(entry.symbol));
  }

  function serializeWorkingRecord(record) {
    if (!record) {
      return "";
    }
    return JSON.stringify(normalizeImageRecordForSave(cloneValue(record)));
  }

  function refreshDirtyState() {
    state.dirty = serializeWorkingRecord(state.workingRecord) !== String(state.savedRecordSnapshot || "");
  }

  function cloneHistoryRows(rows) {
    return cloneRows(rows);
  }

  function canUndoLocalHistory() {
    return state.localHistoryIndex > 0;
  }

  function canRedoLocalHistory() {
    return state.localHistoryIndex >= 0 && state.localHistoryIndex < (state.localHistoryRows.length - 1);
  }

  function resetLocalHistory(record = state.workingRecord) {
    if (!record) {
      state.localHistoryRows = [];
      state.localHistoryIndex = -1;
      state.pendingHistoryCommit = false;
      return;
    }
    state.localHistoryRows = [cloneHistoryRows(record.rows)];
    state.localHistoryIndex = 0;
    state.pendingHistoryCommit = false;
  }

  function commitLocalHistorySnapshot() {
    if (!state.workingRecord) {
      state.pendingHistoryCommit = false;
      return false;
    }
    const nextRows = cloneHistoryRows(state.workingRecord.rows);
    const nextKey = nextRows.join("\n");
    const currentKey = state.localHistoryIndex >= 0
      ? cloneHistoryRows(state.localHistoryRows[state.localHistoryIndex]).join("\n")
      : "";
    state.pendingHistoryCommit = false;
    if (nextKey === currentKey) {
      return false;
    }
    state.localHistoryRows = state.localHistoryRows.slice(0, Math.max(0, state.localHistoryIndex + 1));
    state.localHistoryRows.push(nextRows);
    state.localHistoryIndex = state.localHistoryRows.length - 1;
    return true;
  }

  function applyHistoryRowsSnapshot(rows, statusMessage) {
    if (!state.workingRecord) {
      return false;
    }
    state.workingRecord = buildRecordWithActiveFrameRows(state.workingRecord, cloneHistoryRows(rows));
    refreshDirtyState();
    clearPointerInteractionState();
    state.dragSession = null;
    refreshHeader();
    renderGrid();
    renderAnimationTimeline();
    if (statusMessage) {
      updateStatus(statusMessage, false);
    }
    return true;
  }

  function undoLocalHistory() {
    if (!canUndoLocalHistory() || !state.workingRecord || state.saving) {
      return false;
    }
    cancelActivePaintingInteraction();
    state.localHistoryIndex -= 1;
    return applyHistoryRowsSnapshot(state.localHistoryRows[state.localHistoryIndex], "Undid art change.");
  }

  function redoLocalHistory() {
    if (!canRedoLocalHistory() || !state.workingRecord || state.saving) {
      return false;
    }
    cancelActivePaintingInteraction();
    state.localHistoryIndex += 1;
    return applyHistoryRowsSnapshot(state.localHistoryRows[state.localHistoryIndex], "Redid art change.");
  }

  function getPaletteColor(symbol) {
    const normalizedSymbol = String(symbol || ".").charAt(0) || ".";
    const palette = getSpritePalette(state.workingRecord || undefined);
    return normalizedSymbol === "." ? "#00000000" : String(palette[normalizedSymbol] || "#00000000");
  }

  function getPreviewBackgroundColor() {
    const color = getPaletteColor(state.previewBackgroundSymbol || ".");
    return (state.previewBackgroundSymbol || ".") === "." ? "transparent" : color;
  }

  function pickDefaultPaintSymbol(record) {
    const paletteEntries = getPaletteEntries();
    const rows = Array.isArray(record?.rows) ? record.rows : [];
    for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
      const row = String(rows[rowIndex] || "");
      for (let charIndex = 0; charIndex < row.length; charIndex += 1) {
        const symbol = String(row.charAt(charIndex) || ".").charAt(0) || ".";
        if (symbol !== ".") {
          return symbol;
        }
      }
    }
    return paletteEntries[0]?.symbol || ".";
  }

  function updateStatus(message, isError) {
    if (!state.statusEl) {
      return;
    }
    state.statusEl.textContent = String(message || "");
    state.statusEl.style.color = isError ? "var(--editor-status-error)" : "var(--editor-status-ok)";
  }

  function isEditableKeyTarget(target) {
    const element = target instanceof Element ? target : null;
    if (!element) {
      return false;
    }
    return element.closest("input, textarea, select, [contenteditable='true'], [contenteditable='']");
  }

  function setNameEditing(nextEditing, options = {}) {
    state.editingName = nextEditing === true;
    if (state.nameInputEl && state.workingRecord) {
      const nextValue = String(state.workingRecord.name || "");
      if (!state.editingName || state.nameInputEl !== document.activeElement) {
        state.nameInputEl.value = nextValue;
      }
    }
    refreshHeader();
    if (state.editingName && state.nameInputEl && options.focus !== false) {
      window.requestAnimationFrame(() => {
        state.nameInputEl?.focus();
        state.nameInputEl?.select();
      });
    }
  }

  function refreshHeader() {
    if (scope.atTooltip && state.openMenu && !scope.atTooltip.isOpenFor(TOOL_MENU_TAG_PREFIX + state.openMenu)) {
      state.openMenu = "";
      state.opacityRangeEl = null;
      state.opacityNumberEl = null;
    }
    if (scope.atTooltip && state.animationSpeedMenuOpen && !scope.atTooltip.isOpenFor(ANIMATION_SPEED_TOOLTIP_TAG)) {
      state.animationSpeedMenuOpen = false;
      state.animationSpeedRangeEl = null;
      state.animationSpeedNumberEl = null;
    }
    if (scope.atTooltip && state.animationPlaybackMenuOpen && !scope.atTooltip.isOpenFor(ANIMATION_PLAYBACK_TOOLTIP_TAG)) {
      state.animationPlaybackMenuOpen = false;
    }
    const kindLabel = state.recordType === "tile"
      ? "Tile"
      : (state.recordType === "other" ? "Other Graphic" : "Sprite");
    const recordName = String(state.workingRecord?.name || state.recordId || kindLabel).trim() || kindLabel;
    if (state.titleEl) {
      state.titleEl.textContent = recordName;
      state.titleEl.classList.toggle("hidden", state.editingName === true);
    }
    if (state.titleEditBtnEl) {
      state.titleEditBtnEl.classList.toggle("hidden", state.editingName === true);
    }
    if (state.subtitleEl) {
      if (state.recordType === "tile") {
        state.subtitleEl.textContent = `${state.recordId || "tile"}`;
      } else {
        const roleLabel = state.recordType === "other" ? "other graphic" : "sprite";
        state.subtitleEl.textContent = `${state.recordId || roleLabel} | ${roleLabel}`;
      }
    }
    if (state.nameInputEl) {
      const nextValue = String(state.workingRecord?.name || "");
      if (state.nameInputEl !== document.activeElement && state.nameInputEl.value !== nextValue) {
        state.nameInputEl.value = nextValue;
      }
      state.nameInputEl.placeholder = recordName;
      state.nameInputEl.classList.toggle("hidden", state.editingName !== true);
    }
    if (state.currentSwatchEl) {
      const palette = getSpritePalette(state.workingRecord || undefined);
      const color = String(palette[state.activePaintSymbol || "."] || "#00000000");
      state.currentSwatchEl.classList.toggle("is-transparent", (state.activePaintSymbol || ".") === ".");
      state.currentSwatchEl.style.setProperty("--swatch-color", (state.activePaintSymbol || ".") === "." ? "transparent" : color);
    }
    if (state.secondarySwatchEl) {
      const palette = getSpritePalette(state.workingRecord || undefined);
      const color = String(palette[state.activeSecondarySymbol || "."] || "#00000000");
      state.secondarySwatchEl.classList.toggle("is-transparent", (state.activeSecondarySymbol || ".") === ".");
      state.secondarySwatchEl.style.setProperty("--swatch-color", (state.activeSecondarySymbol || ".") === "." ? "transparent" : color);
    }
    if (state.pencilToolBtnEl) {
      state.pencilToolBtnEl.classList.toggle("is-active", state.activeTool === "pencil");
      state.pencilToolBtnEl.setAttribute("aria-pressed", state.activeTool === "pencil" ? "true" : "false");
    }
    if (state.bucketToolBtnEl) {
      state.bucketToolBtnEl.classList.toggle("is-active", state.activeTool === "bucket");
      state.bucketToolBtnEl.setAttribute("aria-pressed", state.activeTool === "bucket" ? "true" : "false");
    }
    if (state.shapeToolBtnEl) {
      const shapeToolActive = state.activeTool === "shape" || state.activeTool === "line";
      state.shapeToolBtnEl.classList.toggle("is-active", shapeToolActive);
      state.shapeToolBtnEl.setAttribute("aria-pressed", shapeToolActive ? "true" : "false");
      state.shapeToolBtnEl.innerHTML = buildCurrentShapeToolIconMarkup(state);
    }
    if (state.eraserToolBtnEl) {
      state.eraserToolBtnEl.classList.toggle("is-active", state.activeTool === "erase");
      state.eraserToolBtnEl.setAttribute("aria-pressed", state.activeTool === "erase" ? "true" : "false");
      state.eraserToolBtnEl.innerHTML = buildCurrentEraseToolIconMarkup(state);
    }
    if (state.shiftToolBtnEl) {
      state.shiftToolBtnEl.classList.toggle("is-active", state.activeTool === "shift");
      state.shiftToolBtnEl.setAttribute("aria-pressed", state.activeTool === "shift" ? "true" : "false");
    }
    if (state.transformToolBtnEl) {
      const isOpen = state.openMenu === "transform";
      state.transformToolBtnEl.classList.toggle("is-open", isOpen);
      state.transformToolBtnEl.setAttribute("aria-pressed", isOpen ? "true" : "false");
    }
    if (state.opacityToolBtnEl) {
      const isOpen = state.openMenu === "opacity";
      state.opacityToolBtnEl.classList.toggle("is-open", isOpen);
      state.opacityToolBtnEl.setAttribute("aria-pressed", isOpen ? "true" : "false");
      const opacityValue = formatOpacityValue(state.workingRecord?.opacity);
      state.opacityToolBtnEl.title = `Image opacity: ${opacityValue}`;
      state.opacityToolBtnEl.setAttribute("aria-label", `Image opacity: ${opacityValue}`);
    }
    if (state.designTabBtnEl) {
      state.designTabBtnEl.classList.toggle("is-active", state.activeTab === "design");
      state.designTabBtnEl.setAttribute("aria-pressed", state.activeTab === "design" ? "true" : "false");
    }
    if (state.animationsToggleBtnEl) {
      state.animationsToggleBtnEl.classList.toggle("is-active", state.animationsVisible === true);
      state.animationsToggleBtnEl.setAttribute("aria-pressed", state.animationsVisible === true ? "true" : "false");
    }
    if (state.tagsTabBtnEl) {
      state.tagsTabBtnEl.classList.toggle("is-active", state.activeTab === "tags");
      state.tagsTabBtnEl.setAttribute("aria-pressed", state.activeTab === "tags" ? "true" : "false");
    }
    state.designPaneEl?.classList.toggle("hidden", state.activeTab !== "design");
    state.animationPaneEl?.classList.toggle("hidden", state.animationsVisible !== true);
    state.tagsPaneEl?.classList.toggle("hidden", state.activeTab !== "tags");
    if (state.animationSpeedBtnEl) {
      state.animationSpeedBtnEl.classList.toggle("is-open", state.animationSpeedMenuOpen === true);
      state.animationSpeedBtnEl.setAttribute("aria-pressed", state.animationSpeedMenuOpen === true ? "true" : "false");
      state.animationSpeedBtnEl.title = `Animation speed: ${Number(state.workingRecord?.speed) || 0}`;
      state.animationSpeedBtnEl.setAttribute("aria-label", `Animation speed: ${Number(state.workingRecord?.speed) || 0}`);
    }
    if (state.animationPlaybackBtnEl) {
      state.animationPlaybackBtnEl.classList.toggle("is-open", state.animationPlaybackMenuOpen === true);
      state.animationPlaybackBtnEl.setAttribute("aria-pressed", state.animationPlaybackMenuOpen === true ? "true" : "false");
      const playbackLabel = formatPlaybackLabel(state.workingRecord?.playback);
      state.animationPlaybackBtnEl.title = `Playback mode: ${playbackLabel}`;
      state.animationPlaybackBtnEl.setAttribute("aria-label", `Playback mode: ${playbackLabel}`);
    }
    if (state.animationPreviewBtnEl) {
      state.animationPreviewBtnEl.classList.toggle("is-open", state.animationPreviewVisible === true);
      state.animationPreviewBtnEl.setAttribute("aria-pressed", state.animationPreviewVisible === true ? "true" : "false");
      state.animationPreviewBtnEl.title = "Open animation preview";
      state.animationPreviewBtnEl.setAttribute("aria-label", "Open animation preview");
    }
    [state.saveBtnEl, state.tagsSaveBtnEl].forEach((buttonEl) => {
      if (!buttonEl) {
        return;
      }
      buttonEl.disabled = state.saving || !state.dirty || !state.workingRecord;
      buttonEl.textContent = state.saving ? "Saving..." : "Save";
    });
    if (state.undoBtnEl) {
      state.undoBtnEl.disabled = state.saving || !canUndoLocalHistory();
    }
    if (state.redoBtnEl) {
      state.redoBtnEl.disabled = state.saving || !canRedoLocalHistory();
    }
    if (state.opacityRangeEl && state.workingRecord) {
      const opacityPercent = Math.round(normalizeOpacityValue(state.workingRecord.opacity, 1) * 100);
      if (state.opacityRangeEl.value !== String(opacityPercent)) {
        state.opacityRangeEl.value = String(opacityPercent);
      }
    }
    if (state.opacityNumberEl && state.workingRecord) {
      const opacityValue = formatOpacityValue(state.workingRecord.opacity);
      if (state.opacityNumberEl.value !== opacityValue) {
        state.opacityNumberEl.value = opacityValue;
      }
    }
    if (state.animationSpeedRangeEl && state.workingRecord) {
      const speedValue = String(Number(state.workingRecord.speed) || 0);
      if (state.animationSpeedRangeEl.value !== speedValue) {
        state.animationSpeedRangeEl.value = speedValue;
      }
    }
    if (state.animationSpeedNumberEl && state.workingRecord) {
      const speedValue = String(Number(state.workingRecord.speed) || 0);
      if (state.animationSpeedNumberEl.value !== speedValue) {
        state.animationSpeedNumberEl.value = speedValue;
      }
    }
    syncAnimationPreviewPlayback();
  }

  function setActiveTab(nextTab) {
    const normalizedTab = nextTab === "tags" ? "tags" : "design";
    if (state.activeTab === normalizedTab) {
      return;
    }
    state.activeTab = normalizedTab;
    refreshHeader();
    scheduleActiveTabWindowLayoutSync();
  }

  function clearPointerInteractionState() {
    state.isPointerPainting = false;
    state.activePointerId = null;
    state.activePointerCellKey = "";
    state.pointerPaintSymbol = ".";
    state.pendingHistoryCommit = false;
  }

  function releaseGridPointerCapture(pointerId = state.activePointerId) {
    if (!state.gridEl || pointerId == null || typeof state.gridEl.releasePointerCapture !== "function") {
      return;
    }
    try {
      if (typeof state.gridEl.hasPointerCapture === "function" && state.gridEl.hasPointerCapture(pointerId)) {
        state.gridEl.releasePointerCapture(pointerId);
      }
    } catch {
      // Ignore pointer capture release errors when the browser already cleared it.
    }
  }

  function getGridCellFromClientPoint(clientX, clientY) {
    if (!state.gridEl) {
      return null;
    }
    const rect = state.gridEl.getBoundingClientRect();
    if (!rect.width || !rect.height) {
      return null;
    }
    const step = GRID_CELL_SIZE + 2;
    const localX = Math.max(0, Math.min(rect.width - 1, clientX - rect.left));
    const localY = Math.max(0, Math.min(rect.height - 1, clientY - rect.top));
    return {
      x: Math.max(0, Math.min(TILE_ART_SIZE - 1, Math.floor(localX / step))),
      y: Math.max(0, Math.min(TILE_ART_SIZE - 1, Math.floor(localY / step))),
    };
  }

  function beginPointerInteraction(pointerId, x, y, paintSymbol = ".") {
    state.activePointerId = Number(pointerId);
    state.activePointerCellKey = `${x}:${y}`;
    state.pointerPaintSymbol = String(paintSymbol || ".").charAt(0) || ".";
    state.pendingHistoryCommit = true;
    if (state.gridEl && typeof state.gridEl.setPointerCapture === "function") {
      try {
        state.gridEl.setPointerCapture(Number(pointerId));
      } catch {
        // Ignore capture failures and fall back to window-level pointerup handling.
      }
    }
  }

  function completePointerInteraction(pointerId, options = {}) {
    const normalizedPointerId = pointerId == null ? state.activePointerId : Number(pointerId);
    if (state.activePointerId != null && normalizedPointerId != null && normalizedPointerId !== state.activePointerId) {
      return false;
    }
    releaseGridPointerCapture(normalizedPointerId);
    const shouldFinalizeDrag = options.finalizeDrag !== false;
    const hadDragSession = Boolean(state.dragSession);
    const shouldCommit = state.pendingHistoryCommit === true;
    clearPointerInteractionState();
    if (shouldFinalizeDrag && hadDragSession) {
      finalizeDragSession();
      return true;
    }
    if (shouldFinalizeDrag) {
      finalizeDragSession();
    }
    if (shouldCommit) {
      const changed = commitLocalHistorySnapshot();
      if (changed) {
        refreshHeader();
      }
    }
    return false;
  }

  function handleCapturedGridPointerMove(event) {
    if (state.activePointerId == null || Number(event.pointerId) !== state.activePointerId) {
      return false;
    }
    if (state.eyedropperActive || event.buttons === 0) {
      return false;
    }
    const cell = getGridCellFromClientPoint(event.clientX, event.clientY);
    if (!cell) {
      return false;
    }
    const nextCellKey = `${cell.x}:${cell.y}`;
    if (nextCellKey === state.activePointerCellKey) {
      return false;
    }
    state.activePointerCellKey = nextCellKey;
    if (state.dragSession) {
      updateDragSession(cell.x, cell.y);
      return true;
    }
    if (state.activeTool === "pencil" && state.isPointerPainting) {
      paintAt(cell.x, cell.y, state.pointerPaintSymbol || ".");
      return true;
    }
    return false;
  }

  function cancelActivePaintingInteraction() {
    releaseGridPointerCapture();
    if (state.dragSession?.baseRecord) {
      state.workingRecord = normalizeWorkingGraphicRecord(state.recordType, state.dragSession.baseRecord);
    }
    state.dragSession = null;
    clearPointerInteractionState();
    refreshHeader();
    renderGrid();
    renderAnimationTimeline();
  }

  function closeToolMenu(options = {}) {
    const shouldRefreshHeader = options.refreshHeader !== false;
    if (scope.atTooltip) {
      scope.atTooltip.close();
    }
    state.openMenu = "";
    state.opacityRangeEl = null;
    state.opacityNumberEl = null;
    if (shouldRefreshHeader) {
      refreshHeader();
    }
  }

  function buildShortcutKeycap(label) {
    const keyEl = document.createElement("span");
    keyEl.className = "shortcut-keycap";
    keyEl.textContent = String(label || "");
    return keyEl;
  }

  function buildShortcutPlus() {
    const plusEl = document.createElement("span");
    plusEl.className = "shortcut-plus";
    plusEl.textContent = "+";
    return plusEl;
  }

  function buildShortcutMouse(label, dotClassName = "") {
    const mouseEl = document.createElement("span");
    mouseEl.className = "shortcut-mouse-shell";
    const dotEl = document.createElement("span");
    dotEl.className = `shortcut-mouse-dot${dotClassName ? ` ${dotClassName}` : ""}`;
    const labelEl = document.createElement("span");
    labelEl.className = "shortcut-mouse-label";
    labelEl.textContent = String(label || "");
    mouseEl.appendChild(dotEl);
    mouseEl.appendChild(labelEl);
    return mouseEl;
  }

  function appendShortcutGuideRow(parentEl, keys, actionText) {
    const rowEl = document.createElement("div");
    rowEl.className = "shortcut-row tile-art-shortcut-row";
    const keysEl = document.createElement("div");
    keysEl.className = "shortcut-keys";
    keys.forEach((item, index) => {
      if (index > 0) {
        keysEl.appendChild(buildShortcutPlus());
      }
      if (item.kind === "mouse") {
        keysEl.appendChild(buildShortcutMouse(item.label, item.dotClassName));
      } else {
        keysEl.appendChild(buildShortcutKeycap(item.label));
      }
    });
    const actionEl = document.createElement("div");
    actionEl.className = "shortcut-action tile-art-shortcut-action";
    actionEl.textContent = String(actionText || "");
    rowEl.appendChild(keysEl);
    rowEl.appendChild(actionEl);
    parentEl.appendChild(rowEl);
  }

  function buildShortcutHelpPanel(panelEl) {
    panelEl.classList.add("tile-art-shortcut-help-panel");
    const titleEl = document.createElement("div");
    titleEl.className = "tile-art-shortcut-help-title";
    titleEl.textContent = "Quick Guide";
    panelEl.appendChild(titleEl);

    const listEl = document.createElement("div");
    listEl.className = "tile-art-shortcut-help-list";
    appendShortcutGuideRow(listEl, [
      { kind: "mouse", label: "LMB" },
      { kind: "mouse", label: "Drag" },
    ], "Paint");
    appendShortcutGuideRow(listEl, [
      { kind: "mouse", label: "RMB", dotClassName: "is-secondary" },
      { kind: "mouse", label: "Drag", dotClassName: "is-secondary" },
    ], "Secondary");
    appendShortcutGuideRow(listEl, [
      { kind: "key", label: "Shift" },
      { kind: "mouse", label: "Swatch" },
    ], "Preview");
    appendShortcutGuideRow(listEl, [
      { kind: "key", label: "Alt" },
      { kind: "mouse", label: "LMB / RMB" },
    ], "Sample");
    appendShortcutGuideRow(listEl, [
      { kind: "key", label: "Ctrl" },
      { kind: "key", label: "Z" },
    ], "Undo");
    appendShortcutGuideRow(listEl, [
      { kind: "key", label: "Ctrl" },
      { kind: "key", label: "Y" },
    ], "Redo");
    panelEl.appendChild(listEl);
  }

  function openShortcutHelp() {
    if (!state.shortcutHelpBtnEl || !scope.atTooltip) {
      return false;
    }
    scope.atTooltip.open(state.shortcutHelpBtnEl, (panel) => {
      buildShortcutHelpPanel(panel);
    }, SHORTCUT_HELP_TOOLTIP_TAG);
    return true;
  }

  function closeShortcutHelp() {
    if (scope.atTooltip?.isOpenFor(SHORTCUT_HELP_TOOLTIP_TAG)) {
      scope.atTooltip.close();
    }
  }

  function setActiveTool(nextTool) {
    const normalizedTool = ["line", "bucket", "shape", "erase", "shift"].includes(String(nextTool || ""))
      ? String(nextTool)
      : "pencil";
    state.eyedropperActive = false;
    state.activeTool = normalizedTool;
    if (normalizedTool === "shape") {
      state.activeShapeMenuId = state.activeShapeKind || "rectangle";
    } else if (normalizedTool === "line") {
      state.activeShapeMenuId = "line";
    }
    state.isPointerPainting = false;
    closeToolMenu({ refreshHeader: false });
    refreshHeader();
    const toolStatusById = {
      pencil: "Pencil tool selected. Click or drag to paint single pixels.",
      line: "Line tool selected. Click and drag to place a 1px line.",
      bucket: "Fill tool selected. Click a pixel to flood fill connected matching pixels.",
      shape: `Shape tool selected. Drag to place a ${state.activeShapeKind} (${state.activeShapeVariant}).`,
      erase: `Eraser tool selected. Drag to erase a ${state.activeEraseKind} region.`,
      shift: "Shift tool selected. Drag anywhere on the grid to move the whole image.",
    };
    updateStatus(toolStatusById[state.activeTool] || toolStatusById.pencil, false);
  }

  function endEyedropperMode(options = {}) {
    const shouldRestoreTool = options.restoreTool !== false;
    if (shouldRestoreTool) {
      state.activeTool = state.previousToolBeforeEyedropper || "pencil";
    }
    state.eyedropperActive = false;
    clearPointerInteractionState();
    refreshHeader();
    renderGrid();
  }

  function beginEyedropperMode() {
    if (!state.visible || !state.workingRecord || state.activeTab !== "design" || state.eyedropperActive || state.suppressEyedropperUntilAltRelease) {
      return false;
    }
    state.previousToolBeforeEyedropper = state.activeTool || "pencil";
    cancelActivePaintingInteraction();
    closeToolMenu({ refreshHeader: false });
    state.eyedropperActive = true;
    refreshHeader();
    renderGrid();
    updateStatus("Eyedropper active. Click a pixel to pick its color.", false);
    return true;
  }

  function setPreviewBackgroundSymbol(symbol) {
    state.previewBackgroundSymbol = String(symbol || ".").charAt(0) || ".";
    refreshHeader();
    renderGrid();
    const label = state.previewBackgroundSymbol === "." ? "transparent" : state.previewBackgroundSymbol;
    updateStatus(`Preview background set to ${label}.`, false);
  }

  function sampleCanvasColor(symbol, button = 0) {
    const sampledSymbol = String(symbol || ".").charAt(0) || ".";
    if (button === 2) {
      state.activeSecondarySymbol = sampledSymbol;
    } else {
      state.activePaintSymbol = sampledSymbol;
    }
    state.suppressEyedropperUntilAltRelease = true;
    endEyedropperMode();
    renderSwatches();
    updateStatus(
      button === 2
        ? `Secondary color sampled from '${sampledSymbol}'.`
        : `Primary color sampled from '${sampledSymbol}'.`,
      false,
    );
  }

  function setToolMenu(nextMenu) {
    const normalizedMenu = String(nextMenu || "").trim();
    if (!normalizedMenu) {
      closeToolMenu();
      return;
    }
    if (!scope.atTooltip) {
      return;
    }
    if (scope.atTooltip.isOpenFor(TOOL_MENU_TAG_PREFIX + normalizedMenu)) {
      closeToolMenu();
      return;
    }
    const anchorEl = normalizedMenu === "shape"
      ? state.shapeToolBtnEl
      : normalizedMenu === "erase"
        ? state.eraserToolBtnEl
        : normalizedMenu === "transform"
          ? state.transformToolBtnEl
          : state.opacityToolBtnEl;
    if (!anchorEl) {
      return;
    }
    state.openMenu = normalizedMenu;
    if (normalizedMenu === "shape") {
      state.activeShapeMenuId = state.activeTool === "line" ? "line" : (state.activeShapeKind || "rectangle");
    }
    state.opacityRangeEl = null;
    state.opacityNumberEl = null;
    refreshHeader();
    scope.atTooltip.open(anchorEl, (panel) => {
      buildToolMenuPanel(panel, normalizedMenu, true);
    }, TOOL_MENU_TAG_PREFIX + normalizedMenu);
  }

  function applyRows(nextRows, options = {}) {
    if (!state.workingRecord) {
      return false;
    }
    const normalizedRows = cloneRows(nextRows);
    const previousRows = cloneRows(state.workingRecord.rows);
    if (normalizedRows.join("\n") === previousRows.join("\n")) {
      return false;
    }
    state.workingRecord = buildRecordWithActiveFrameRows(state.workingRecord, normalizedRows);
    refreshDirtyState();
    if (options.commitHistory !== false) {
      commitLocalHistorySnapshot();
    }
    refreshHeader();
    renderGrid();
    renderAnimationTimeline();
    if (options.statusMessage) {
      updateStatus(options.statusMessage, false);
    }
    return true;
  }

  function applyTransform(action) {
    if (!state.workingRecord) {
      return false;
    }
    let nextRows = cloneRows(state.workingRecord.rows);
    if (action === "rotate-cw") {
      nextRows = rotateRowsClockwise(nextRows);
    } else if (action === "rotate-ccw") {
      nextRows = rotateRowsCounterClockwise(nextRows);
    } else if (action === "flip-h") {
      nextRows = flipRowsHorizontally(nextRows);
    } else if (action === "flip-v") {
      nextRows = flipRowsVertically(nextRows);
    } else {
      return false;
    }
    const changed = applyRows(nextRows, { statusMessage: "Unsaved graphic art changes." });
    if (changed) {
      state.openMenu = "";
      renderToolMenu();
      refreshHeader();
    }
    return changed;
  }

  function setOpacityValue(value, options = {}) {
    if (!state.workingRecord) {
      return false;
    }
    const nextOpacity = normalizeOpacityValue(
      options.percent === true ? Number(value) / 100 : Number(value),
      normalizeOpacityValue(state.workingRecord.opacity, 1),
    );
    if (normalizeOpacityValue(state.workingRecord.opacity, 1) === nextOpacity) {
      refreshHeader();
      return false;
    }
    state.workingRecord = {
      ...state.workingRecord,
      opacity: nextOpacity,
    };
    refreshDirtyState();
    refreshHeader();
    updateStatus("Unsaved graphic art changes.", false);
    return true;
  }

  function setAnimationSpeedValue(value) {
    if (!state.workingRecord) {
      return false;
    }
    const nextSpeed = Number.isFinite(Number(value)) && Number(value) >= 0 ? Number(value) : 0;
    if (Number(state.workingRecord.speed) === nextSpeed) {
      refreshHeader();
      return false;
    }
    state.workingRecord = normalizeWorkingGraphicRecord(state.recordType, {
      ...state.workingRecord,
      speed: nextSpeed,
    });
    refreshDirtyState();
    refreshHeader();
    updateStatus("Unsaved animation changes.", false);
    return true;
  }

  function setAnimationPlaybackValue(value) {
    if (!state.workingRecord) {
      return false;
    }
    const nextPlayback = normalizeImagePlayback(value);
    if (normalizeImagePlayback(state.workingRecord.playback) === nextPlayback) {
      refreshHeader();
      return false;
    }
    state.workingRecord = normalizeWorkingGraphicRecord(state.recordType, {
      ...state.workingRecord,
      playback: nextPlayback,
    });
    state.animationPreviewDirection = 1;
    if (state.animationPreviewVisible) {
      const previewFrames = getAnimationPreviewFrames(state.workingRecord);
      state.animationPreviewFrameId = String(
        previewFrames[0]?.id
          || getDefaultFrameId(state.workingRecord)
          || "",
      ).trim();
    }
    refreshDirtyState();
    refreshHeader();
    updateStatus(`Playback mode set to ${formatPlaybackLabel(nextPlayback)}.`, false);
    return true;
  }

  function getShapeRows(baseRows, startX, startY, endX, endY, shapeKind, variant, primarySymbol, secondarySymbol) {
    const fillMask = buildShapeFillMask(shapeKind, startX, startY, endX, endY);
    const outlineMask = buildOutlineMask(fillMask);
    let nextRows = cloneRows(baseRows);
    if (variant === "fill") {
      nextRows = applyMaskToRows(nextRows, fillMask, primarySymbol);
    } else if (variant === "two-tone") {
      nextRows = applyMaskToRows(nextRows, fillMask, secondarySymbol);
      nextRows = applyMaskToRows(nextRows, outlineMask, primarySymbol);
    } else {
      nextRows = applyMaskToRows(nextRows, outlineMask, primarySymbol);
    }
    return nextRows;
  }

  function getEraseRows(baseRows, startX, startY, endX, endY, shapeKind) {
    const fillMask = buildShapeFillMask(shapeKind, startX, startY, endX, endY);
    return applyMaskToRows(baseRows, fillMask, ".");
  }

  function beginDragSession(mode, startX, startY, options = {}) {
    if (!state.workingRecord) {
      return false;
    }
    state.dragSession = {
      mode,
      startX,
      startY,
      lastX: startX,
      lastY: startY,
      baseRecord: cloneValue(state.workingRecord),
      paintSymbol: String(options.paintSymbol || state.activePaintSymbol || ".").charAt(0) || ".",
      secondarySymbol: String(options.secondarySymbol || state.activeSecondarySymbol || ".").charAt(0) || ".",
    };
    return true;
  }

  function updateDragSession(endX, endY) {
    const session = state.dragSession;
    if (!session || !session.baseRecord) {
      return false;
    }
    session.lastX = endX;
    session.lastY = endY;
    let nextRows = cloneRows(session.baseRecord.rows);
    if (session.mode === "shape") {
      nextRows = getShapeRows(
        session.baseRecord.rows,
        session.startX,
        session.startY,
        endX,
        endY,
        state.activeShapeKind,
        state.activeShapeVariant,
        session.paintSymbol,
        session.secondarySymbol,
      );
    } else if (session.mode === "erase") {
      nextRows = getEraseRows(
        session.baseRecord.rows,
        session.startX,
        session.startY,
        endX,
        endY,
        state.activeEraseKind,
      );
    } else if (session.mode === "line") {
      nextRows = getLineRows(
        session.baseRecord.rows,
        session.startX,
        session.startY,
        endX,
        endY,
        session.paintSymbol,
      );
    } else if (session.mode === "shift") {
      nextRows = shiftRows(
        session.baseRecord.rows,
        endX - session.startX,
        endY - session.startY,
      );
    }
    state.workingRecord = buildRecordWithActiveFrameRows(session.baseRecord, nextRows);
    refreshHeader();
    renderGrid();
    renderAnimationTimeline();
    return true;
  }

  function finalizeDragSession() {
    const session = state.dragSession;
    state.dragSession = null;
    if (!session || !session.baseRecord || !state.workingRecord) {
      return false;
    }
    const nextRows = cloneRows(state.workingRecord.rows);
    const baseRows = cloneRows(session.baseRecord.rows);
    if (nextRows.join("\n") === baseRows.join("\n")) {
      return false;
    }
    refreshDirtyState();
    commitLocalHistorySnapshot();
    refreshHeader();
    renderGrid();
    updateStatus("Unsaved graphic art changes.", false);
    return true;
  }

  function buildToolMenuPanel(panelEl, menuId, useTooltipStyle = false) {
    state.opacityRangeEl = null;
    state.opacityNumberEl = null;
    const closeMenuAndRefresh = () => {
      closeToolMenu();
    };
    const makeMenuItem = (label, onClick, isActive = false) => {
      if (useTooltipStyle && scope.atTooltip) {
        return scope.atTooltip.makeItem(
          `<span>${label}</span>`,
          () => {
            onClick();
            closeMenuAndRefresh();
          },
          isActive ? "is-active" : "",
        );
      }
      const btn = document.createElement("button");
      btn.type = "button";
      btn.className = "tile-art-tool-menu-btn" + (isActive ? " is-active" : "");
      btn.textContent = label;
      btn.addEventListener("click", () => {
        onClick();
        closeMenuAndRefresh();
      });
      return btn;
    };
    const makePersistentMenuButton = (label, onClick, isActive = false, extraClass = "") => {
      if (useTooltipStyle) {
        const btn = document.createElement("button");
        btn.type = "button";
        btn.className = `at-tooltip-item${isActive ? " is-active" : ""}${extraClass ? ` ${extraClass}` : ""}`;
        btn.setAttribute("role", "menuitem");
        btn.innerHTML = `<span>${label}</span>`;
        btn.addEventListener("click", () => {
          onClick();
        });
        return btn;
      }
      const btn = document.createElement("button");
      btn.type = "button";
      btn.className = `tile-art-tool-menu-btn${isActive ? " is-active" : ""}${extraClass ? ` ${extraClass}` : ""}`;
      btn.textContent = label;
      btn.addEventListener("click", () => {
        onClick();
      });
      return btn;
    };
    if (menuId === "shape") {
      if (useTooltipStyle && scope.atTooltip) {
        const shapeDefinitions = [
          ["rectangle", "Rectangle"],
          ["circle", "Circle"],
          ["triangle", "Triangle"],
        ];
        const variantDefinitions = [
          ["outline", "Outline"],
          ["fill", "Fill"],
          ["two-tone", "Outline + Fill"],
        ];
        const menuItems = [
          ...shapeDefinitions.map(([shapeId, label]) => menuSubmenu(
            buildShapeOptionIconMarkup(shapeId, "fill", "draw"),
            variantDefinitions.map(([variantId, variantLabel]) => menuItem(
              buildShapeOptionIconMarkup(shapeId, variantId, "draw"),
              () => {
                state.activeShapeKind = shapeId;
                state.activeShapeVariant = variantId;
                state.activeShapeMenuId = shapeId;
                state.openMenu = "";
                setActiveTool("shape");
              },
              state.activeTool === "shape" && state.activeShapeKind === shapeId && state.activeShapeVariant === variantId
                ? "is-active"
                : "",
              {
                ariaLabel: `${label} ${variantLabel}`,
                presentation: "icon",
                title: `${label} ${variantLabel}`,
              },
            )),
            state.activeShapeMenuId === shapeId || (state.activeTool === "shape" && state.activeShapeKind === shapeId)
              ? "is-active"
              : "",
            {
              ariaLabel: label,
              layout: "horizontal",
              presentation: "icon",
              title: label,
            },
          )),
          menuItem(
            buildLineOptionIconMarkup("draw"),
            () => {
              state.activeShapeMenuId = "line";
              state.openMenu = "";
              setActiveTool("line");
            },
            state.activeTool === "line" ? "is-active" : "",
            {
              ariaLabel: "Line",
              presentation: "icon",
              title: "Line",
            },
          ),
        ];
        appendContextMenuItems(scope.atTooltip, panelEl, menuItems, TOOL_MENU_TAG_PREFIX + "shape");
        return;
      } else {
        const titleEl = document.createElement("div");
        titleEl.className = "tile-art-tool-menu-title";
        titleEl.textContent = "Shapes";
        panelEl.appendChild(titleEl);
      }
      const shapeDefinitions = [
        ["rectangle", "Rectangle"],
        ["circle", "Circle"],
        ["triangle", "Triangle"],
      ];
      const variantDefinitions = [
        ["outline", "Outline"],
        ["fill", "Fill"],
        ["two-tone", "Outline + Fill"],
      ];
      const menuLayoutEl = document.createElement("div");
      menuLayoutEl.className = "tile-art-shape-menu-layout";
      const primaryColumnEl = document.createElement("div");
      primaryColumnEl.className = "tile-art-shape-menu-primary";
      const submenuColumnEl = document.createElement("div");
      submenuColumnEl.className = "tile-art-shape-menu-submenu";
      const renderShapeSubmenu = () => {
        submenuColumnEl.innerHTML = "";
        const activeShapeId = state.activeShapeMenuId === "line" ? (state.activeShapeKind || "rectangle") : state.activeShapeMenuId;
        const activeShapeLabel = shapeDefinitions.find(([shapeId]) => shapeId === activeShapeId)?.[1] || "Rectangle";
        const subtitleEl = document.createElement("div");
        subtitleEl.className = useTooltipStyle ? "at-tooltip-label tile-art-shape-menu-subtitle" : "tile-art-tool-menu-label tile-art-shape-menu-subtitle";
        subtitleEl.textContent = activeShapeLabel;
        submenuColumnEl.appendChild(subtitleEl);
        variantDefinitions.forEach(([variantId, variantLabel]) => {
          submenuColumnEl.appendChild(makeMenuItem(
            variantLabel,
            () => {
              state.activeShapeKind = activeShapeId;
              state.activeShapeVariant = variantId;
              state.activeShapeMenuId = activeShapeId;
              state.openMenu = "";
              setActiveTool("shape");
            },
            state.activeTool === "shape" && state.activeShapeKind === activeShapeId && state.activeShapeVariant === variantId,
          ));
        });
      };
      shapeDefinitions.forEach(([shapeId, label]) => {
        primaryColumnEl.appendChild(makePersistentMenuButton(
          `${label} >`,
          () => {
            state.activeShapeMenuId = shapeId;
            panelEl.innerHTML = "";
            buildToolMenuPanel(panelEl, menuId, useTooltipStyle);
          },
          state.activeShapeMenuId === shapeId || (state.activeTool === "shape" && state.activeShapeKind === shapeId),
          "tile-art-shape-menu-trigger",
        ));
      });
      primaryColumnEl.appendChild(makeMenuItem(
        "Line",
        () => {
          state.activeShapeMenuId = "line";
          state.openMenu = "";
          setActiveTool("line");
        },
        state.activeTool === "line",
      ));
      renderShapeSubmenu();
      menuLayoutEl.appendChild(primaryColumnEl);
      menuLayoutEl.appendChild(submenuColumnEl);
      panelEl.appendChild(menuLayoutEl);
    } else if (menuId === "erase") {
      if (useTooltipStyle && scope.atTooltip) {
        const eraseItems = [
          ["rectangle", "Rectangle"],
          ["circle", "Circle"],
          ["triangle", "Triangle"],
        ].map(([shapeId, label]) => menuItem(
          buildShapeOptionIconMarkup(shapeId, "fill", "erase"),
          () => {
            state.activeEraseKind = shapeId;
            state.openMenu = "";
            setActiveTool("erase");
          },
          state.activeEraseKind === shapeId ? "is-active" : "",
          {
            ariaLabel: `${label} eraser`,
            presentation: "icon",
            title: `${label} eraser`,
          },
        ));
        appendContextMenuItems(scope.atTooltip, panelEl, eraseItems, TOOL_MENU_TAG_PREFIX + "erase");
        return;
      } else {
        const titleEl = document.createElement("div");
        titleEl.className = "tile-art-tool-menu-title";
        titleEl.textContent = "Erasers";
        panelEl.appendChild(titleEl);
      }
      [
        ["rectangle", "Rectangle"],
        ["circle", "Circle"],
        ["triangle", "Triangle"],
      ].forEach(([shapeId, label]) => {
        panelEl.appendChild(makeMenuItem(
          label,
          () => {
          state.activeEraseKind = shapeId;
          state.openMenu = "";
          setActiveTool("erase");
          },
          state.activeEraseKind === shapeId,
        ));
      });
    } else if (menuId === "transform") {
      if (useTooltipStyle && scope.atTooltip) {
        const transformItems = [
          menuSubmenu(
            buildTransformCategoryIconMarkup("rotate"),
            [
              menuItem(
                buildTransformOptionIconMarkup("rotate-cw"),
                () => {
                  applyTransform("rotate-cw");
                },
                "",
                {
                  ariaLabel: "Rotate 90 clockwise",
                  presentation: "icon",
                  title: "Rotate 90 clockwise",
                },
              ),
              menuItem(
                buildTransformOptionIconMarkup("rotate-ccw"),
                () => {
                  applyTransform("rotate-ccw");
                },
                "",
                {
                  ariaLabel: "Rotate 90 counter-clockwise",
                  presentation: "icon",
                  title: "Rotate 90 counter-clockwise",
                },
              ),
            ],
            "",
            {
              ariaLabel: "Rotate",
              layout: "horizontal",
              presentation: "icon",
              title: "Rotate",
            },
          ),
          menuSubmenu(
            buildTransformCategoryIconMarkup("flip"),
            [
              menuItem(
                buildTransformOptionIconMarkup("flip-h"),
                () => {
                  applyTransform("flip-h");
                },
                "",
                {
                  ariaLabel: "Flip horizontally",
                  presentation: "icon",
                  title: "Flip horizontally",
                },
              ),
              menuItem(
                buildTransformOptionIconMarkup("flip-v"),
                () => {
                  applyTransform("flip-v");
                },
                "",
                {
                  ariaLabel: "Flip vertically",
                  presentation: "icon",
                  title: "Flip vertically",
                },
              ),
            ],
            "",
            {
              ariaLabel: "Flip",
              layout: "horizontal",
              presentation: "icon",
              title: "Flip",
            },
          ),
        ];
        appendContextMenuItems(scope.atTooltip, panelEl, transformItems, TOOL_MENU_TAG_PREFIX + "transform");
        return;
      } else {
        const titleEl = document.createElement("div");
        titleEl.className = "tile-art-tool-menu-title";
        titleEl.textContent = "Transform";
        panelEl.appendChild(titleEl);
      }
      [
        ["rotate-cw", "Rotate 90 CW"],
        ["rotate-ccw", "Rotate 90 CCW"],
        ["flip-h", "Flip Horizontally"],
        ["flip-v", "Flip Vertically"],
      ].forEach(([actionId, label]) => {
        panelEl.appendChild(makeMenuItem(label, () => {
          applyTransform(actionId);
        }));
      });
    } else if (menuId === "opacity") {
      const titleEl = document.createElement("div");
      titleEl.className = useTooltipStyle && scope.atTooltip ? "at-tooltip-label" : "tile-art-tool-menu-title";
      titleEl.textContent = `Opacity ${formatOpacityValue(state.workingRecord?.opacity)}`;
      const subtitleEl = document.createElement("div");
      subtitleEl.className = useTooltipStyle && scope.atTooltip ? "at-tooltip-label tile-art-tool-menu-help" : "tile-art-tool-menu-help";
      subtitleEl.textContent = "Stored on the image record as a value between 0 and 1.";
      const controlsEl = document.createElement("div");
      controlsEl.className = "tile-art-opacity-controls";
      const rangeEl = document.createElement("input");
      rangeEl.type = "range";
      rangeEl.min = "0";
      rangeEl.max = "100";
      rangeEl.step = "1";
      rangeEl.className = "tile-art-opacity-range";
      rangeEl.value = String(Math.round(normalizeOpacityValue(state.workingRecord?.opacity, 1) * 100));
      rangeEl.addEventListener("input", () => {
        setOpacityValue(rangeEl.value, { percent: true });
      });
      const numberEl = document.createElement("input");
      numberEl.type = "number";
      numberEl.min = "0";
      numberEl.max = "1";
      numberEl.step = "0.01";
      numberEl.className = "tile-art-opacity-number";
      numberEl.value = formatOpacityValue(state.workingRecord?.opacity);
      numberEl.addEventListener("input", () => {
        setOpacityValue(numberEl.value);
      });
      controlsEl.appendChild(rangeEl);
      controlsEl.appendChild(numberEl);
      panelEl.appendChild(titleEl);
      panelEl.appendChild(subtitleEl);
      panelEl.appendChild(controlsEl);
      state.opacityRangeEl = rangeEl;
      state.opacityNumberEl = numberEl;
    }
  }

  function renderToolMenu() {}

  function renderAnimationSpeedMenu() {}

  function buildAnimationSpeedTooltipPanel(panelEl) {
    panelEl.classList.add("tile-art-animation-speed-menu");
    const labelEl = document.createElement("div");
    labelEl.className = "at-tooltip-label";
    labelEl.textContent = "Animation Speed";
    const helpEl = document.createElement("div");
    helpEl.className = "at-tooltip-label tile-art-tool-menu-help";
    helpEl.textContent = "Stored on the image record for future playback.";
    const controlsEl = document.createElement("div");
    controlsEl.className = "tile-art-opacity-controls";
    const rangeEl = document.createElement("input");
    rangeEl.type = "range";
    rangeEl.min = "0";
    rangeEl.max = "60";
    rangeEl.step = "1";
    rangeEl.className = "tile-art-opacity-range";
    rangeEl.value = String(Number(state.workingRecord?.speed) || 0);
    rangeEl.addEventListener("input", () => {
      setAnimationSpeedValue(rangeEl.value);
    });
    const numberEl = document.createElement("input");
    numberEl.type = "number";
    numberEl.min = "0";
    numberEl.max = "60";
    numberEl.step = "1";
    numberEl.className = "tile-art-opacity-number";
    numberEl.value = rangeEl.value;
    numberEl.addEventListener("input", () => {
      setAnimationSpeedValue(numberEl.value);
    });
    controlsEl.appendChild(rangeEl);
    controlsEl.appendChild(numberEl);
    panelEl.appendChild(labelEl);
    panelEl.appendChild(helpEl);
    panelEl.appendChild(controlsEl);
    state.animationSpeedRangeEl = rangeEl;
    state.animationSpeedNumberEl = numberEl;
  }

  function toggleAnimationSpeedMenu() {
    if (!state.animationSpeedBtnEl) {
      return;
    }
    if (!scope.atTooltip) {
      state.animationSpeedMenuOpen = false;
      state.animationPlaybackMenuOpen = false;
      refreshHeader();
      return;
    }
    if (scope.atTooltip.isOpenFor(ANIMATION_SPEED_TOOLTIP_TAG)) {
      scope.atTooltip.close();
      state.animationSpeedMenuOpen = false;
      refreshHeader();
      return;
    }
    state.animationSpeedMenuOpen = true;
    state.animationPlaybackMenuOpen = false;
    refreshHeader();
    renderAnimationPlaybackMenu();
    scope.atTooltip.open(state.animationSpeedBtnEl, (panel) => {
      buildAnimationSpeedTooltipPanel(panel);
    }, ANIMATION_SPEED_TOOLTIP_TAG);
  }

  function renderAnimationPlaybackMenu() {}

  function buildAnimationPlaybackTooltipPanel(panelEl) {
    appendContextMenuItems(scope.atTooltip, panelEl, [
      menuItem("<span>Normal</span>", () => {
        setAnimationPlaybackValue("normal");
        state.animationPlaybackMenuOpen = false;
        refreshHeader();
      }, normalizeImagePlayback(state.workingRecord?.playback) === "normal" ? "is-active" : ""),
      menuItem("<span>Rewind</span>", () => {
        setAnimationPlaybackValue("rewind");
        state.animationPlaybackMenuOpen = false;
        refreshHeader();
      }, normalizeImagePlayback(state.workingRecord?.playback) === "rewind" ? "is-active" : ""),
      menuItem("<span>Stop</span>", () => {
        setAnimationPlaybackValue("stop");
        state.animationPlaybackMenuOpen = false;
        refreshHeader();
      }, normalizeImagePlayback(state.workingRecord?.playback) === "stop" ? "is-active" : ""),
    ], `${TOOL_MENU_TAG_PREFIX}playback`);
  }

  function toggleAnimationPlaybackMenu() {
    if (!state.animationPlaybackBtnEl) {
      return;
    }
    if (!scope.atTooltip) {
      state.animationPlaybackMenuOpen = false;
      state.animationSpeedMenuOpen = false;
      refreshHeader();
      return;
    }
    if (scope.atTooltip.isOpenFor(ANIMATION_PLAYBACK_TOOLTIP_TAG)) {
      scope.atTooltip.close();
      state.animationPlaybackMenuOpen = false;
      refreshHeader();
      return;
    }
    state.animationPlaybackMenuOpen = true;
    state.animationSpeedMenuOpen = false;
    refreshHeader();
    renderAnimationSpeedMenu();
    scope.atTooltip.open(state.animationPlaybackBtnEl, (panel) => {
      buildAnimationPlaybackTooltipPanel(panel);
    }, ANIMATION_PLAYBACK_TOOLTIP_TAG);
  }

  function buildSwatchButton(entry, options = {}) {
    const swatchBtn = document.createElement("button");
    swatchBtn.type = "button";
    const isPrimarySelected = state.activePaintSymbol === entry.symbol;
    const isSecondarySelected = state.activeSecondarySymbol === entry.symbol;
    swatchBtn.className = "tile-art-swatch-btn"
      + (options.usedList === true ? " tile-art-used-swatch-btn" : "")
      + (isPrimarySelected ? " is-active" : "")
      + (isSecondarySelected ? " is-secondary" : "")
      + (entry.symbol === "." ? " is-transparent is-transparent-action" : "");
    swatchBtn.style.setProperty("--swatch-color", entry.symbol === "." ? "transparent" : entry.color);
    swatchBtn.innerHTML = ""
      + (isPrimarySelected
        ? '<span class="tile-art-swatch-indicator is-primary" aria-hidden="true"><span class="tile-art-swatch-indicator-left"></span><span class="tile-art-swatch-indicator-right"></span></span>'
        : "")
      + (isSecondarySelected
        ? '<span class="tile-art-swatch-indicator is-secondary" aria-hidden="true"><span class="tile-art-swatch-indicator-left"></span><span class="tile-art-swatch-indicator-right"></span></span>'
        : "");
    swatchBtn.title = entry.symbol === "."
      ? "Transparent / empty pixel"
      : `${entry.symbol} - ${entry.color}. Left click = primary, right click = secondary, Shift+click = preview background.`;
    swatchBtn.setAttribute("aria-label", swatchBtn.title);
    swatchBtn.addEventListener("click", (event) => {
      if (event.shiftKey) {
        setPreviewBackgroundSymbol(entry.symbol);
        return;
      }
      state.activePaintSymbol = entry.symbol;
      refreshHeader();
      renderSwatches();
    });
    swatchBtn.addEventListener("contextmenu", (event) => {
      event.preventDefault();
      if (event.shiftKey) {
        setPreviewBackgroundSymbol(entry.symbol);
        return;
      }
      state.activeSecondarySymbol = entry.symbol;
      refreshHeader();
      renderSwatches();
    });
    return swatchBtn;
  }

  function renderSwatches() {
    if (!state.swatchesEl) {
      return;
    }
    const paletteEntries = getPaletteEntries();
    state.swatchesEl.innerHTML = "";
    paletteEntries.forEach((entry) => {
      state.swatchesEl.appendChild(buildSwatchButton(entry));
    });
    renderUsedSwatches();
  }

  function renderUsedSwatches() {
    if (!state.usedSwatchesEl) {
      return;
    }
    state.usedSwatchesEl.innerHTML = "";
    const usedEntries = getUsedPaletteEntries();
    usedEntries.forEach((entry) => {
      state.usedSwatchesEl.appendChild(buildSwatchButton(entry, { usedList: true }));
    });
  }

  function paintAt(x, y, symbol = state.activePaintSymbol || ".") {
    if (!state.workingRecord) {
      return;
    }
    const nextRows = paintWorkingRowsCell(state.workingRecord.rows, x, y, symbol);
    state.workingRecord = buildRecordWithActiveFrameRows(state.workingRecord, nextRows);
    refreshDirtyState();
    refreshHeader();
    renderGrid();
    renderAnimationTimeline();
    updateStatus("Unsaved tile art changes.", false);
  }

  function fillAt(startX, startY, symbol = state.activePaintSymbol || ".") {
    if (!state.workingRecord) {
      return;
    }
    const replacementSymbol = String(symbol || ".").charAt(0) || ".";
    const targetSymbol = getWorkingCellSymbol(state.workingRecord, startX, startY);
    if (targetSymbol === replacementSymbol) {
      return;
    }
    const nextRows = Array.isArray(state.workingRecord.rows)
      ? state.workingRecord.rows.map((row) => String(row || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE))
      : Array.from({ length: TILE_ART_SIZE }, () => ".".repeat(TILE_ART_SIZE));
    const queue = [[startX, startY]];
    const visited = new Set();
    while (queue.length > 0) {
      const nextCell = queue.shift();
      const x = Number(nextCell?.[0]);
      const y = Number(nextCell?.[1]);
      if (x < 0 || x >= TILE_ART_SIZE || y < 0 || y >= TILE_ART_SIZE) {
        continue;
      }
      const cellKey = x + ":" + y;
      if (visited.has(cellKey)) {
        continue;
      }
      visited.add(cellKey);
      const row = String(nextRows[y] || ".".repeat(TILE_ART_SIZE)).padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE);
      const symbol = String(row.charAt(x) || ".").charAt(0) || ".";
      if (symbol !== targetSymbol) {
        continue;
      }
      nextRows[y] = row.slice(0, x) + replacementSymbol + row.slice(x + 1);
      queue.push([x - 1, y]);
      queue.push([x + 1, y]);
      queue.push([x, y - 1]);
      queue.push([x, y + 1]);
    }
    state.workingRecord = buildRecordWithActiveFrameRows(state.workingRecord, nextRows);
    refreshDirtyState();
    commitLocalHistorySnapshot();
    refreshHeader();
    renderGrid();
    renderAnimationTimeline();
    updateStatus("Unsaved tile art changes.", false);
  }

  function renderGrid() {
    if (!state.gridEl || !state.workingRecord) {
      if (state.usedSwatchesEl) {
        state.usedSwatchesEl.innerHTML = "";
      }
      return;
    }
    renderUsedSwatches();
    const palette = getSpritePalette(state.workingRecord);
    state.gridEl.innerHTML = "";
    state.gridEl.style.gridTemplateColumns = `repeat(${TILE_ART_SIZE}, ${GRID_CELL_SIZE}px)`;
    state.gridEl.classList.toggle("is-eyedropper", state.eyedropperActive === true);
    state.gridEl.style.cursor = state.eyedropperActive ? EYEDROPPER_CURSOR : "";
    state.gridEl.style.setProperty("--preview-bg-color", getPreviewBackgroundColor());
    state.gridWrapEl?.style.setProperty("--preview-bg-color", getPreviewBackgroundColor());
    for (let y = 0; y < TILE_ART_SIZE; y += 1) {
      for (let x = 0; x < TILE_ART_SIZE; x += 1) {
        const symbol = getWorkingCellSymbol(state.workingRecord, x, y);
        const color = String(palette[symbol] || "#00000000");
        const cellEl = document.createElement("button");
        cellEl.type = "button";
        cellEl.className = "tile-art-cell" + (symbol === "." ? " is-transparent" : "");
        cellEl.style.setProperty("--paint-color", symbol === "." ? "transparent" : color);
        cellEl.title = `${x + 1},${y + 1} '${symbol}'`;
        cellEl.addEventListener("pointerdown", (event) => {
          event.preventDefault();
          event.stopPropagation();
          focusWindow();
          if (state.eyedropperActive) {
            sampleCanvasColor(symbol, event.button);
            return;
          }
          const paintSymbol = event.button === 2 ? (state.activeSecondarySymbol || ".") : (state.activePaintSymbol || ".");
          if (state.activeTool === "bucket") {
            clearPointerInteractionState();
            fillAt(x, y, paintSymbol);
            refreshHeader();
            return;
          }
          beginPointerInteraction(event.pointerId, x, y, paintSymbol);
          if (state.activeTool === "shape") {
            state.isPointerPainting = false;
            beginDragSession("shape", x, y, {
              paintSymbol,
              secondarySymbol: event.button === 2 ? state.activePaintSymbol : state.activeSecondarySymbol,
            });
            updateDragSession(x, y);
            return;
          }
          if (state.activeTool === "line") {
            state.isPointerPainting = false;
            beginDragSession("line", x, y, {
              paintSymbol,
            });
            updateDragSession(x, y);
            return;
          }
          if (state.activeTool === "erase") {
            state.isPointerPainting = false;
            beginDragSession("erase", x, y);
            updateDragSession(x, y);
            return;
          }
          if (state.activeTool === "shift") {
            state.isPointerPainting = false;
            beginDragSession("shift", x, y);
            updateDragSession(x, y);
            return;
          }
          state.isPointerPainting = true;
          paintAt(x, y, paintSymbol);
        });
        cellEl.addEventListener("contextmenu", (event) => {
          event.preventDefault();
        });
        state.gridEl.appendChild(cellEl);
      }
    }
  }

  function renderAnimationTimeline() {
    if (!state.animationTimelineEl || !state.animationTimelineAddHostEl) {
      return;
    }
    state.animationTimelineEl.innerHTML = "";
    state.animationTimelineAddHostEl.innerHTML = "";
    const frames = getWorkingFrames(state.workingRecord);
    if (!state.workingRecord || frames.length <= 0) {
      return;
    }
    const activeFrameId = getActiveFrameId(state.workingRecord);
    const defaultFrameId = getDefaultFrameId(state.workingRecord);
    frames.forEach((frame, frameIndex) => {
      const frameEl = document.createElement("button");
      frameEl.type = "button";
      frameEl.className = "tile-art-frame-card" + (String(frame.id || "").trim() === activeFrameId ? " is-active" : "") + (frame.enabled === false ? " is-disabled" : "");
      frameEl.setAttribute("data-frame-id", String(frame.id || "").trim());
      frameEl.draggable = true;
      frameEl.title = `${String(frame.id || `frame_${frameIndex}`)}${frame.enabled === false ? " (disabled)" : ""}`;
      frameEl.setAttribute("aria-label", `Open ${String(frame.id || `frame_${frameIndex}`)} frame`);
      frameEl.addEventListener("click", () => {
        selectAnimationFrame(frame.id, { statusMessage: `Editing ${String(frame.id || "").trim()}.` });
      });
      frameEl.addEventListener("contextmenu", (event) => {
        event.preventDefault();
        event.stopPropagation();
        const frameId = String(frame.id || "").trim();
        openContextMenuAtPoint(scope.atTooltip, event.clientX, event.clientY, [
          ...(frameId !== defaultFrameId
            ? [menuItem("<span>Set Default Frame</span>", () => {
              state.workingRecord = normalizeWorkingGraphicRecord(state.recordType, {
                ...state.workingRecord,
                defaultFrame: frameId,
              });
              refreshDirtyState();
              refreshHeader();
              renderAnimationTimeline();
              updateStatus(`${frameId} is now the default frame.`, false);
            })]
            : []),
          menuItem("<span>Duplicate</span>", () => {
            const duplicateId = getNextFrameId(state.workingRecord);
            applyFrameSet([
              ...frames,
              {
                ...cloneValue(frame),
                id: duplicateId,
                index: frames.length,
                rows: cloneRows(frame.rows),
              },
            ], {
              activeFrameId: duplicateId,
              statusMessage: `Duplicated ${frameId}.`,
            });
          }),
          menuItem(`<span>${frame.enabled === false ? "Enable" : "Disable"}</span>`, () => {
            applyFrameSet(frames.map((entry) => (
              String(entry.id || "").trim() === frameId
                ? { ...entry, enabled: entry.enabled === false }
                : entry
            )), {
              activeFrameId: frameId,
              statusMessage: `${frame.enabled === false ? "Enabled" : "Disabled"} ${frameId}.`,
            });
          }),
          menuItem("<span>Delete</span>", () => {
            if (frames.length <= 1) {
              updateStatus("At least one frame is required.", true);
              return;
            }
            const remainingFrames = frames.filter((entry) => String(entry.id || "").trim() !== frameId);
            const fallbackActiveFrameId = remainingFrames[Math.min(frameIndex, remainingFrames.length - 1)]?.id || remainingFrames[0]?.id || "";
            const fallbackDefaultFrameId = String(state.workingRecord?.defaultFrame || "").trim() === frameId
              ? String(remainingFrames[0]?.id || "")
              : String(state.workingRecord?.defaultFrame || "").trim();
            applyFrameSet(remainingFrames, {
              activeFrameId: fallbackActiveFrameId,
              defaultFrameId: fallbackDefaultFrameId,
              statusMessage: `Deleted ${frameId}.`,
            });
          }, "", { disabled: frames.length <= 1 }),
        ], "tile-art-frame-context");
      });
      frameEl.addEventListener("dragstart", (event) => {
        state.frameDragId = String(frame.id || "").trim();
        clearFrameDropCue();
        if (event.dataTransfer) {
          event.dataTransfer.effectAllowed = "move";
          event.dataTransfer.setData("text/plain", state.frameDragId);
        }
        frameEl.classList.add("is-dragging");
      });
      frameEl.addEventListener("dragend", () => {
        state.frameDragId = "";
        frameEl.classList.remove("is-dragging");
        clearFrameDropCue();
      });
      frameEl.addEventListener("dragover", (event) => {
        if (!state.frameDragId || state.frameDragId === String(frame.id || "").trim()) {
          return;
        }
        event.preventDefault();
        const targetRect = frameEl.getBoundingClientRect();
        setFrameDropCue(String(frame.id || "").trim(), event.clientX >= targetRect.left + (targetRect.width / 2) ? "after" : "before");
        if (event.dataTransfer) {
          event.dataTransfer.dropEffect = "move";
        }
      });
      frameEl.addEventListener("dragleave", () => {
        if (state.frameDropTargetId === String(frame.id || "").trim()) {
          clearFrameDropCue();
        }
      });
      frameEl.addEventListener("drop", (event) => {
        const draggedId = String(state.frameDragId || event.dataTransfer?.getData?.("text/plain") || "").trim();
        const targetId = String(frame.id || "").trim();
        if (!draggedId || draggedId === targetId) {
          return;
        }
        event.preventDefault();
        event.stopPropagation();
        const orderedFrames = frames.slice();
        const draggedIndex = orderedFrames.findIndex((entry) => String(entry.id || "").trim() === draggedId);
        const targetIndex = orderedFrames.findIndex((entry) => String(entry.id || "").trim() === targetId);
        if (draggedIndex < 0 || targetIndex < 0) {
          return;
        }
        const [draggedFrame] = orderedFrames.splice(draggedIndex, 1);
        const targetRect = frameEl.getBoundingClientRect();
        const insertIndex = event.clientX >= targetRect.left + (targetRect.width / 2)
          ? targetIndex + (draggedIndex < targetIndex ? 0 : 1)
          : targetIndex + (draggedIndex < targetIndex ? -1 : 0);
        orderedFrames.splice(Math.max(0, Math.min(insertIndex, orderedFrames.length)), 0, draggedFrame);
        clearFrameDropCue();
        applyFrameSet(orderedFrames, {
          activeFrameId: draggedId,
          statusMessage: "Reordered animation frames.",
        });
      });

      const previewEl = document.createElement("div");
      previewEl.className = "tile-art-frame-preview";
      const previewRecord = {
        width: TILE_ART_SIZE,
        height: TILE_ART_SIZE,
        pixelScale: Math.max(1, Number(state.workingRecord?.pixelScale) || 2),
        rows: cloneRows(frame.rows),
      };
      const previewUrl = buildSpritePreviewDataUrl(previewRecord, 2);
      if (previewUrl) {
        const imageEl = document.createElement("img");
        imageEl.className = "tile-art-frame-preview-image";
        imageEl.src = previewUrl;
        imageEl.alt = "";
        previewEl.appendChild(imageEl);
      }
      const labelEl = document.createElement("div");
      labelEl.className = "tile-art-frame-label";
      const statusIcon = frame.enabled === false ? "x" : "check";
      const defaultIcon = String(frame.id || "").trim() === defaultFrameId ? "key" : "";
      labelEl.innerHTML = ""
        + '<span class="tile-art-frame-label-icons">'
        + `<span class="tile-art-frame-label-icon is-${statusIcon}" aria-hidden="true">${frame.enabled === false ? "X" : "✓"}</span>`
        + (defaultIcon ? '<span class="tile-art-frame-label-icon is-key" aria-hidden="true">K</span>' : "")
        + "</span>"
        + `<span class="tile-art-frame-label-text">${String(frame.id || `frame_${frameIndex}`)}</span>`;
      frameEl.appendChild(previewEl);
      frameEl.appendChild(labelEl);
      state.animationTimelineEl.appendChild(frameEl);
    });

    const addFrameBtnEl = document.createElement("button");
    addFrameBtnEl.type = "button";
    addFrameBtnEl.className = "tile-art-frame-card tile-art-frame-add-btn";
    addFrameBtnEl.title = "Add animation frame";
    addFrameBtnEl.setAttribute("aria-label", "Add animation frame");
    addFrameBtnEl.innerHTML = '<span class="tile-art-frame-add-icon" aria-hidden="true">+</span>';
    addFrameBtnEl.addEventListener("dragover", (event) => {
      if (!state.frameDragId) {
        return;
      }
      event.preventDefault();
      addFrameBtnEl.classList.add("is-drop-after");
      if (event.dataTransfer) {
        event.dataTransfer.dropEffect = "move";
      }
    });
    addFrameBtnEl.addEventListener("dragleave", () => {
      addFrameBtnEl.classList.remove("is-drop-after");
    });
    addFrameBtnEl.addEventListener("drop", (event) => {
      const draggedId = String(state.frameDragId || event.dataTransfer?.getData?.("text/plain") || "").trim();
      if (!draggedId) {
        return;
      }
      event.preventDefault();
      addFrameBtnEl.classList.remove("is-drop-after");
      clearFrameDropCue();
      const orderedFrames = frames.slice();
      const draggedIndex = orderedFrames.findIndex((entry) => String(entry.id || "").trim() === draggedId);
      if (draggedIndex < 0) {
        return;
      }
      const [draggedFrame] = orderedFrames.splice(draggedIndex, 1);
      orderedFrames.push(draggedFrame);
      applyFrameSet(orderedFrames, {
        activeFrameId: draggedId,
        statusMessage: "Reordered animation frames.",
      });
    });
    addFrameBtnEl.addEventListener("click", () => {
      const nextFrameId = getNextFrameId(state.workingRecord);
      applyFrameSet([
        ...frames,
        {
          id: nextFrameId,
          enabled: true,
          index: frames.length,
          rows: Array.from({ length: TILE_ART_SIZE }, () => ".".repeat(TILE_ART_SIZE)),
        },
      ], {
        activeFrameId: nextFrameId,
        statusMessage: `Added ${nextFrameId}.`,
      });
    });
    state.animationTimelineAddHostEl.appendChild(addFrameBtnEl);
    if (state.animationsVisible === true) {
      scheduleActiveTabWindowLayoutSync();
    }
  }

  function renderTags() {
    if (!state.tagListEl) {
      return;
    }
    const tags = normalizeEditorTags(state.workingRecord?.tags);
    state.tagListEl.innerHTML = "";
    if (tags.length <= 0) {
      const emptyEl = document.createElement("div");
      emptyEl.className = "tile-art-tags-empty";
      emptyEl.textContent = "No tags yet. Type a tag and press Enter.";
      state.tagListEl.appendChild(emptyEl);
      return;
    }
    tags.forEach((tag) => {
      const chipEl = document.createElement("button");
      chipEl.type = "button";
      chipEl.className = "tile-art-tag-chip";
      chipEl.title = `Remove tag "${tag}"`;
      chipEl.setAttribute("aria-label", `Remove tag ${tag}`);
      chipEl.innerHTML = `<span class="tile-art-tag-chip-label">${tag}</span><span class="tile-art-tag-chip-remove" aria-hidden="true">x</span>`;
      chipEl.addEventListener("click", () => {
        if (!state.workingRecord) {
          return;
        }
        state.workingRecord = {
          ...state.workingRecord,
          tags: normalizeEditorTags((state.workingRecord.tags || []).filter((entry) => String(entry || "").toLocaleLowerCase() !== tag.toLocaleLowerCase())),
        };
        refreshDirtyState();
        renderTags();
        refreshHeader();
        updateStatus("Unsaved tile art changes.", false);
        if (state.activeTab === "tags") {
          scheduleActiveTabWindowLayoutSync();
        }
      });
      state.tagListEl.appendChild(chipEl);
    });
  }

  function addTag(rawValue) {
    if (!state.workingRecord) {
      return false;
    }
    const normalizedTag = normalizeEditorTagValue(rawValue);
    if (!normalizedTag) {
      return false;
    }
    const nextTags = normalizeEditorTags([...(state.workingRecord.tags || []), normalizedTag]);
    if (nextTags.length === normalizeEditorTags(state.workingRecord.tags).length) {
      if (state.tagInputEl) {
        state.tagInputEl.value = "";
      }
      updateStatus("That tag already exists.", false);
      return false;
    }
    state.workingRecord = {
      ...state.workingRecord,
      tags: nextTags,
    };
    refreshDirtyState();
    if (state.tagInputEl) {
      state.tagInputEl.value = "";
    }
    renderTags();
    refreshHeader();
    updateStatus("Unsaved tile art changes.", false);
    if (state.activeTab === "tags") {
      scheduleActiveTabWindowLayoutSync();
    }
    return true;
  }

  async function exportTags() {
    if (!state.workingRecord) {
      return false;
    }
    const serialized = serializeEditorTags(state.workingRecord.tags);
    return copyTextWithClipboardFallback(
      serialized,
      "Copy tag export string",
      () => updateStatus("Copied tile tags to clipboard.", false),
      (clipboardAvailable) => updateStatus(
        clipboardAvailable
          ? "Clipboard unavailable. Tag export string opened for manual copy."
          : "Tag export string ready to copy.",
        false,
      ),
    );
  }

  function importTags() {
    if (!state.workingRecord) {
      return false;
    }
    const pasted = promptForImportText("Paste tag export string", "");
    if (pasted === null) {
      return false;
    }
    const importedTags = parseImportedEditorTags(pasted);
    if (importedTags.length <= 0) {
      updateStatus("No valid tags were found in that import string.", true);
      return false;
    }
    const previousTags = normalizeEditorTags(state.workingRecord.tags);
    const nextTags = normalizeEditorTags([...(state.workingRecord.tags || []), ...importedTags]);
    if (nextTags.length === previousTags.length) {
      updateStatus("All imported tags already exist on this tile.", false);
      return false;
    }
    state.workingRecord = {
      ...state.workingRecord,
      tags: nextTags,
    };
    refreshDirtyState();
    renderTags();
    refreshHeader();
    updateStatus(`Imported ${nextTags.length - previousTags.length} tag${nextTags.length - previousTags.length === 1 ? "" : "s"}.`, false);
    if (state.activeTab === "tags") {
      scheduleActiveTabWindowLayoutSync();
    }
    return true;
  }

  function renderEditor(statusMessage) {
    refreshHeader();
    renderToolMenu();
    renderAnimationSpeedMenu();
    renderAnimationPlaybackMenu();
    renderAnimationTimeline();
    renderSwatches();
    renderGrid();
    renderTags();
    updateStatus(
      statusMessage || (
        state.dirty
          ? "Unsaved tile art changes."
          : "Select a color, paint the 16x16 grid, then save."
      ),
      false,
    );
    if (state.activeTab === "tags") {
      scheduleActiveTabWindowLayoutSync();
    }
  }

  function confirmDiscardIfDirty() {
    return confirmDiscardChanges("Discard unsaved tile art changes?", state.dirty);
  }

  async function save() {
    if (!state.workingRecord || !state.recordId || state.saving) {
      return false;
    }
    const imagesPayload = cloneValue(scope.ensureDocumentContentPayload?.("images", { schemaVersion: 1, images: [] })) || { schemaVersion: 1, images: [] };
    const nextImages = Array.isArray(imagesPayload.images) ? imagesPayload.images.slice() : [];
    const recordIndex = nextImages.findIndex((entry) => entry && typeof entry === "object" && !Array.isArray(entry) && String(entry.id || "").trim() === state.recordId);
    if (recordIndex < 0) {
      updateStatus("Graphic record no longer exists.", true);
      return false;
    }
    state.saving = true;
    refreshHeader();
    updateStatus("Saving graphic art...", false);
    const existingRecord = nextImages[recordIndex] || {};
    const nextRecord = normalizeImageRecordForSave({
      ...cloneValue(existingRecord),
      ...cloneValue(state.workingRecord),
      id: state.recordId,
      name: String(state.nameInputEl?.value ?? state.workingRecord?.name ?? "").trim(),
      width: TILE_ART_SIZE,
      height: TILE_ART_SIZE,
      pixelScale: Math.max(1, Number(state.workingRecord?.pixelScale) || 2),
      rows: cloneRows(state.workingRecord.rows),
      frames: getWorkingFrames(state.workingRecord),
      defaultFrame: getDefaultFrameId(state.workingRecord),
      speed: Number.isFinite(Number(state.workingRecord?.speed)) && Number(state.workingRecord?.speed) >= 0 ? Number(state.workingRecord.speed) : 0,
      playback: normalizeImagePlayback(state.workingRecord?.playback),
      roles: state.recordType === "tile"
        ? Array.from(new Set([...normalizeRoleList(existingRecord.roles), "tile"]))
        : (
          state.recordType === "sprite"
            ? Array.from(new Set([...normalizeRoleList(existingRecord.roles), "sprite"]))
            : normalizeRoleList(existingRecord.roles).filter((entry) => entry !== "sprite")
        ),
      tileSymbol: state.recordType === "tile"
        ? (String(state.workingRecord?.tileSymbol || existingRecord?.tileSymbol || existingRecord?.symbol || "").trim().charAt(0) || String(state.recordId || "T").charAt(0) || "T")
        : String(existingRecord?.tileSymbol || state.workingRecord?.tileSymbol || "").trim().charAt(0),
    });
    nextRecord.tags = normalizeEditorTags(nextRecord.tags);
    nextImages[recordIndex] = nextRecord;
    const nextPayload = {
      schemaVersion: typeof imagesPayload.schemaVersion === "number" ? imagesPayload.schemaVersion : 1,
      images: nextImages,
    };
    try {
      await scope.persistContentPayload?.("images", nextPayload);
      if (typeof scope.reloadGraphicsContentFromApi === "function") {
        try {
          await scope.reloadGraphicsContentFromApi();
        } catch {
          scope.applyContentPayloadToRuntime?.("images", nextPayload);
        }
      } else {
        scope.applyContentPayloadToRuntime?.("images", nextPayload);
      }
      scope.cacheStandaloneMapBootstrap?.(scope.mapId);
      scope.renderPaintPalette?.();
      scope.renderInstancePalette?.();
      scope.refreshWorldOverviewWindow?.();
      state.workingRecord = normalizeWorkingGraphicRecord(state.recordType, nextRecord);
      state.activeFrameId = getActiveFrameId(state.workingRecord);
      state.savedRecordSnapshot = serializeWorkingRecord(state.workingRecord);
      refreshDirtyState();
      scope.setStatus?.("Saved graphic art for " + (nextRecord.name || state.recordId) + ".", false);
      renderEditor("Saved graphic art.");
      return true;
    } catch (error) {
      const message = String(error || "Graphic save failed.");
      scope.setStatus?.(message, true);
      updateStatus(message, true);
      return false;
    } finally {
      state.saving = false;
      refreshHeader();
    }
  }

  function applyRecord(recordType, recordId, record) {
    state.recordType = recordType === "other" ? "other" : (recordType === "sprite" ? "sprite" : "tile");
    state.recordId = String(recordId || "").trim();
    state.workingRecord = normalizeWorkingGraphicRecord(state.recordType, record);
    state.activePaintSymbol = pickDefaultPaintSymbol(state.workingRecord);
    state.activeSecondarySymbol = getAlternatePaintSymbol(state.workingRecord, state.activePaintSymbol);
    state.eyedropperActive = false;
    state.suppressEyedropperUntilAltRelease = false;
    state.activeTool = "pencil";
    state.activeShapeKind = "rectangle";
    state.activeShapeVariant = "outline";
    state.activeEraseKind = "rectangle";
    state.openMenu = "";
    state.activeTab = "design";
    state.animationsVisible = false;
    state.animationSpeedMenuOpen = false;
    state.animationPlaybackMenuOpen = false;
    state.activeFrameId = getDefaultFrameId(state.workingRecord);
    state.templateDragDepth = 0;
    setTemplateDragActive(false);
    state.savedRecordSnapshot = serializeWorkingRecord(state.workingRecord);
    refreshDirtyState();
    resetLocalHistory(state.workingRecord);
    state.dragSession = null;
    state.editingName = false;
    renderEditor();
    scheduleActiveTabWindowLayoutSync();
  }

  function ensureShell() {
    if (state.shellEl && state.shellEl.isConnected) {
      return state.shellEl;
    }
    const shellEl = document.createElement("div");
    shellEl.className = "tool-popout-window tile-art-window";
    shellEl.setAttribute("data-panel-key", TILE_ART_WINDOW_KEY);

    const titlebarEl = document.createElement("div");
    titlebarEl.className = "tool-popout-titlebar";
    titlebarEl.innerHTML =
      '<div class="tool-popout-title">Graphic Painter</div>' +
      '<div class="tool-popout-hint">16x16 art editor</div>' +
      '<button type="button" class="tool-popout-close-btn" title="Close graphic painter" aria-label="Close graphic painter">X</button>';

    const bodyEl = document.createElement("div");
    bodyEl.className = "tool-popout-body tile-art-body";

    const cardEl = document.createElement("div");
    cardEl.className = "tile-art-card";

    const headEl = document.createElement("div");
    headEl.className = "tile-art-head";
    const titleRowEl = document.createElement("div");
    titleRowEl.className = "tile-art-title-row";
    const titleStackEl = document.createElement("div");
    titleStackEl.className = "tile-art-title-stack";
    const titleDisplayEl = document.createElement("div");
    titleDisplayEl.className = "tile-art-title-display";
    const titleEl = document.createElement("div");
    titleEl.className = "tile-art-record-title";
    const nameInputEl = document.createElement("input");
    nameInputEl.type = "text";
    nameInputEl.className = "tile-art-title-input hidden";
    nameInputEl.maxLength = 80;
    nameInputEl.spellcheck = false;
    nameInputEl.placeholder = "Graphic name";
    nameInputEl.addEventListener("input", () => {
      if (!state.workingRecord) {
        return;
      }
      const nextName = String(nameInputEl.value || "");
      if (String(state.workingRecord.name || "") === nextName) {
        return;
      }
      state.workingRecord = {
        ...state.workingRecord,
        name: nextName,
      };
      refreshDirtyState();
      refreshHeader();
      updateStatus("Unsaved tile art changes.", false);
    });
    nameInputEl.addEventListener("keydown", (event) => {
      if (event.key === "Enter") {
        event.preventDefault();
        setNameEditing(false, { focus: false });
        return;
      }
      if (event.key === "Escape") {
        event.preventDefault();
        if (state.workingRecord) {
          nameInputEl.value = String(state.workingRecord.name || "");
        }
        setNameEditing(false, { focus: false });
      }
    });
    nameInputEl.addEventListener("blur", () => {
      if (state.editingName) {
        setNameEditing(false, { focus: false });
      }
    });
    titleStackEl.appendChild(titleEl);
    titleStackEl.appendChild(nameInputEl);
    const titleEditBtnEl = document.createElement("button");
    titleEditBtnEl.type = "button";
    titleEditBtnEl.className = "tile-art-title-edit-btn";
    titleEditBtnEl.title = "Rename graphic";
    titleEditBtnEl.setAttribute("aria-label", "Rename graphic");
    titleEditBtnEl.innerHTML = '<span class="tile-art-title-edit-icon" aria-hidden="true"></span>';
    titleEditBtnEl.addEventListener("click", () => {
      setNameEditing(true);
    });
    const subtitleEl = document.createElement("div");
    subtitleEl.className = "tile-art-record-meta";
    titleDisplayEl.appendChild(titleEl);
    titleDisplayEl.appendChild(titleEditBtnEl);
    titleStackEl.appendChild(titleDisplayEl);
    titleStackEl.appendChild(nameInputEl);
    titleStackEl.appendChild(subtitleEl);

    const tabButtonsEl = document.createElement("div");
    tabButtonsEl.className = "tile-art-tabs";
    const shortcutHelpBtnEl = document.createElement("button");
    shortcutHelpBtnEl.type = "button";
    shortcutHelpBtnEl.className = "tile-art-tab-help-btn";
    shortcutHelpBtnEl.title = "Painter shortcuts";
    shortcutHelpBtnEl.setAttribute("aria-label", "Show painter shortcut guide");
    shortcutHelpBtnEl.innerHTML = '<span class="tile-art-tab-help-icon" aria-hidden="true"></span>';
    shortcutHelpBtnEl.addEventListener("mouseenter", () => {
      openShortcutHelp();
    });
    shortcutHelpBtnEl.addEventListener("focus", () => {
      openShortcutHelp();
    });
    shortcutHelpBtnEl.addEventListener("mouseleave", () => {
      closeShortcutHelp();
    });
    shortcutHelpBtnEl.addEventListener("blur", () => {
      closeShortcutHelp();
    });
    const designTabBtnEl = document.createElement("button");
    designTabBtnEl.type = "button";
    designTabBtnEl.className = "tile-art-tab-btn";
    designTabBtnEl.textContent = "Design";
    designTabBtnEl.addEventListener("click", () => {
      setActiveTab("design");
    });
    const animationsToggleBtnEl = document.createElement("button");
    animationsToggleBtnEl.type = "button";
    animationsToggleBtnEl.className = "tile-art-tab-btn";
    animationsToggleBtnEl.textContent = "Animations";
    animationsToggleBtnEl.setAttribute("aria-pressed", "false");
    animationsToggleBtnEl.addEventListener("click", () => {
      state.animationsVisible = state.animationsVisible !== true;
      refreshHeader();
      renderAnimationSpeedMenu();
      renderAnimationPlaybackMenu();
      renderAnimationTimeline();
      scheduleActiveTabWindowLayoutSync();
    });
    const tagsTabBtnEl = document.createElement("button");
    tagsTabBtnEl.type = "button";
    tagsTabBtnEl.className = "tile-art-tab-btn";
    tagsTabBtnEl.textContent = "Tags";
    tagsTabBtnEl.addEventListener("click", () => {
      setActiveTab("tags");
    });
    tabButtonsEl.appendChild(shortcutHelpBtnEl);
    tabButtonsEl.appendChild(designTabBtnEl);
    tabButtonsEl.appendChild(animationsToggleBtnEl);
    tabButtonsEl.appendChild(tagsTabBtnEl);
    titleRowEl.appendChild(titleStackEl);
    titleRowEl.appendChild(tabButtonsEl);
    headEl.appendChild(titleRowEl);

    const designPaneEl = document.createElement("div");
    designPaneEl.className = "tile-art-pane tile-art-design-pane";
    const animationPaneEl = document.createElement("div");
    animationPaneEl.className = "tile-art-animation-pane hidden";
    const animationControlsEl = document.createElement("div");
    animationControlsEl.className = "tile-art-animation-controls";
    const animationToolsLabelEl = document.createElement("div");
    animationToolsLabelEl.className = "tile-art-tools-label";
    animationToolsLabelEl.textContent = "Tools";
    const animationControlButtonsEl = document.createElement("div");
    animationControlButtonsEl.className = "tile-art-tool-buttons";
    const animationSpeedBtnEl = document.createElement("button");
    animationSpeedBtnEl.type = "button";
    animationSpeedBtnEl.className = "tile-art-tool-btn";
    animationSpeedBtnEl.title = "Animation speed";
    animationSpeedBtnEl.setAttribute("aria-label", "Animation speed");
    animationSpeedBtnEl.innerHTML = '<span class="tile-art-tool-icon tile-art-tool-icon-speed" aria-hidden="true"></span>';
    animationSpeedBtnEl.addEventListener("click", () => {
      toggleAnimationSpeedMenu();
    });
    animationControlButtonsEl.appendChild(animationSpeedBtnEl);
    const animationPlaybackBtnEl = document.createElement("button");
    animationPlaybackBtnEl.type = "button";
    animationPlaybackBtnEl.className = "tile-art-tool-btn";
    animationPlaybackBtnEl.title = "Playback mode";
    animationPlaybackBtnEl.setAttribute("aria-label", "Playback mode");
    animationPlaybackBtnEl.innerHTML = ''
      + '<span class="tile-art-tool-icon tile-art-tool-icon-playback" aria-hidden="true">'
      + '<span class="tile-art-tool-icon-playback-arrow-a"></span>'
      + '<span class="tile-art-tool-icon-playback-arrow-b"></span>'
      + '</span>';
    animationPlaybackBtnEl.addEventListener("click", () => {
      toggleAnimationPlaybackMenu();
    });
    animationControlButtonsEl.appendChild(animationPlaybackBtnEl);
    const animationPreviewBtnEl = document.createElement("button");
    animationPreviewBtnEl.type = "button";
    animationPreviewBtnEl.className = "tile-art-tool-btn";
    animationPreviewBtnEl.title = "Open animation preview";
    animationPreviewBtnEl.setAttribute("aria-label", "Open animation preview");
    animationPreviewBtnEl.innerHTML = '<span class="tile-art-tool-icon tile-art-tool-icon-play" aria-hidden="true"></span>';
    animationPreviewBtnEl.addEventListener("click", () => {
      openAnimationPreview();
    });
    animationControlButtonsEl.appendChild(animationPreviewBtnEl);
    animationControlsEl.appendChild(animationToolsLabelEl);
    animationControlsEl.appendChild(animationControlButtonsEl);
    const animationTimelineRowEl = document.createElement("div");
    animationTimelineRowEl.className = "tile-art-animation-timeline-row";
    const animationTimelineEl = document.createElement("div");
    animationTimelineEl.className = "tile-art-animation-timeline";
    const animationTimelineAddHostEl = document.createElement("div");
    animationTimelineAddHostEl.className = "tile-art-animation-add-host";
    animationTimelineRowEl.appendChild(animationTimelineEl);
    animationTimelineRowEl.appendChild(animationTimelineAddHostEl);
    animationPaneEl.appendChild(animationControlsEl);
    animationPaneEl.appendChild(animationTimelineRowEl);

    const currentColorEl = document.createElement("div");
    currentColorEl.className = "tile-art-current";
    currentColorEl.innerHTML =
      '<div class="tile-art-current-row">' +
        '<div class="tile-art-current-swatch tile-art-current-primary">' +
          '<span class="tile-art-current-indicator is-primary" aria-hidden="true">' +
            '<span class="tile-art-current-indicator-left"></span>' +
            '<span class="tile-art-current-indicator-right"></span>' +
          '</span>' +
        '</div>' +
      '</div>' +
      '<div class="tile-art-current-row">' +
        '<div class="tile-art-current-swatch tile-art-current-secondary">' +
          '<span class="tile-art-current-indicator is-secondary" aria-hidden="true">' +
            '<span class="tile-art-current-indicator-left"></span>' +
            '<span class="tile-art-current-indicator-right"></span>' +
          '</span>' +
        '</div>' +
      '</div>';
    const currentSwatchEl = currentColorEl.querySelector(".tile-art-current-primary");
    const secondarySwatchEl = currentColorEl.querySelector(".tile-art-current-secondary");
    const opacitySummaryEl = null;

    const toolsEl = document.createElement("div");
    toolsEl.className = "tile-art-tools";
    const toolsLabelEl = document.createElement("div");
    toolsLabelEl.className = "tile-art-tools-label";
    toolsLabelEl.textContent = "Tools";
    const toolButtonsEl = document.createElement("div");
    toolButtonsEl.className = "tile-art-tool-buttons";
    const pencilToolBtnEl = document.createElement("button");
    pencilToolBtnEl.type = "button";
    pencilToolBtnEl.className = "tile-art-tool-btn";
    pencilToolBtnEl.title = "Pencil tool";
    pencilToolBtnEl.setAttribute("aria-label", "Pencil tool");
    pencilToolBtnEl.innerHTML = '<span class="tile-art-tool-icon tile-art-tool-icon-pencil" aria-hidden="true"></span>';
    pencilToolBtnEl.addEventListener("click", () => {
      setActiveTool("pencil");
    });
    const bucketToolBtnEl = document.createElement("button");
    bucketToolBtnEl.type = "button";
    bucketToolBtnEl.className = "tile-art-tool-btn";
    bucketToolBtnEl.title = "Paint bucket tool";
    bucketToolBtnEl.setAttribute("aria-label", "Paint bucket tool");
    bucketToolBtnEl.innerHTML = '<span class="tile-art-tool-icon tile-art-tool-icon-bucket" aria-hidden="true"></span>';
    bucketToolBtnEl.addEventListener("click", () => {
      setActiveTool("bucket");
    });
    const shapeToolBtnEl = document.createElement("button");
    shapeToolBtnEl.type = "button";
    shapeToolBtnEl.className = "tile-art-tool-btn";
    shapeToolBtnEl.title = "Shape tools";
    shapeToolBtnEl.setAttribute("aria-label", "Shape tools");
    shapeToolBtnEl.innerHTML = buildCurrentShapeToolIconMarkup(state);
    shapeToolBtnEl.addEventListener("click", () => {
      setToolMenu("shape");
    });
    const eraserToolBtnEl = document.createElement("button");
    eraserToolBtnEl.type = "button";
    eraserToolBtnEl.className = "tile-art-tool-btn";
    eraserToolBtnEl.title = "Shape erasers";
    eraserToolBtnEl.setAttribute("aria-label", "Shape erasers");
    eraserToolBtnEl.innerHTML = buildCurrentEraseToolIconMarkup(state);
    eraserToolBtnEl.addEventListener("click", () => {
      setToolMenu("erase");
    });
    const transformToolBtnEl = document.createElement("button");
    transformToolBtnEl.type = "button";
    transformToolBtnEl.className = "tile-art-tool-btn";
    transformToolBtnEl.title = "Transform image";
    transformToolBtnEl.setAttribute("aria-label", "Transform image");
    transformToolBtnEl.innerHTML = '<span class="tile-art-tool-icon tile-art-tool-icon-transform" aria-hidden="true"></span>';
    transformToolBtnEl.addEventListener("click", () => {
      setToolMenu("transform");
    });
    const opacityToolBtnEl = document.createElement("button");
    opacityToolBtnEl.type = "button";
    opacityToolBtnEl.className = "tile-art-tool-btn";
    opacityToolBtnEl.title = "Image opacity: 1";
    opacityToolBtnEl.setAttribute("aria-label", "Image opacity: 1");
    opacityToolBtnEl.innerHTML = '<span class="tile-art-tool-icon tile-art-tool-icon-opacity" aria-hidden="true"></span>';
    opacityToolBtnEl.addEventListener("click", () => {
      setToolMenu("opacity");
    });
    const shiftToolBtnEl = document.createElement("button");
    shiftToolBtnEl.type = "button";
    shiftToolBtnEl.className = "tile-art-tool-btn";
    shiftToolBtnEl.title = "Shift the full image";
    shiftToolBtnEl.setAttribute("aria-label", "Shift the full image");
    shiftToolBtnEl.innerHTML = '<span class="tile-art-tool-icon tile-art-tool-icon-shift" aria-hidden="true"></span>';
    shiftToolBtnEl.addEventListener("click", () => {
      setActiveTool("shift");
    });
    toolButtonsEl.appendChild(pencilToolBtnEl);
    toolButtonsEl.appendChild(bucketToolBtnEl);
    toolButtonsEl.appendChild(shapeToolBtnEl);
    toolButtonsEl.appendChild(eraserToolBtnEl);
    toolButtonsEl.appendChild(transformToolBtnEl);
    toolButtonsEl.appendChild(opacityToolBtnEl);
    toolButtonsEl.appendChild(shiftToolBtnEl);
    toolsEl.appendChild(toolsLabelEl);
    toolsEl.appendChild(toolButtonsEl);

    const colorStripEl = document.createElement("div");
    colorStripEl.className = "tile-art-color-strip";
    const swatchesEl = document.createElement("div");
    swatchesEl.className = "tile-art-swatches";
    const previewHintEl = document.createElement("div");
    previewHintEl.className = "tile-art-preview-hint";
    previewHintEl.textContent = "Shift click a color to set the preview background";

    const gridStageEl = document.createElement("div");
    gridStageEl.className = "tile-art-grid-stage";
    const usedSwatchesEl = document.createElement("div");
    usedSwatchesEl.className = "tile-art-used-swatches";

    const gridWrapEl = document.createElement("div");
    gridWrapEl.className = "tile-art-grid-wrap";
    const gridEl = document.createElement("div");
    gridEl.className = "tile-art-grid";
    gridEl.addEventListener("pointermove", (event) => {
      handleCapturedGridPointerMove(event);
    });
    gridEl.addEventListener("pointerup", (event) => {
      completePointerInteraction(event.pointerId);
    });
    gridEl.addEventListener("pointercancel", (event) => {
      completePointerInteraction(event.pointerId);
    });
    gridEl.addEventListener("lostpointercapture", (event) => {
      if (state.activePointerId != null && Number(event.pointerId) === state.activePointerId) {
        clearPointerInteractionState();
      }
    });
    gridWrapEl.appendChild(gridEl);
    gridStageEl.appendChild(usedSwatchesEl);
    gridStageEl.appendChild(gridWrapEl);

    designPaneEl.appendChild(animationPaneEl);
    designPaneEl.appendChild(toolsEl);
    colorStripEl.appendChild(currentColorEl);
    colorStripEl.appendChild(swatchesEl);
    designPaneEl.appendChild(colorStripEl);
    designPaneEl.appendChild(previewHintEl);
    designPaneEl.appendChild(gridStageEl);

    const tagsPaneEl = document.createElement("div");
    tagsPaneEl.className = "tile-art-pane tile-art-tags-pane hidden";
    const tagFieldEl = document.createElement("label");
    tagFieldEl.className = "tile-art-tag-field";
    const tagHeadEl = document.createElement("div");
    tagHeadEl.className = "tile-art-tag-head";
    const tagLabelEl = document.createElement("span");
    tagLabelEl.className = "tile-art-tag-label";
    tagLabelEl.textContent = "Add Tag";
    const tagActionsEl = document.createElement("div");
    tagActionsEl.className = "tile-art-tag-actions";
    const exportTagsBtnEl = document.createElement("button");
    exportTagsBtnEl.type = "button";
    exportTagsBtnEl.className = "mini-btn";
    exportTagsBtnEl.textContent = "Export";
    exportTagsBtnEl.addEventListener("click", () => {
      void exportTags();
    });
    const importTagsBtnEl = document.createElement("button");
    importTagsBtnEl.type = "button";
    importTagsBtnEl.className = "mini-btn";
    importTagsBtnEl.textContent = "Import";
    importTagsBtnEl.addEventListener("click", () => {
      importTags();
    });
    const tagsSaveBtnEl = document.createElement("button");
    tagsSaveBtnEl.type = "button";
    tagsSaveBtnEl.className = "mini-btn";
    tagsSaveBtnEl.textContent = "Save";
    tagsSaveBtnEl.addEventListener("click", () => {
      void save();
    });
    tagActionsEl.appendChild(exportTagsBtnEl);
    tagActionsEl.appendChild(importTagsBtnEl);
    tagActionsEl.appendChild(tagsSaveBtnEl);
    tagHeadEl.appendChild(tagLabelEl);
    tagHeadEl.appendChild(tagActionsEl);
    const tagInputEl = document.createElement("input");
    tagInputEl.type = "text";
    tagInputEl.className = "tile-art-tag-input";
    tagInputEl.maxLength = 48;
    tagInputEl.spellcheck = false;
    tagInputEl.placeholder = "Type a tag and press Enter";
    tagInputEl.addEventListener("keydown", (event) => {
      if (event.key !== "Enter") {
        return;
      }
      event.preventDefault();
      addTag(tagInputEl.value);
    });
    const tagListEl = document.createElement("div");
    tagListEl.className = "tile-art-tag-list";
    tagFieldEl.appendChild(tagHeadEl);
    tagFieldEl.appendChild(tagInputEl);
    tagsPaneEl.appendChild(tagFieldEl);
    tagsPaneEl.appendChild(tagListEl);

    const footerEl = document.createElement("div");
    footerEl.className = "tile-art-footer";
    const statusEl = document.createElement("div");
    statusEl.className = "tile-art-status";
    const actionsEl = document.createElement("div");
    actionsEl.className = "tile-art-actions";
    const undoBtnEl = document.createElement("button");
    undoBtnEl.type = "button";
    undoBtnEl.className = "mini-btn";
    undoBtnEl.textContent = "Undo";
    undoBtnEl.addEventListener("click", () => {
      undoLocalHistory();
    });
    const redoBtnEl = document.createElement("button");
    redoBtnEl.type = "button";
    redoBtnEl.className = "mini-btn";
    redoBtnEl.textContent = "Redo";
    redoBtnEl.addEventListener("click", () => {
      redoLocalHistory();
    });
    const saveBtnEl = document.createElement("button");
    saveBtnEl.type = "button";
    saveBtnEl.className = "mini-btn";
    saveBtnEl.textContent = "Save";
    saveBtnEl.addEventListener("click", () => {
      void save();
    });
    actionsEl.appendChild(undoBtnEl);
    actionsEl.appendChild(redoBtnEl);
    actionsEl.appendChild(saveBtnEl);

    const resizeEl = document.createElement("div");
    resizeEl.className = "tool-popout-resize";

    cardEl.appendChild(headEl);
    cardEl.appendChild(designPaneEl);
    cardEl.appendChild(tagsPaneEl);
    footerEl.appendChild(statusEl);
    footerEl.appendChild(actionsEl);
    cardEl.appendChild(footerEl);
    bodyEl.appendChild(cardEl);

    const closeBtnEl = titlebarEl.querySelector(".tool-popout-close-btn");

    shellEl.appendChild(titlebarEl);
    shellEl.appendChild(bodyEl);
    shellEl.appendChild(resizeEl);

    shellEl.addEventListener("pointerdown", () => {
      focusWindow();
    });

    const handleTemplateDragEnter = (event) => {
      const descriptor = getDraggedGraphicDescriptor(event);
      if (!descriptor) {
        return;
      }
      event.preventDefault();
      event.stopPropagation();
      state.templateDragDepth += 1;
      setTemplateDragActive(true);
      if (event.dataTransfer) {
        event.dataTransfer.dropEffect = "copy";
      }
    };

    const handleTemplateDragOver = (event) => {
      const descriptor = getDraggedGraphicDescriptor(event);
      if (!descriptor) {
        return;
      }
      event.preventDefault();
      event.stopPropagation();
      setTemplateDragActive(true);
      if (event.dataTransfer) {
        event.dataTransfer.dropEffect = "copy";
      }
    };

    const handleTemplateDragLeave = (event) => {
      const descriptor = getDraggedGraphicDescriptor(event);
      if (!descriptor) {
        return;
      }
      event.preventDefault();
      event.stopPropagation();
      state.templateDragDepth = Math.max(0, Number(state.templateDragDepth) - 1);
      if (state.templateDragDepth <= 0) {
        setTemplateDragActive(false);
      }
    };

    const handleTemplateDrop = (event) => {
      const descriptor = getDraggedGraphicDescriptor(event);
      state.templateDragDepth = 0;
      setTemplateDragActive(false);
      if (!descriptor) {
        return;
      }
      event.preventDefault();
      event.stopPropagation();
      focusWindow();
      if (state.activeTab !== "design") {
        setActiveTab("design");
      }
      applyDraggedGraphicTemplate(descriptor.recordType, descriptor.recordId);
    };

    gridStageEl.addEventListener("dragenter", handleTemplateDragEnter);
    gridStageEl.addEventListener("dragover", handleTemplateDragOver);
    gridStageEl.addEventListener("dragleave", handleTemplateDragLeave);
    gridStageEl.addEventListener("drop", handleTemplateDrop);

    titlebarEl.addEventListener("pointerdown", (event) => {
      if (closeBtnEl && closeBtnEl.contains(event.target)) {
        return;
      }
      if (event.button !== 0) {
        return;
      }
      event.preventDefault();
      focusWindow();
      const layerRect = getLayerRect();
      const originLeft = Number(state.x) || 0;
      const originTop = Number(state.y) || 0;
      const startX = event.clientX;
      const startY = event.clientY;
      const move = (moveEvent) => {
        const nextRect = clampWindowRect(
          layerRect,
          originLeft + (moveEvent.clientX - startX),
          originTop + (moveEvent.clientY - startY),
          state.width,
          state.height,
        );
        state.x = nextRect.left;
        state.y = nextRect.top;
        applyWindowRect();
      };
      const up = () => {
        window.removeEventListener("pointermove", move);
        window.removeEventListener("pointerup", up);
        persistState();
      };
      window.addEventListener("pointermove", move);
      window.addEventListener("pointerup", up);
    });

    closeBtnEl?.addEventListener("click", (event) => {
      event.preventDefault();
      event.stopPropagation();
      close();
    });

    state.shellEl = shellEl;
    state.bodyEl = bodyEl;
    state.titlebarEl = titlebarEl;
    state.titleEl = titleEl;
    state.titleEditBtnEl = titleEditBtnEl;
    state.subtitleEl = subtitleEl;
    state.nameInputEl = nameInputEl;
    state.tabButtonsEl = tabButtonsEl;
    state.shortcutHelpBtnEl = shortcutHelpBtnEl;
    state.designTabBtnEl = designTabBtnEl;
    state.animationsToggleBtnEl = animationsToggleBtnEl;
    state.tagsTabBtnEl = tagsTabBtnEl;
    state.designPaneEl = designPaneEl;
    state.animationPaneEl = animationPaneEl;
    state.animationControlsEl = animationControlsEl;
    state.animationSpeedBtnEl = animationSpeedBtnEl;
    state.animationPlaybackBtnEl = animationPlaybackBtnEl;
    state.animationPreviewBtnEl = animationPreviewBtnEl;
    state.animationSpeedMenuHostEl = null;
    state.animationPlaybackMenuHostEl = null;
    state.animationTimelineEl = animationTimelineEl;
    state.animationTimelineAddHostEl = animationTimelineAddHostEl;
    state.tagsPaneEl = tagsPaneEl;
    state.tagInputEl = tagInputEl;
    state.exportTagsBtnEl = exportTagsBtnEl;
    state.importTagsBtnEl = importTagsBtnEl;
    state.tagsSaveBtnEl = tagsSaveBtnEl;
    state.tagListEl = tagListEl;
    state.currentSwatchEl = currentSwatchEl;
    state.secondarySwatchEl = secondarySwatchEl;
    state.opacitySummaryEl = opacitySummaryEl;
    state.toolButtonsEl = toolButtonsEl;
    state.pencilToolBtnEl = pencilToolBtnEl;
    state.bucketToolBtnEl = bucketToolBtnEl;
    state.shapeToolBtnEl = shapeToolBtnEl;
    state.eraserToolBtnEl = eraserToolBtnEl;
    state.transformToolBtnEl = transformToolBtnEl;
    state.opacityToolBtnEl = opacityToolBtnEl;
    state.shiftToolBtnEl = shiftToolBtnEl;
    state.toolMenuHostEl = null;
    state.swatchesEl = swatchesEl;
    state.usedSwatchesEl = usedSwatchesEl;
    state.undoBtnEl = undoBtnEl;
    state.redoBtnEl = redoBtnEl;
    state.previewHintEl = previewHintEl;
    state.gridStageEl = gridStageEl;
    state.gridEl = gridEl;
    state.gridWrapEl = gridWrapEl;
    state.saveBtnEl = saveBtnEl;
    state.footerEl = footerEl;
    state.closeBtnEl = closeBtnEl;
    state.statusEl = statusEl;
    state.resizeEl = resizeEl;
    uiScope.toolWindowLayerEl?.appendChild(shellEl);
    applyWindowRect();
    shellEl.classList.toggle("hidden", state.visible !== true);
    renderEditor();
    scheduleActiveTabWindowLayoutSync();
    return shellEl;
  }

  function open(recordTypeOrId, maybeRecordId) {
    const recordType = maybeRecordId == null
      ? "tile"
      : (recordTypeOrId === "sprite" || recordTypeOrId === "other" ? recordTypeOrId : "tile");
    const normalizedId = String(maybeRecordId == null ? recordTypeOrId : maybeRecordId || "").trim();
    if (!normalizedId) {
      return false;
    }
    if (state.recordId && (state.recordId !== normalizedId || state.recordType !== recordType) && !confirmDiscardIfDirty()) {
      return false;
    }
    const record = getRecordById(recordType, normalizedId);
    if (!record) {
      scope.setStatus?.("Graphic not found: " + normalizedId, true);
      return false;
    }
    ensureShell();
    applyRecord(recordType, normalizedId, record);
    const nextRect = clampWindowRect(getLayerRect(), state.x, state.y, state.width, state.height);
    state.x = nextRect.left;
    state.y = nextRect.top;
    state.width = nextRect.width;
    state.height = nextRect.height;
    state.visible = true;
    state.shellEl?.classList.remove("hidden");
    applyWindowRect();
    focusWindow();
    if (state.animationPreviewVisible === true) {
      ensureAnimationPreviewShell();
      state.animationPreviewShellEl?.classList.remove("hidden");
      applyAnimationPreviewRect();
      syncAnimationPreviewPlayback();
    }
    persistState();
    return true;
  }

    function close() {
      if (!confirmDiscardIfDirty()) {
        return false;
      }
      state.visible = false;
      clearFocus();
      releaseGridPointerCapture();
      clearPointerInteractionState();
      state.dragSession = null;
      state.eyedropperActive = false;
      state.suppressEyedropperUntilAltRelease = false;
      state.templateDragDepth = 0;
      setTemplateDragActive(false);
      resetLocalHistory(null);
      closeToolMenu({ refreshHeader: false });
      closeAnimationPreview({ persist: false });
      state.shellEl?.classList.add("hidden");
    persistState();
      return true;
  }

  function initialize() {
    if (initialized) {
      return;
    }
    initialized = true;
    ensureShell();
    window.addEventListener("pointerup", (event) => {
      completePointerInteraction(event.pointerId);
    });
    window.addEventListener("resize", () => {
      const nextRect = clampWindowRect(getLayerRect(), state.x, state.y, state.width, state.height);
      state.x = nextRect.left;
      state.y = nextRect.top;
      state.width = nextRect.width;
      state.height = nextRect.height;
      applyWindowRect();
      applyAnimationPreviewRect();
      persistState();
    });
    if (state.visible && state.recordId) {
      open(state.recordType, state.recordId);
    } else {
      state.visible = false;
      state.shellEl?.classList.add("hidden");
    }
    window.addEventListener("pointercancel", (event) => {
      completePointerInteraction(event.pointerId);
    });
    window.addEventListener("keydown", (event) => {
      if (state.visible && !isEditableKeyTarget(event.target) && (event.ctrlKey || event.metaKey) && !event.altKey) {
        const key = String(event.key || "").toLowerCase();
        if (key === "z" && event.shiftKey !== true) {
          event.preventDefault();
          undoLocalHistory();
          return;
        }
        if (key === "y" || (key === "z" && event.shiftKey === true)) {
          event.preventDefault();
          redoLocalHistory();
          return;
        }
      }
      if (event.key !== "Alt") {
        return;
      }
      beginEyedropperMode();
    });
    window.addEventListener("keyup", (event) => {
      if (event.key !== "Alt") {
        return;
      }
      state.suppressEyedropperUntilAltRelease = false;
      if (state.eyedropperActive) {
        endEyedropperMode();
      }
    });
    window.addEventListener("blur", () => {
      state.suppressEyedropperUntilAltRelease = false;
      if (state.eyedropperActive) {
        endEyedropperMode();
      } else {
        completePointerInteraction();
      }
    });
  }

  return {
    initialize,
    open,
    close,
    isOpen: () => state.visible === true,
  };
}
