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

export function createHistoryController(scope) {
  const documentScope = scope.documentScope || scope;
  const renderScope = scope.renderScope || scope;
  const uiScope = scope.uiScope || scope;
  const sessionScope = scope.sessionScope || scope;
  const MAX_HISTORY_ENTRIES = 2;
  let pendingPersistTimer = 0;

  function clearPendingPersistTimer() {
    if (!pendingPersistTimer) {
      return;
    }
    window.clearTimeout(pendingPersistTimer);
    pendingPersistTimer = 0;
  }

  function persistHistoryState() {
    clearPendingPersistTimer();
    return true;
  }

  function schedulePersistHistoryState() {
    clearPendingPersistTimer();
    return true;
  }

  function restoreHistoryState() {
    return null;
  }

  function applyHistorySnapshot() {
    return false;
  }

  function captureState() {
    return {
      width: Math.max(1, Number(documentScope.width) || 1),
      height: Math.max(1, Number(documentScope.height) || 1),
      mapName: String(documentScope.mapName || scope.mapId || ""),
      backgroundColor: documentScope.normalizeMapBackgroundColor(documentScope.backgroundColor),
      backgroundTileId: String(documentScope.backgroundTileId || "").trim(),
      heightBlurStep: Math.max(0, Math.min(1, Number(documentScope.heightBlurStep ?? documentScope.heightDetailStep) || 0.1)),
      layers: documentScope.cloneLayers(documentScope.roomLayers),
      heightLayers: documentScope.cloneHeightLayers(documentScope.heightLayers),
      npcs: documentScope.cloneNpcOverlays(documentScope.npcOverlays),
      worldChunkBackgrounds: typeof scope.captureWorldChunkBackgroundState === "function"
        ? scope.captureWorldChunkBackgroundState()
        : {},
      worldBookmarks: typeof scope.captureWorldBookmarkState === "function"
        ? scope.captureWorldBookmarkState()
        : [],
      editorUi: documentScope.cloneEditorUiState(),
    };
  }

  function cloneHistoryState(state) {
    if (!state || typeof state !== "object") {
      return captureState();
    }
    return {
      width: Math.max(1, Number(state.width) || 1),
      height: Math.max(1, Number(state.height) || 1),
      mapName: String(state.mapName || scope.mapId || ""),
      backgroundColor: documentScope.normalizeMapBackgroundColor(state.backgroundColor),
      backgroundTileId: documentScope.normalizeBackgroundTileId(state.backgroundTileId),
      heightBlurStep: Math.max(0, Math.min(1, Number(state.heightBlurStep ?? state.heightDetailStep) || 0.1)),
      layers: documentScope.cloneLayers(Array.isArray(state.layers) ? state.layers : []),
      heightLayers: documentScope.cloneHeightLayers(Array.isArray(state.heightLayers) ? state.heightLayers : []),
      npcs: documentScope.cloneNpcOverlays(Array.isArray(state.npcs) ? state.npcs : []),
      worldChunkBackgrounds: state && state.worldChunkBackgrounds && typeof state.worldChunkBackgrounds === "object" && !Array.isArray(state.worldChunkBackgrounds)
        ? { ...state.worldChunkBackgrounds }
        : {},
      worldBookmarks: typeof scope.applyWorldBookmarkState === "function" && Array.isArray(state?.worldBookmarks)
        ? state.worldBookmarks.map((entry) => ({
          id: String(entry?.id || "").trim(),
          label: String(entry?.label || entry?.id || "").trim(),
          x: Math.floor(Number(entry?.x) || 0),
          y: Math.floor(Number(entry?.y) || 0),
        }))
        : [],
      editorUi: documentScope.cloneEditorUiState(state.editorUi || {}),
    };
  }

  function refreshUiAfterHistoryMutation() {
    documentScope.ensureBaseLayer();
    sessionScope.activeLayer = documentScope.roomLayers.some((layer) => layer.layer === sessionScope.activeLayer) ? sessionScope.activeLayer : 0;
    if (!documentScope.npcOverlays.some((npc) => npc.id === sessionScope.selectedNpcId)) {
      sessionScope.selectedNpcId = documentScope.npcOverlays[0] ? String(documentScope.npcOverlays[0].id || "") : "";
    }
    if (uiScope.refreshInstanceSectionState) {
      uiScope.refreshInstanceSectionState();
    }
    uiScope.renderPaintPalette();
    if (uiScope.renderHeightLayerList) {
      uiScope.renderHeightLayerList();
    }
    uiScope.renderInstancePalette();
    uiScope.renderLayerList();
    uiScope.renderNpcList();
    if (uiScope.renderTriggerList) {
      uiScope.renderTriggerList();
    }
    if (uiScope.renderMonsterList) {
      uiScope.renderMonsterList();
    }
    if (uiScope.renderPathList) {
      uiScope.renderPathList();
    }
    if (uiScope.renderTransitionList) {
      uiScope.renderTransitionList();
    }
    uiScope.refreshInformationPanel();
    if (typeof scope.refreshWorldOverviewWindow === "function") {
      scope.refreshWorldOverviewWindow();
    }
    renderScope.draw();
  }

  function applyState(state, options) {
    const config = options && typeof options === "object" ? options : {};
    documentScope.width = Math.max(1, Number(state?.width) || documentScope.width || 1);
    documentScope.height = Math.max(1, Number(state?.height) || documentScope.height || 1);
    documentScope.mapName = String(state?.mapName || scope.mapId || documentScope.mapName || "");
    documentScope.backgroundColor = documentScope.normalizeMapBackgroundColor(state?.backgroundColor || documentScope.backgroundColor);
    documentScope.backgroundTileId = documentScope.normalizeBackgroundTileId(state?.backgroundTileId);
    documentScope.heightBlurStep = Math.max(0, Math.min(1, Number(state?.heightBlurStep ?? state?.heightDetailStep) || documentScope.heightBlurStep || documentScope.heightDetailStep || 0.1));
    documentScope.roomLayers = documentScope.cloneLayers(Array.isArray(state?.layers) ? state.layers : []);
    documentScope.heightLayers = documentScope.cloneHeightLayers(Array.isArray(state?.heightLayers) ? state.heightLayers : []);
    const nextNpcs = documentScope.cloneNpcOverlays(Array.isArray(state?.npcs) ? state.npcs : []);
    sessionScope.editorUiState = state && state.editorUi ? documentScope.cloneEditorUiState(state.editorUi) : { panelLayouts: {} };
    if (!documentScope.getHeightLayerById(sessionScope.activeHeightLayerId)) {
      sessionScope.activeHeightLayerId = String(documentScope.heightLayers[0]?.id || "").trim();
    }
    if (sessionScope.editingTargetKind === "height" && !sessionScope.activeHeightLayerId) {
      sessionScope.editingTargetKind = "room";
    }
    nextNpcs.forEach((npc) => documentScope.syncNpcOverlayFromRecord(npc));
    documentScope.npcOverlays.length = 0;
    nextNpcs.forEach((npc) => documentScope.npcOverlays.push(npc));
    if (typeof scope.applyWorldChunkBackgroundState === "function" && scope.isWorldModeActive?.()) {
      scope.applyWorldChunkBackgroundState(state?.worldChunkBackgrounds || {});
    }
    if (typeof scope.applyWorldBookmarkState === "function" && scope.isWorldModeActive?.()) {
      scope.applyWorldBookmarkState(Array.isArray(state?.worldBookmarks) ? state.worldBookmarks : []);
    }
    if (typeof scope.rebuildVisibleWorldChunksFromDocument === "function" && typeof scope.isWorldModeActive === "function" && scope.isWorldModeActive()) {
      scope.rebuildVisibleWorldChunksFromDocument();
    }
    if (!config.deferRefresh) {
      refreshUiAfterHistoryMutation();
    }
  }

  function restoreToHistoryIndex(targetIndex) {
    const normalizedTargetIndex = Math.max(0, Math.min(Number(targetIndex) || 0, scope.historyEntries.length - 1));
    const targetEntry = scope.historyEntries[normalizedTargetIndex] || null;
    if (!targetEntry?.state) {
      return false;
    }
    applyState(targetEntry.state);
    return true;
  }

  function getStateSignature(state) {
    const layerSig = scope.cloneLayers(Array.isArray(state?.layers) ? state.layers : [])
      .sort((a, b) => Number(a.layer) - Number(b.layer))
      .map((layer) => ({
        layer: Number(layer.layer) || 0,
        name: typeof layer.name === "string" ? layer.name : "",
        rows: scope.normalizeRows(layer.rows, Number(layer.layer) === 0 ? "." : " "),
      }));
    const heightLayerSig = scope.cloneHeightLayers(Array.isArray(state?.heightLayers) ? state.heightLayers : [])
      .sort((a, b) => String(a.id || "").localeCompare(String(b.id || "")))
      .map((entry) => ({
        id: String(entry.id || ""),
        name: typeof entry.name === "string" ? entry.name : "",
        z: Number(entry.z) || 1,
        x: Number(entry.x) || 0,
        y: Number(entry.y) || 0,
        rows: Array.isArray(entry.rows) ? entry.rows.map((row) => String(row || "")) : [],
      }));
    const npcSig = scope.cloneNpcOverlays(Array.isArray(state?.npcs) ? state.npcs : [])
      .sort((a, b) => String(a.id || "").localeCompare(String(b.id || "")))
      .map((entry) => ({
        id: String(entry.id || ""),
        layer: Number(entry.layer) || 0,
        name: String(entry.name || ""),
        spriteId: String(entry.spriteId || ""),
        x: Number(entry.x) || 0,
        y: Number(entry.y) || 0,
      }));
    return JSON.stringify({
      width: Math.max(1, Number(state?.width) || 1),
      height: Math.max(1, Number(state?.height) || 1),
      mapName: String(state?.mapName || ""),
      backgroundColor: scope.normalizeMapBackgroundColor(state?.backgroundColor),
      backgroundTileId: scope.normalizeBackgroundTileId(state?.backgroundTileId),
      heightBlurStep: Math.max(0, Math.min(1, Number(state?.heightBlurStep ?? state?.heightDetailStep) || 0.1)),
      layerSig,
      heightLayerSig,
      npcSig,
      worldChunkBackgrounds: state && state.worldChunkBackgrounds && typeof state.worldChunkBackgrounds === "object" && !Array.isArray(state.worldChunkBackgrounds)
        ? state.worldChunkBackgrounds
        : {},
      worldBookmarks: Array.isArray(state?.worldBookmarks)
        ? state.worldBookmarks.map((entry) => ({
          id: String(entry?.id || ""),
          label: String(entry?.label || ""),
          x: Number(entry?.x) || 0,
          y: Number(entry?.y) || 0,
        }))
        : [],
      editorUi: scope.cloneEditorUiState(state?.editorUi || {}),
    });
  }

  function formatCellCoord(cell) {
    return "(" + cell.x + "," + cell.y + ")";
  }

  function formatHistoryLabel(entry) {
    const pairText = (entry.before || entry.after)
      ? (" (" + (entry.before || "?") + " -> " + (entry.after || "?") + ")")
      : "";
    return entry.label + pairText;
  }

  function renderHistoryPreview() {
    if (!scope.historyPreviewEl) {
      return;
    }
    const selectedEntry = scope.historyEntries[scope.historySelectionIndex] || null;
    if (!selectedEntry) {
      scope.historyPreviewEl.innerHTML = '<h4>Undo / Redo</h4><div class="history-preview-empty">Single-step history is empty.</div>';
      return;
    }
    const details = Array.isArray(selectedEntry.details) ? selectedEntry.details : [];
    const detailHtml = details.length > 0
      ? "<ul>" + details.map((detail) => "<li>" + detail + "</li>").join("") + "</ul>"
      : '<div class="history-preview-empty">No additional details recorded.</div>';
    const title = scope.historySelectionIndex === scope.historyIndex ? "Current state" : "Single undo step";
    scope.historyPreviewEl.innerHTML =
      "<h4>" + title + "</h4>" +
      '<div style="margin-bottom:6px;">' + formatHistoryLabel(selectedEntry) + "</div>" +
      detailHtml +
      '<div class="history-preview-empty" style="margin-top:8px;">Only one undo / redo step is kept in memory.</div>';
  }

  function renderHistoryList() {
    if (scope.historyListEl) {
      scope.historyListEl.innerHTML = "";
      scope.historyEntries.forEach((entry, index) => {
        if (index === scope.historyIndex) {
          return;
        }
        const row = document.createElement("button");
        row.type = "button";
        row.className = "history-row" + (index === scope.historySelectionIndex ? " active" : "");
        const timeText = new Date(entry.createdAt).toLocaleTimeString();
        row.innerHTML =
          "<span>" + String(entry.seq) + ". " + formatHistoryLabel(entry) + "</span>" +
          '<span class="history-meta">' + timeText + "</span>";
        row.addEventListener("click", () => {
          if (index === scope.historySelectionIndex) {
            return;
          }
          scope.historySelectionIndex = index;
          renderHistoryList();
          renderHistoryPreview();
        });
        scope.historyListEl.appendChild(row);
      });
    }
    if (scope.historyCurrentEl) {
      const currentEntry = scope.historyEntries[scope.historyIndex] || null;
      scope.historyCurrentEl.innerHTML = currentEntry
        ? (
          '<div class="history-current-label">Current State</div>' +
          '<button type="button" class="history-row current-row">' +
            "<span>" + String(currentEntry.seq) + ". " + formatHistoryLabel(currentEntry) + "</span>" +
            '<span class="history-meta">' + new Date(currentEntry.createdAt).toLocaleTimeString() + "</span>" +
          "</button>"
        )
        : '<div class="history-current-label">Current State</div><div class="history-current-empty">No history yet.</div>';
    }
  }

  function refreshToolbarState(preserveCurrentStatus) {
    const canUndo = scope.historyIndex > 0;
    const canRedo = scope.historyIndex < scope.historyEntries.length - 1;
    const currentHistoryId = scope.historyEntries[scope.historyIndex] ? Number(scope.historyEntries[scope.historyIndex].id) || 0 : 0;
    const isDirtyFromSaved = currentHistoryId !== (Number(scope.lastSavedHistoryId) || 0);

    scope.undoBtn.disabled = scope.isSaving || !canUndo;
    scope.redoBtn.disabled = scope.isSaving || !canRedo;
    scope.saveBtn.disabled = scope.isSaving || !isDirtyFromSaved;

    renderHistoryList();
    renderHistoryPreview();

    if (preserveCurrentStatus) {
      return;
    }
    if (scope.isSaving) {
      scope.setStatus("Saving...", false);
    } else if (canRedo) {
      scope.setStatus("Redo is available. A new edit will replace it.", false);
    } else if (isDirtyFromSaved) {
      scope.setStatus("Unsaved changes.", false);
    } else {
      scope.setStatus("All changes saved.", false);
    }
  }

  function registerHistory(label, before, after, details, options) {
    const config = options && typeof options === "object" ? options : {};
    const nextState = cloneHistoryState(config.nextState || captureState());
    const currentEntry = scope.historyEntries[scope.historyIndex] || null;
    const currentState = currentEntry?.state ? cloneHistoryState(currentEntry.state) : captureState();
    if (!config.skipStateCheck && getStateSignature(nextState) === getStateSignature(currentState)) {
      return;
    }

    if (scope.historyIndex < scope.historyEntries.length - 1) {
      scope.historyEntries = scope.historyEntries.slice(0, scope.historyIndex + 1);
    }

    const entry = {
      id: scope.nextHistoryId,
      seq: scope.nextHistoryId,
      createdAt: Date.now(),
      label,
      before,
      after,
      details: Array.isArray(details) ? details : [],
      state: nextState,
    };
    scope.nextHistoryId += 1;
    scope.historyEntries.push(entry);
    if (scope.historyEntries.length > MAX_HISTORY_ENTRIES) {
      scope.historyEntries = scope.historyEntries.slice(scope.historyEntries.length - MAX_HISTORY_ENTRIES);
    }
    scope.historyIndex = scope.historyEntries.length - 1;
    scope.historySelectionIndex = scope.historyIndex;
    schedulePersistHistoryState();
    refreshToolbarState();
  }

  function undo() {
    if (scope.historyIndex <= 0) {
      return false;
    }
    scope.historyIndex -= 1;
    scope.historySelectionIndex = scope.historyIndex;
    const restored = restoreToHistoryIndex(scope.historyIndex);
    schedulePersistHistoryState();
    refreshToolbarState();
    if (restored) {
      scope.setStatus("Undid the last change.", false);
    }
    return restored;
  }

  function redo() {
    if (scope.historyIndex >= scope.historyEntries.length - 1) {
      return false;
    }
    scope.historyIndex += 1;
    scope.historySelectionIndex = scope.historyIndex;
    const restored = restoreToHistoryIndex(scope.historyIndex);
    schedulePersistHistoryState();
    refreshToolbarState();
    if (restored) {
      scope.setStatus("Redid the last change.", false);
    }
    return restored;
  }

  return {
    persistHistoryState,
    schedulePersistHistoryState,
    restoreHistoryState,
    applyHistorySnapshot,
    captureState,
    applyState,
    applyOperation: () => false,
    restoreToHistoryIndex,
    getStateSignature,
    formatCellCoord,
    formatHistoryLabel,
    renderHistoryPreview,
    renderHistoryList,
    refreshToolbarState,
    registerHistory,
    undo,
    redo,
  };
}
