import { createDebouncedCallback } from "./debounce";

const TOOL_WINDOW_LAYOUT_STORAGE_KEY = "worldshaper:studio:tool-windows:v1";

type ToolWindowMode = "inline" | "floating";

type ToolWindowState = {
  mode?: ToolWindowMode;
  visible?: boolean;
  x?: number;
  y?: number;
  width?: number;
  height?: number;
  inlineHeight?: number;
  order?: number;
};

type ToolWindowStateMap = Record<string, ToolWindowState>;

type PopupPanState = {
  isPanning: boolean;
  startX: number;
  startY: number;
  scrollLeft: number;
  scrollTop: number;
};

type LayerLike = {
  layer?: number;
};

type PopupSessionState = {
  activeLayer: number;
  viewingAllLayers: boolean;
  visibleLayersById: Record<string, boolean>;
  activeSidebarTab: string;
  pan: PopupPanState;
  draggingNpc: unknown;
  pointerCandidate: unknown;
  paintingStroke: unknown;
  dragDrawX: number;
  dragDrawY: number;
  isSaving: boolean;
  activeBrushTileId: string;
  activeGraphicsTab: string;
  activeGraphicsRecordId: string;
  canvasToolMode: string;
  activeInstanceBrushId: string;
  activeEntityCategory: string;
  hoverTileX: number;
  hoverTileY: number;
  selectedNpcId: string;
  selectedTile: unknown;
  spritePickerOpenNpcId: string;
  hoveredNpcId: string;
  hoverCanvasX: number;
  hoverCanvasY: number;
  templateSectionCollapsed: boolean;
  placedSectionCollapsed: boolean;
  drawLayerSectionCollapsed: boolean;
  heightLayerSectionCollapsed: boolean;
  organizedListDrag: unknown;
  tileMutationBatchDepth: number;
  hideTileGrid: boolean;
  showChunkBounds: boolean;
  zoomPreviewUntil: number;
  scrollPreviewUntil: number;
  toolWindows: ToolWindowStateMap;
};

type PersistedLayoutState = {
  activeSidebarTab: string;
  toolWindows: ToolWindowStateMap;
};

function normalizeToolWindowState(value: unknown): ToolWindowState {
  const source = value && typeof value === "object" && !Array.isArray(value)
    ? value as Record<string, unknown>
    : {};
  const nextState: ToolWindowState = {};
  if (source.mode === "inline" || source.mode === "floating") {
    nextState.mode = source.mode;
  }
  if (typeof source.visible === "boolean") {
    nextState.visible = source.visible;
  }
  ["x", "y", "width", "height", "inlineHeight", "order"].forEach((key) => {
    const numericValue = Number(source[key]);
    if (Number.isFinite(numericValue)) {
      nextState[key as keyof ToolWindowState] = Math.round(numericValue) as never;
    }
  });
  return nextState;
}

function normalizeToolWindowStateMap(value: unknown): ToolWindowStateMap {
  const source = value && typeof value === "object" && !Array.isArray(value)
    ? value as Record<string, unknown>
    : {};
  const nextMap: ToolWindowStateMap = {};
  Object.entries(source).forEach(([key, entry]) => {
    const normalizedKey = String(key || "").trim();
    if (!normalizedKey) {
      return;
    }
    nextMap[normalizedKey] = normalizeToolWindowState(entry);
  });
  return nextMap;
}

function captureToolWindowStateMap(value: unknown): ToolWindowStateMap {
  const snapshot: ToolWindowStateMap = {};
  Object.entries(normalizeToolWindowStateMap(value)).forEach(([key, entry]) => {
    snapshot[key] = { ...entry };
  });
  return snapshot;
}

