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

import {
  buildSpritesPayloadFromImagesPayload,
  buildTilesPayloadFromImagesPayload,
  mergeImagesPayloadWithSpritesPayload,
  mergeImagesPayloadWithTilesPayload,
} from "../contentTransforms/graphicsPayload";
import { resizeRows } from "../components/worldshaperShared";
import { moveItemRelative } from "./reorderableListController";

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

export function createMapDocumentController(config) {
  const {
    mapId,
    getMapId,
    mapDocument,
    popupSessionStore,
    baseRows,
    getBaseRows,
    normalizeMapBackgroundColor,
    onMapNameUpdated,
    invalidateTileSurface,
  } = config;

  function resolveMapId() {
    const resolved = typeof getMapId === "function" ? getMapId() : mapId;
    return String(resolved || mapId || "").trim();
  }

  function resolveBaseRows() {
    const resolved = typeof getBaseRows === "function" ? getBaseRows() : baseRows;
    return Array.isArray(resolved) ? resolved : [];
  }

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

  function normalizeRows(rows, fillChar) {
    return Array.from({ length: mapDocument.height }, (_, y) => {
      const raw = String((rows && rows[y]) || "");
      if (raw.length >= mapDocument.width) {
        return raw.slice(0, mapDocument.width);
      }
      return raw + fillChar.repeat(Math.max(0, mapDocument.width - raw.length));
    });
  }

  function getLayerByNumber(layerNumber) {
    const normalizedLayer = Number(layerNumber) || 0;
    return mapDocument.roomLayers.find((layer) => Number(layer.layer) === normalizedLayer) || null;
  }

  function getDefaultEditableLayerNumber() {
    const sorted = mapDocument.roomLayers
      .map((layer) => Number(layer.layer) || 0)
      .filter((layerNumber) => layerNumber > 0)
      .sort((a, b) => a - b);
    return sorted.length > 0 ? sorted[0] : 0;
  }

  function getLayerDefaultName(layerNumber) {
    const normalizedLayer = Number(layerNumber) || 0;
    return normalizedLayer === 0 ? "Background" : ("Layer " + Math.max(0, normalizedLayer - 1));
  }

  function getLayerDisplayName(layerOrNumber) {
    const layer = typeof layerOrNumber === "object" && layerOrNumber
      ? layerOrNumber
      : getLayerByNumber(layerOrNumber);
    if (!layer) {
      return getLayerDefaultName(layerOrNumber);
    }
    const customName = typeof layer.name === "string" ? layer.name.trim() : "";
    return customName || getLayerDefaultName(layer.layer);
  }

  function isBackgroundLayer(layerNumber) {
    return (Number(layerNumber) || 0) === 0;
  }

  function cloneHeightLayers(source) {
    const seenIds = new Set();
    return (Array.isArray(source) ? source : [])
      .flatMap((entry, index) => {
        const fallbackId = "height_" + String(index + 1);
        const id = String(entry?.id || fallbackId).trim() || fallbackId;
        if (seenIds.has(id)) {
          return [];
        }
        seenIds.add(id);
        return [{
          id,
          name: typeof entry?.name === "string" && entry.name.trim() ? entry.name.trim() : undefined,
          z: index + 1,
          x: Math.max(0, Number(entry?.x) || 0),
          y: Math.max(0, Number(entry?.y) || 0),
          rows: Array.isArray(entry?.rows) ? entry.rows.map((row) => String(row || "")) : [],
        }];
      });
  }

  function getHeightLayerById(heightLayerId) {
    const normalizedId = String(heightLayerId || "").trim();
    if (!normalizedId) {
      return null;
    }
    return mapDocument.heightLayers.find((entry) => String(entry?.id || "").trim() === normalizedId) || null;
  }

  function getHeightLayerDisplayName(heightLayerOrId) {
    const entry = typeof heightLayerOrId === "object" && heightLayerOrId
      ? heightLayerOrId
      : getHeightLayerById(heightLayerOrId);
    if (!entry) {
      return "Height Layer";
    }
    return typeof entry.name === "string" && entry.name.trim()
      ? entry.name.trim()
      : String(entry.id || "Height Layer");
  }

  function ensureBaseLayer() {
    const baseLayerRows = resolveBaseRows();
    if (!mapDocument.roomLayers.some((layer) => Number(layer.layer) === 0)) {
      mapDocument.roomLayers.unshift({ layer: 0, name: undefined, rows: normalizeRows(baseLayerRows, "."), instanceIds: [] });
    }
    if (!mapDocument.roomLayers.some((layer) => Number(layer.layer) > 0)) {
      mapDocument.roomLayers.push({ layer: 1, name: undefined, rows: normalizeRows([], " "), instanceIds: [] });
    }
    mapDocument.roomLayers = mapDocument.roomLayers
      .map((layer) => ({
        layer: Number(layer.layer),
        name: typeof layer.name === "string" && layer.name.trim() ? layer.name.trim() : undefined,
        rows: normalizeRows(layer.rows, Number(layer.layer) === 0 ? "." : " "),
        instanceIds: Array.isArray(layer.instanceIds) ? layer.instanceIds : [],
      }))
      .sort((a, b) => a.layer - b.layer);

    const baseIndex = mapDocument.roomLayers.findIndex((layer) => layer.layer === 0);
    if (baseIndex >= 0) {
      const candidate = mapDocument.roomLayers[baseIndex].rows.join("").replace(/\s/g, "");
      if (!candidate) {
        mapDocument.roomLayers[baseIndex].rows = normalizeRows(baseLayerRows, ".");
      }
    }
    invalidateTileSurface();
    popupSessionStore.syncLayerVisibility(mapDocument.roomLayers);
    if ((Number(popupSessionStore.state.activeLayer) || 0) <= 0) {
      popupSessionStore.state.activeLayer = getDefaultEditableLayerNumber();
    }
  }

  function moveLayerToDepth(sourceLayerNumber, targetLayerNumber, position) {
    const sourceLayer = Number(sourceLayerNumber) || 0;
    const targetLayer = Number(targetLayerNumber) || 0;
    if (sourceLayer <= 0 || targetLayer <= 0 || sourceLayer === targetLayer) {
      return null;
    }

    const currentOrderedNonBaseLayers = mapDocument.roomLayers
      .map((layer) => Number(layer.layer) || 0)
      .filter((layerNumber) => layerNumber > 0)
      .sort((a, b) => a - b);
    const reorderedNonBaseLayers = moveItemRelative(
      currentOrderedNonBaseLayers.map((layerNumber) => String(layerNumber)),
      String(sourceLayer),
      String(targetLayer),
      position === "after" ? "after" : "before",
    )
      .map((layerNumber) => Number(layerNumber) || 0)
      .filter((layerNumber) => layerNumber > 0);
    if (reorderedNonBaseLayers.length !== currentOrderedNonBaseLayers.length) {
      return null;
    }
    if (reorderedNonBaseLayers.every((layerNumber, index) => layerNumber === currentOrderedNonBaseLayers[index])) {
      return null;
    }

    const layerNumberMap = { 0: 0 };
    reorderedNonBaseLayers.forEach((oldLayerNumber, index) => {
      layerNumberMap[String(oldLayerNumber)] = index + 1;
    });

    const previousVisibilityById = {};
    mapDocument.roomLayers.forEach((layer) => {
      const layerNumber = Number(layer.layer) || 0;
      previousVisibilityById[String(layerNumber)] = popupSessionStore.isLayerVisible(layerNumber, mapDocument.roomLayers);
    });
    const previousActiveLayer = Number(popupSessionStore.state.activeLayer) || 0;
    const previousSelectedTile = popupSessionStore.state.selectedTile && typeof popupSessionStore.state.selectedTile === "object"
      ? { ...popupSessionStore.state.selectedTile }
      : null;

    mapDocument.roomLayers = mapDocument.roomLayers
      .map((layer) => ({
        ...layer,
        layer: layerNumberMap[String(Number(layer.layer) || 0)] ?? (Number(layer.layer) || 0),
      }))
      .sort((a, b) => a.layer - b.layer);
    mapDocument.npcOverlays.forEach((npc) => {
      const nextLayer = layerNumberMap[String(Number(npc.layer) || 0)] ?? (Number(npc.layer) || 0);
      npc.layer = nextLayer;
      if (npc.record && typeof npc.record === "object" && !Array.isArray(npc.record)) {
        npc.record.layer = nextLayer;
      }
    });
    if (previousSelectedTile) {
      popupSessionStore.state.selectedTile = {
        ...previousSelectedTile,
        layer: layerNumberMap[String(Number(previousSelectedTile.layer) || 0)] ?? (Number(previousSelectedTile.layer) || 0),
      };
    }
    popupSessionStore.state.activeLayer = layerNumberMap[String(previousActiveLayer)] ?? previousActiveLayer;

    const nextVisibleLayersById = {};
    Object.entries(previousVisibilityById).forEach(([oldLayerNumber, wasVisible]) => {
      const nextLayerNumber = layerNumberMap[String(Number(oldLayerNumber) || 0)];
      if (nextLayerNumber === undefined) {
        return;
      }
      nextVisibleLayersById[String(nextLayerNumber)] = wasVisible !== false;
    });
    popupSessionStore.state.visibleLayersById = nextVisibleLayersById;
    ensureBaseLayer();
    popupSessionStore.syncLayerVisibility(mapDocument.roomLayers);
    invalidateTileSurface();

    return {
      previousOrder: currentOrderedNonBaseLayers,
      nextOrder: reorderedNonBaseLayers,
      layerNumberMap,
      sourceLayer,
      targetLayer,
      position: position === "after" ? "after" : "before",
    };
  }

  function moveHeightLayerToDepth(sourceHeightLayerId, targetHeightLayerId, position) {
    const sourceId = String(sourceHeightLayerId || "").trim();
    const targetId = String(targetHeightLayerId || "").trim();
    if (!sourceId || !targetId || sourceId === targetId) {
      return null;
    }
    const currentOrderedIds = mapDocument.heightLayers
      .map((entry) => String(entry?.id || "").trim())
      .filter(Boolean);
    if (!currentOrderedIds.includes(sourceId) || !currentOrderedIds.includes(targetId)) {
      return null;
    }
    const reorderedIds = moveItemRelative(
      currentOrderedIds,
      sourceId,
      targetId,
      position === "after" ? "after" : "before",
    ).filter(Boolean);
    if (reorderedIds.length !== currentOrderedIds.length) {
      return null;
    }
    if (reorderedIds.every((entryId, index) => entryId === currentOrderedIds[index])) {
      return null;
    }
    const entriesById = new Map(
      mapDocument.heightLayers.map((entry) => [String(entry?.id || "").trim(), entry]),
    );
    mapDocument.heightLayers = cloneHeightLayers(
      reorderedIds
        .map((entryId) => entriesById.get(entryId) || null)
        .filter((entry) => entry !== null),
    );
    return {
      previousOrder: currentOrderedIds,
      nextOrder: reorderedIds,
      sourceId,
      targetId,
      position: position === "after" ? "after" : "before",
    };
  }

  function applyMapInformationEdits(nextState) {
    const currentMapId = resolveMapId();
    const nextWidth = Math.max(1, Math.min(512, Number(nextState?.width) || mapDocument.width));
    const nextHeight = Math.max(1, Math.min(512, Number(nextState?.height) || mapDocument.height));
    const nextName = String(nextState?.name || mapDocument.mapName || currentMapId).trim() || currentMapId;
    const nextBackgroundColor = normalizeMapBackgroundColor(nextState?.backgroundColor || mapDocument.backgroundColor);
    const nextHeightBlurStep = normalizeHeightBlurStep(nextState?.heightBlurStep ?? nextState?.heightDetailStep, mapDocument.heightBlurStep);
    const oldWidth = mapDocument.width;
    const oldHeight = mapDocument.height;
    const oldName = String(mapDocument.mapName || currentMapId || "").trim() || currentMapId;
    const oldBackgroundColor = normalizeMapBackgroundColor(mapDocument.backgroundColor);
    const oldHeightBlurStep = normalizeHeightBlurStep(mapDocument.heightBlurStep);

    mapDocument.width = nextWidth;
    mapDocument.height = nextHeight;
    mapDocument.mapName = nextName;
    mapDocument.backgroundColor = nextBackgroundColor;
    mapDocument.heightBlurStep = nextHeightBlurStep;
    mapDocument.mapInfoDraft = {
      width: mapDocument.width,
      height: mapDocument.height,
      name: String(mapDocument.mapName || currentMapId || ""),
      backgroundColor: normalizeMapBackgroundColor(mapDocument.backgroundColor),
      heightBlurStep: normalizeHeightBlurStep(mapDocument.heightBlurStep),
    };
    mapDocument.roomLayers = mapDocument.roomLayers.map((layer) => ({
      layer: layer.layer,
      name: typeof layer.name === "string" && layer.name.trim() ? layer.name.trim() : undefined,
      rows: resizeRows(layer.rows, mapDocument.width, mapDocument.height, layer.layer === 0 ? "." : " "),
      instanceIds: Array.isArray(layer.instanceIds) ? layer.instanceIds : [],
    }));
    mapDocument.heightLayers = cloneHeightLayers(mapDocument.heightLayers)
      .map((entry) => {
        const clampedX = Math.max(0, Math.min(mapDocument.width - 1, Number(entry.x) || 0));
        const clampedY = Math.max(0, Math.min(mapDocument.height - 1, Number(entry.y) || 0));
        const rows = Array.isArray(entry.rows)
          ? entry.rows
            .slice(0, Math.max(0, mapDocument.height - clampedY))
            .map((row) => String(row || "").slice(0, Math.max(0, mapDocument.width - clampedX)))
          : [];
        return {
          ...entry,
          x: clampedX,
          y: clampedY,
          rows,
        };
      });
    mapDocument.npcOverlays.splice(0, mapDocument.npcOverlays.length, ...mapDocument.npcOverlays.filter((npc) => {
      const x = Number(npc.x);
      const y = Number(npc.y);
      if (!Number.isFinite(x) || !Number.isFinite(y)) {
        return false;
      }
      if (x < 0 || y < 0) {
        return true;
      }
      return x >= 0 && x < mapDocument.width && y >= 0 && y < mapDocument.height;
    }));
    popupSessionStore.state.selectedNpcId = mapDocument.npcOverlays.some((npc) => npc.id === popupSessionStore.state.selectedNpcId)
      ? popupSessionStore.state.selectedNpcId
      : (mapDocument.npcOverlays[0] ? String(mapDocument.npcOverlays[0].id || "") : "");
    popupSessionStore.state.selectedTile = null;
    ensureBaseLayer();
    if (typeof onMapNameUpdated === "function") {
      onMapNameUpdated();
    }

    return {
      oldWidth,
      oldHeight,
      oldName,
      oldBackgroundColor,
      oldHeightBlurStep,
      nextWidth: mapDocument.width,
      nextHeight: mapDocument.height,
      nextName: mapDocument.mapName,
      nextBackgroundColor: mapDocument.backgroundColor,
      nextHeightBlurStep: normalizeHeightBlurStep(mapDocument.heightBlurStep),
      removedOutOfBoundsNpcs: true,
    };
  }

  function ensureContentPayload(type, fallback) {
    const normalizedType = String(type || "").trim();
    if (normalizedType === "tiles") {
      return buildTilesPayloadFromImagesPayload(mapDocument.contentBundle.images || { schemaVersion: 1, images: [] });
    }
    if (normalizedType === "sprites") {
      return buildSpritesPayloadFromImagesPayload(mapDocument.contentBundle.images || { schemaVersion: 1, images: [] });
    }
    const existing = mapDocument.contentBundle[normalizedType];
    if (existing && typeof existing === "object" && !Array.isArray(existing)) {
      return existing;
    }
    const nextPayload = cloneValue(fallback) || {};
    mapDocument.contentBundle[normalizedType] = nextPayload;
    return nextPayload;
  }

  function setContentPayload(type, payload) {
    const normalizedType = String(type || "").trim();
    if (normalizedType === "tiles") {
      mapDocument.contentBundle.images = mergeImagesPayloadWithTilesPayload(
        mapDocument.contentBundle.images || { schemaVersion: 1, images: [] },
        payload,
      );
      return buildTilesPayloadFromImagesPayload(mapDocument.contentBundle.images);
    }
    if (normalizedType === "sprites") {
      mapDocument.contentBundle.images = mergeImagesPayloadWithSpritesPayload(
        mapDocument.contentBundle.images || { schemaVersion: 1, images: [] },
        payload,
      );
      return buildSpritesPayloadFromImagesPayload(mapDocument.contentBundle.images);
    }
    mapDocument.contentBundle[normalizedType] = payload;
    return mapDocument.contentBundle[normalizedType];
  }

  return {
    normalizeRows,
    getLayerByNumber,
    getDefaultEditableLayerNumber,
    getLayerDefaultName,
    getLayerDisplayName,
    isBackgroundLayer,
    cloneHeightLayers,
    getHeightLayerById,
    getHeightLayerDisplayName,
    ensureBaseLayer,
    moveLayerToDepth,
    moveHeightLayerToDepth,
    applyMapInformationEdits,
    ensureContentPayload,
    setContentPayload,
  };
}