export function createPopupSessionStore(initialState: Partial<PopupSessionState> = {}) {
  let lastPersistHostWindow = window;
  let lastPersistStorageKey = TOOL_WINDOW_LAYOUT_STORAGE_KEY;
  const state: PopupSessionState = {
    activeLayer: 1,
    viewingAllLayers: false,
    visibleLayersById: {},
    activeSidebarTab: "layers",
    pan: { isPanning: false, startX: 0, startY: 0, scrollLeft: 0, scrollTop: 0 },
    draggingNpc: null,
    pointerCandidate: null,
    paintingStroke: null,
    dragDrawX: 0,
    dragDrawY: 0,
    isSaving: false,
    activeBrushTileId: "",
    activeGraphicsTab: "tiles",
    activeGraphicsRecordId: "",
    canvasToolMode: "paint",
    activeInstanceBrushId: "",
    activeEntityCategory: "friendly",
    hoverTileX: -1,
    hoverTileY: -1,
    selectedNpcId: "",
    selectedTile: null,
    spritePickerOpenNpcId: "",
    hoveredNpcId: "",
    hoverCanvasX: 0,
    hoverCanvasY: 0,
    templateSectionCollapsed: false,
    placedSectionCollapsed: false,
    drawLayerSectionCollapsed: false,
    heightLayerSectionCollapsed: false,
    organizedListDrag: null,
    tileMutationBatchDepth: 0,
    hideTileGrid: false,
    showChunkBounds: false,
    zoomPreviewUntil: 0,
    scrollPreviewUntil: 0,
    toolWindows: {},
    ...initialState,
  };
  state.toolWindows = normalizeToolWindowStateMap(state.toolWindows);

  function syncLayerVisibility(roomLayers: LayerLike[]) {
    const nextVisibleLayersById: Record<string, boolean> = {};
    roomLayers.forEach((layer) => {
      const layerKey = String(Number(layer.layer) || 0);
      nextVisibleLayersById[layerKey] = Object.prototype.hasOwnProperty.call(state.visibleLayersById, layerKey)
        ? state.visibleLayersById[layerKey] !== false
        : true;
    });
    state.visibleLayersById = nextVisibleLayersById;
    return state.visibleLayersById;
  }

  function setLayerVisibility(layerNumber: number, isVisible: boolean, roomLayers: LayerLike[]) {
    syncLayerVisibility(roomLayers);
    state.visibleLayersById[String(Number(layerNumber) || 0)] = isVisible !== false;
  }

  function isLayerVisible(layerNumber: number, roomLayers: LayerLike[]) {
    syncLayerVisibility(roomLayers);
    const layerKey = String(Number(layerNumber) || 0);
    return !Object.prototype.hasOwnProperty.call(state.visibleLayersById, layerKey) || state.visibleLayersById[layerKey] !== false;
  }

  function beginTileMutationBatch() {
    state.tileMutationBatchDepth += 1;
  }

  function endTileMutationBatch() {
    if (state.tileMutationBatchDepth <= 0) {
      state.tileMutationBatchDepth = 0;
      return;
    }
    state.tileMutationBatchDepth -= 1;
  }

  function getToolWindowState(key: string) {
    const normalizedKey = String(key || "").trim();
    if (!normalizedKey || !state.toolWindows[normalizedKey]) {
      return null;
    }
    return {
      ...state.toolWindows[normalizedKey],
    };
  }

  function setToolWindowState(key: string, value: unknown) {
    const normalizedKey = String(key || "").trim();
    if (!normalizedKey) {
      return null;
    }
    const mergedState: ToolWindowState = {
      ...(state.toolWindows[normalizedKey] || {}),
      ...normalizeToolWindowState(value),
    };
    state.toolWindows = {
      ...state.toolWindows,
      [normalizedKey]: mergedState,
    };
    return {
      ...mergedState,
    };
  }

  function capturePersistedLayoutState(): PersistedLayoutState {
    return {
      activeSidebarTab: String(state.activeSidebarTab || "").trim() || "layers",
      toolWindows: captureToolWindowStateMap(state.toolWindows),
    };
  }

  function restorePersistedLayout(hostWindow = window, storageKey = TOOL_WINDOW_LAYOUT_STORAGE_KEY) {
    try {
      const raw = hostWindow?.localStorage?.getItem(storageKey);
      if (!raw) {
        return capturePersistedLayoutState();
      }
      const parsed = JSON.parse(raw) as Partial<PersistedLayoutState>;
      const activeSidebarTab = String(parsed?.activeSidebarTab || "").trim();
      if (activeSidebarTab) {
        state.activeSidebarTab = activeSidebarTab;
      }
      state.toolWindows = normalizeToolWindowStateMap(parsed?.toolWindows);
    } catch {
      return capturePersistedLayoutState();
    }
    return capturePersistedLayoutState();
  }

  function persistPersistedLayout(hostWindow = window, storageKey = TOOL_WINDOW_LAYOUT_STORAGE_KEY) {
    lastPersistHostWindow = hostWindow || window;
    lastPersistStorageKey = storageKey || TOOL_WINDOW_LAYOUT_STORAGE_KEY;
    try {
      hostWindow?.localStorage?.setItem(storageKey, JSON.stringify(capturePersistedLayoutState()));
      return true;
    } catch {
      return false;
    }
  }

  const persistPersistedLayoutDeferredInternal = createDebouncedCallback(() => {
    persistPersistedLayout(lastPersistHostWindow, lastPersistStorageKey);
  }, 140);

  function persistPersistedLayoutDeferred(hostWindow = window, storageKey = TOOL_WINDOW_LAYOUT_STORAGE_KEY) {
    lastPersistHostWindow = hostWindow || window;
    lastPersistStorageKey = storageKey || TOOL_WINDOW_LAYOUT_STORAGE_KEY;
    persistPersistedLayoutDeferredInternal();
    return true;
  }

  function flushPersistedLayout(hostWindow = window, storageKey = TOOL_WINDOW_LAYOUT_STORAGE_KEY) {
    lastPersistHostWindow = hostWindow || window;
    lastPersistStorageKey = storageKey || TOOL_WINDOW_LAYOUT_STORAGE_KEY;
    return persistPersistedLayoutDeferredInternal.flush() || persistPersistedLayout(lastPersistHostWindow, lastPersistStorageKey);
  }

  function clearPersistedLayout(hostWindow = window, storageKey = TOOL_WINDOW_LAYOUT_STORAGE_KEY) {
    try {
      hostWindow?.localStorage?.removeItem(storageKey);
      state.toolWindows = {};
      return true;
    } catch {
      return false;
    }
  }

  return {
    state,
    syncLayerVisibility,
    setLayerVisibility,
    isLayerVisible,
    beginTileMutationBatch,
    endTileMutationBatch,
    getToolWindowState,
    setToolWindowState,
    capturePersistedLayoutState,
    restorePersistedLayout,
    persistPersistedLayout,
    persistPersistedLayoutDeferred,
    flushPersistedLayout,
    clearPersistedLayout,
  };
}

