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

const PANEL_ORDER = [
  "information",
  "layers",
  "tiles",
  "instances",
  "triggers",
  "paths",
  "transitions",
  "history",
];

const PANEL_LABELS = {
  information: "Settings",
  layers: "Layers",
  tiles: "Graphics",
  instances: "Entities",
  triggers: "Triggers",
  paths: "Paths",
  transitions: "Transitions",
  history: "History",
};

const PANEL_DEFAULT_SIZES = {
  information: { width: 340, height: 360 },
  layers: { width: 340, height: 430 },
  tiles: { width: 360, height: 540 },
  instances: { width: 380, height: 520 },
  triggers: { width: 320, height: 280 },
  paths: { width: 320, height: 280 },
  transitions: { width: 320, height: 280 },
  history: { width: 380, height: 340 },
};

const PANEL_MIN_SIZES = {
  default: { width: 260, height: 220 },
  history: { width: 320, height: 300 },
};

const DEFAULT_VISIBLE_PANELS = new Set(["layers"]);

export function createToolWindowController(scope) {
  let initialized = false;
  const uiScope = scope.uiScope || scope;
  const sessionScope = scope.sessionScope || scope;
  const panelEntries = PANEL_ORDER
    .map((key, index) => {
      const persistedState = typeof sessionScope.getPersistedToolWindowState === "function"
        ? sessionScope.getPersistedToolWindowState(key)
        : null;
      return {
        key,
        label: PANEL_LABELS[key] || key,
        buttonEl: uiScope[key + "TabBtn"],
        panelEl: uiScope[key + "Panel"],
        shellEl: null,
        bodyEl: null,
        titlebarEl: null,
        resizeEl: null,
        dockBtnEl: null,
        mode: persistedState?.mode === "floating" ? "floating" : "inline",
        visible: typeof persistedState?.visible === "boolean" ? persistedState.visible : DEFAULT_VISIBLE_PANELS.has(key),
        x: Number(persistedState?.x) || 0,
        y: Number(persistedState?.y) || 0,
        width: Number(persistedState?.width) || 0,
        height: Number(persistedState?.height) || 0,
        inlineHeight: Number(persistedState?.inlineHeight) || 0,
        order: Number.isFinite(Number(persistedState?.order)) ? Number(persistedState.order) : index,
      };
    })
    .filter((entry) => entry.buttonEl && entry.panelEl);
  panelEntries.sort((left, right) => left.order - right.order);
  const entryByKey = new Map(panelEntries.map((entry) => [entry.key, entry]));
  let nextZIndex = 60;
  let suppressedClickKey = "";

  function getEntry(key) {
    return entryByKey.get(String(key || "").trim()) || null;
  }

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

  function getStageRect() {
    return uiScope.stageEl?.getBoundingClientRect() || null;
  }

  function getDockRect() {
    return uiScope.sidebarTabsEl?.getBoundingClientRect() || null;
  }

  function getSidebarBodyRect() {
    return uiScope.sidebarPanelsHostEl?.getBoundingClientRect() || null;
  }

  function pointInsideRect(clientX, clientY, rect) {
    if (!rect) {
      return false;
    }
    return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
  }

  function pointInsideStage(clientX, clientY) {
    return pointInsideRect(clientX, clientY, getStageRect());
  }

  function pointInsideDock(clientX, clientY) {
    return pointInsideRect(clientX, clientY, getDockRect());
  }

  function pointInsideSidebarBody(clientX, clientY) {
    return pointInsideRect(clientX, clientY, getSidebarBodyRect());
  }

  function setDockTargetActive(isActive) {
    uiScope.sidebarTabsEl?.classList.toggle("dock-target", isActive === true);
  }

  function setSidebarBodyTargetActive(isActive) {
    uiScope.sidebarPanelsHostEl?.classList.toggle("sidebar-drop-target", isActive === true);
  }

  function getMinSize(key) {
    return PANEL_MIN_SIZES[key] || PANEL_MIN_SIZES.default;
  }

  function clampWindowRect(left, top, width, height) {
    const layerRect = getLayerRect();
    const clampedWidth = Math.max(180, Math.min(Math.max(180, width), Math.max(180, layerRect.width - 12)));
    const clampedHeight = Math.max(140, Math.min(Math.max(140, height), Math.max(140, layerRect.height - 12)));
    const maxLeft = Math.max(0, layerRect.width - clampedWidth);
    const maxTop = Math.max(0, layerRect.height - clampedHeight);
    return {
      left: Math.max(0, Math.min(maxLeft, left)),
      top: Math.max(0, Math.min(maxTop, top)),
      width: clampedWidth,
      height: clampedHeight,
    };
  }

  function focusWindow(entry) {
    if (!entry?.shellEl || entry.mode !== "floating" || entry.visible !== true) {
      return;
    }
    nextZIndex += 1;
    panelEntries.forEach((candidate) => {
      candidate.shellEl?.classList.remove("is-focused");
    });
    entry.shellEl.style.zIndex = String(nextZIndex);
    entry.shellEl.classList.add("is-focused");
  }

  function applyFloatingRect(entry) {
    if (!entry?.shellEl || entry.mode !== "floating") {
      return;
    }
    entry.shellEl.style.left = Math.round(entry.x) + "px";
    entry.shellEl.style.top = Math.round(entry.y) + "px";
    entry.shellEl.style.width = Math.round(entry.width) + "px";
    entry.shellEl.style.height = Math.round(entry.height) + "px";
  }

  function getSidebarInsertBefore(clientY, excludeShell) {
    if (!uiScope.sidebarPanelsHostEl) {
      return null;
    }
    const children = Array.from(uiScope.sidebarPanelsHostEl.children).filter((child) => {
      if (!(child instanceof HTMLElement) || child === excludeShell) {
        return false;
      }
      const style = window.getComputedStyle(child);
      return style.display !== "none";
    });
    return children.find((child) => {
      const rect = child.getBoundingClientRect();
      return clientY < rect.top + (rect.height / 2);
    }) || null;
  }

  function attachShellToInline(entry, clientY) {
    if (!entry?.shellEl || !uiScope.sidebarPanelsHostEl) {
      return;
    }
    const beforeNode = Number.isFinite(Number(clientY)) ? getSidebarInsertBefore(Number(clientY), entry.shellEl) : null;
    if (beforeNode) {
      uiScope.sidebarPanelsHostEl.insertBefore(entry.shellEl, beforeNode);
    } else {
      uiScope.sidebarPanelsHostEl.appendChild(entry.shellEl);
    }
  }

  function attachShellToFloating(entry) {
    if (!entry?.shellEl || !uiScope.toolWindowLayerEl) {
      return;
    }
    uiScope.toolWindowLayerEl.appendChild(entry.shellEl);
  }

  function clearDockHighlights() {
    setDockTargetActive(false);
    setSidebarBodyTargetActive(false);
  }

  function syncInlineOrderState() {
    if (!uiScope.sidebarPanelsHostEl) {
      return;
    }
    let nextOrder = 0;
    Array.from(uiScope.sidebarPanelsHostEl.children).forEach((child) => {
      if (!(child instanceof HTMLElement)) {
        return;
      }
      const key = String(child.getAttribute("data-panel-key") || "").trim();
      const entry = getEntry(key);
      if (!entry || entry.mode !== "inline") {
        return;
      }
      entry.order = nextOrder;
      nextOrder += 1;
    });
  }

  function persistPanelState() {
    syncInlineOrderState();
    if (typeof sessionScope.setPersistedToolWindowState === "function") {
      panelEntries.forEach((entry) => {
        sessionScope.setPersistedToolWindowState(entry.key, {
          mode: entry.mode,
          visible: entry.visible === true,
          x: entry.x,
          y: entry.y,
          width: entry.width,
          height: entry.height,
          inlineHeight: entry.inlineHeight,
          order: entry.order,
        });
      });
    }
    if (typeof scope.persistPopupSessionLayout === "function") {
      scope.persistPopupSessionLayout();
    } else if (typeof sessionScope.persistPopupSessionLayout === "function") {
      sessionScope.persistPopupSessionLayout();
    }
  }

  function updateShellPresentation(entry) {
    if (!entry?.shellEl) {
      return;
    }
    entry.shellEl.classList.toggle("tool-popout-window-inline", entry.mode === "inline");
    entry.shellEl.classList.toggle("hidden", entry.visible !== true);
    if (entry.titlebarEl) {
      const hintEl = entry.titlebarEl.querySelector(".tool-popout-hint");
      if (hintEl) {
        hintEl.textContent = entry.mode === "floating" ? "Drag to dock" : "Drag into canvas";
      }
    }
    if (entry.mode === "floating") {
      applyFloatingRect(entry);
    } else {
      entry.shellEl.style.left = "";
      entry.shellEl.style.top = "";
      entry.shellEl.style.width = "";
      entry.shellEl.style.height = Number(entry.inlineHeight) > 0 ? Math.round(entry.inlineHeight) + "px" : "";
      entry.shellEl.style.zIndex = "";
      entry.shellEl.classList.remove("is-focused");
    }
  }

  function syncPanels() {
    panelEntries.forEach((entry) => {
      entry.buttonEl.classList.toggle("popped", entry.mode === "floating");
      entry.buttonEl.classList.toggle("active", entry.visible === true);
      entry.buttonEl.classList.toggle(
        "tool-active-hidden",
        scope.activeSidebarTab === entry.key && entry.visible !== true,
      );
      entry.buttonEl.setAttribute("aria-pressed", entry.visible === true ? "true" : "false");
      entry.panelEl.classList.remove("hidden");
      updateShellPresentation(entry);
    });
  }

  function setActiveTool(key) {
    if (typeof scope.setSidebarTab === "function") {
      scope.setSidebarTab(key);
    } else {
      scope.activeSidebarTab = key;
      syncPanels();
    }
  }

  function ensureFloatingSize(entry, clientX, clientY) {
    if (Number(entry.width) > 0 && Number(entry.height) > 0) {
      return;
    }
    const defaultSize = PANEL_DEFAULT_SIZES[entry.key] || PANEL_DEFAULT_SIZES.tiles;
    const minSize = getMinSize(entry.key);
    const layerRect = getLayerRect();
    const preferredWidth = Math.max(minSize.width, defaultSize.width);
    const preferredHeight = Math.max(minSize.height, defaultSize.height);
    const relativeX = Number(clientX) - layerRect.left - (preferredWidth / 2);
    const relativeY = Number(clientY) - layerRect.top - 18;
    const nextRect = clampWindowRect(
      Number.isFinite(relativeX) ? relativeX : 350,
      Number.isFinite(relativeY) ? relativeY : 90,
      preferredWidth,
      preferredHeight,
    );
    entry.x = nextRect.left;
    entry.y = nextRect.top;
    entry.width = nextRect.width;
    entry.height = nextRect.height;
  }

  function moveEntryToInline(entry, options) {
    if (!entry || !entry.shellEl) {
      return false;
    }
    if (entry.mode === "floating") {
      const measuredHeight = Math.round(entry.shellEl.getBoundingClientRect().height || 0);
      if (measuredHeight > 0) {
        entry.inlineHeight = Math.max(180, measuredHeight);
      }
    }
    entry.mode = "inline";
    entry.visible = true;
    attachShellToInline(entry, options?.clientY);
    updateShellPresentation(entry);
    syncPanels();
    persistPanelState();
    return true;
  }

  function moveEntryToFloating(entry, options) {
    if (!entry || !entry.shellEl || !uiScope.toolWindowLayerEl) {
      return false;
    }
    entry.mode = "floating";
    entry.visible = true;
    ensureFloatingSize(entry, options?.clientX, options?.clientY);
    if (Number.isFinite(Number(options?.clientX)) && Number.isFinite(Number(options?.clientY))) {
      const layerRect = getLayerRect();
      const nextRect = clampWindowRect(
        Number(options.clientX) - layerRect.left - (entry.width / 2),
        Number(options.clientY) - layerRect.top - 18,
        entry.width,
        entry.height,
      );
      entry.x = nextRect.left;
      entry.y = nextRect.top;
      entry.width = nextRect.width;
      entry.height = nextRect.height;
    }
    attachShellToFloating(entry);
    updateShellPresentation(entry);
    syncPanels();
    focusWindow(entry);
    persistPanelState();
    return true;
  }

  function toggleEntryVisibility(entry) {
    if (!entry) {
      return;
    }
    entry.visible = entry.visible !== true;
    if (entry.visible && entry.mode === "floating") {
      focusWindow(entry);
    }
    syncPanels();
    persistPanelState();
  }

  function createShell(entry) {
    const shellEl = document.createElement("div");
    shellEl.className = "tool-popout-window";
    shellEl.setAttribute("data-panel-key", entry.key);

    const titlebarEl = document.createElement("div");
    titlebarEl.className = "tool-popout-titlebar";
    titlebarEl.innerHTML =
      '<div class="tool-popout-title">' + entry.label + "</div>" +
      '<div class="tool-popout-hint">Drag into canvas</div>' +
      '<button type="button" class="tool-popout-dock-btn" title="Send back to dock" aria-label="Send back to dock">' +
        '<span class="tool-popout-dock-icon">|&#8592;</span>' +
      "</button>";

    const bodyEl = document.createElement("div");
    bodyEl.className = "tool-popout-body";
    const resizeEl = document.createElement("div");
    resizeEl.className = "tool-popout-resize";
    const dockBtnEl = titlebarEl.querySelector(".tool-popout-dock-btn");

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

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

    titlebarEl.addEventListener("pointerdown", (event) => {
      if (dockBtnEl && dockBtnEl.contains(event.target)) {
        return;
      }
      if (event.button !== 0) {
        return;
      }
      event.preventDefault();
      setActiveTool(entry.key);

      const startX = event.clientX;
      const startY = event.clientY;
      const startMode = entry.mode;
      const originLeft = Number(entry.x) || 0;
      const originTop = Number(entry.y) || 0;
      let dragArmed = false;

      const move = (moveEvent) => {
        const distance = Math.hypot(moveEvent.clientX - startX, moveEvent.clientY - startY);
        if (!dragArmed && distance >= 6) {
          dragArmed = true;
          if (startMode === "inline") {
            moveEntryToFloating(entry, { clientX: moveEvent.clientX, clientY: moveEvent.clientY });
          }
        }
        if (!dragArmed) {
          return;
        }
        if (entry.mode === "floating") {
          const nextRect = clampWindowRect(
            originLeft + (moveEvent.clientX - startX),
            originTop + (moveEvent.clientY - startY),
            entry.width,
            entry.height,
          );
          entry.x = nextRect.left;
          entry.y = nextRect.top;
          applyFloatingRect(entry);
          const overDock = pointInsideDock(moveEvent.clientX, moveEvent.clientY);
          const overSidebar = !overDock && pointInsideSidebarBody(moveEvent.clientX, moveEvent.clientY);
          setDockTargetActive(overDock);
          setSidebarBodyTargetActive(overSidebar);
        } else if (entry.mode === "inline") {
          setSidebarBodyTargetActive(pointInsideSidebarBody(moveEvent.clientX, moveEvent.clientY));
        }
      };

      const up = (upEvent) => {
        window.removeEventListener("pointermove", move);
        window.removeEventListener("pointerup", up);
        if (!dragArmed) {
          clearDockHighlights();
          return;
        }
        if (entry.mode === "floating") {
          if (pointInsideDock(upEvent.clientX, upEvent.clientY) || pointInsideSidebarBody(upEvent.clientX, upEvent.clientY)) {
            moveEntryToInline(entry, { clientY: upEvent.clientY });
          }
        } else if (entry.mode === "inline") {
          if (pointInsideStage(upEvent.clientX, upEvent.clientY)) {
            moveEntryToFloating(entry, { clientX: upEvent.clientX, clientY: upEvent.clientY });
          } else if (pointInsideSidebarBody(upEvent.clientX, upEvent.clientY)) {
            attachShellToInline(entry, upEvent.clientY);
            syncPanels();
          }
        }
        clearDockHighlights();
        persistPanelState();
      };

      window.addEventListener("pointermove", move);
      window.addEventListener("pointerup", up);
    });

    dockBtnEl?.addEventListener("click", (event) => {
      event.preventDefault();
      event.stopPropagation();
      setActiveTool(entry.key);
      moveEntryToInline(entry, {});
    });

    resizeEl.addEventListener("pointerdown", (event) => {
      if (event.button !== 0) {
        return;
      }
      event.preventDefault();
      setActiveTool(entry.key);
      if (entry.mode === "floating") {
        focusWindow(entry);
      }
      const startX = event.clientX;
      const startY = event.clientY;
      const originWidth = Number(entry.width) || 0;
      const originHeight = Number(entry.height) || 0;
      const originInlineHeight = Number(entry.inlineHeight) > 0
        ? Number(entry.inlineHeight)
        : Math.round(entry.shellEl.getBoundingClientRect().height);
      const minSize = getMinSize(entry.key);
      const move = (moveEvent) => {
        if (entry.mode === "floating") {
          const nextRect = clampWindowRect(
            entry.x,
            entry.y,
            Math.max(minSize.width, originWidth + (moveEvent.clientX - startX)),
            Math.max(minSize.height, originHeight + (moveEvent.clientY - startY)),
          );
          entry.width = nextRect.width;
          entry.height = nextRect.height;
          applyFloatingRect(entry);
          return;
        }
        const sidebarBodyHeight = Math.round(uiScope.sidebarPanelsHostEl?.clientHeight || 0);
        const maxInlineHeight = Math.max(180, sidebarBodyHeight || originInlineHeight || 180);
        entry.inlineHeight = Math.max(
          180,
          Math.min(maxInlineHeight, originInlineHeight + (moveEvent.clientY - startY)),
        );
        updateShellPresentation(entry);
      };
      const up = () => {
        window.removeEventListener("pointermove", move);
        window.removeEventListener("pointerup", up);
        persistPanelState();
      };
      window.addEventListener("pointermove", move);
      window.addEventListener("pointerup", up);
    });

    entry.shellEl = shellEl;
    entry.bodyEl = bodyEl;
    entry.titlebarEl = titlebarEl;
    entry.resizeEl = resizeEl;
    entry.dockBtnEl = dockBtnEl;
    return shellEl;
  }

  function handleTabButtonClick(key) {
    const normalizedKey = String(key || "").trim();
    if (!normalizedKey) {
      return;
    }
    if (suppressedClickKey === normalizedKey) {
      suppressedClickKey = "";
      return;
    }
    const entry = getEntry(normalizedKey);
    if (!entry) {
      return;
    }
    setActiveTool(normalizedKey);
    toggleEntryVisibility(entry);
  }

  function restoreAllWindows() {
    panelEntries.forEach((entry, index) => {
      entry.mode = "inline";
      entry.visible = DEFAULT_VISIBLE_PANELS.has(entry.key) || scope.activeSidebarTab === entry.key;
      entry.x = 0;
      entry.y = 0;
      entry.width = 0;
      entry.height = 0;
      entry.inlineHeight = 0;
      entry.order = index;
      attachShellToInline(entry);
    });
    syncPanels();
    persistPanelState();
    if (typeof scope.setStatus === "function") {
      scope.setStatus("Restored the default tool window layout.", false);
    }
  }

  function bindTabDrag(entry) {
    if (!entry?.buttonEl) {
      return;
    }
    entry.buttonEl.addEventListener("pointerdown", (event) => {
      if (event.button !== 0) {
        return;
      }
      const startX = event.clientX;
      const startY = event.clientY;
      let dragArmed = false;
      entry.buttonEl.classList.add("drag-armed");
      const move = (moveEvent) => {
        if (dragArmed) {
          return;
        }
        const distance = Math.hypot(moveEvent.clientX - startX, moveEvent.clientY - startY);
        if (distance >= 8) {
          dragArmed = true;
        }
      };
      const up = (upEvent) => {
        window.removeEventListener("pointermove", move);
        window.removeEventListener("pointerup", up);
        entry.buttonEl.classList.remove("drag-armed");
        if (!dragArmed) {
          return;
        }
        suppressedClickKey = entry.key;
        window.setTimeout(() => {
          if (suppressedClickKey === entry.key) {
            suppressedClickKey = "";
          }
        }, 0);
        setActiveTool(entry.key);
        if (pointInsideStage(upEvent.clientX, upEvent.clientY)) {
          moveEntryToFloating(entry, { clientX: upEvent.clientX, clientY: upEvent.clientY });
          return;
        }
        if (pointInsideSidebarBody(upEvent.clientX, upEvent.clientY)) {
          moveEntryToInline(entry, { clientY: upEvent.clientY });
          return;
        }
        toggleEntryVisibility(entry);
      };
      window.addEventListener("pointermove", move);
      window.addEventListener("pointerup", up);
    });
  }

  function initialize() {
    if (initialized) {
      return;
    }
    initialized = true;
    panelEntries.forEach((entry) => {
      entry.panelEl.setAttribute("data-panel-key", entry.key);
      entry.panelEl.classList.remove("hidden");
      createShell(entry);
      if (entry.mode === "floating") {
        ensureFloatingSize(entry);
        const nextRect = clampWindowRect(entry.x, entry.y, entry.width, entry.height);
        entry.x = nextRect.left;
        entry.y = nextRect.top;
        entry.width = nextRect.width;
        entry.height = nextRect.height;
        attachShellToFloating(entry);
      } else {
        attachShellToInline(entry);
      }
      bindTabDrag(entry);
    });

    window.addEventListener("resize", () => {
      panelEntries.forEach((entry) => {
        if (!entry.shellEl) {
          return;
        }
        if (entry.mode === "floating") {
          const nextRect = clampWindowRect(entry.x, entry.y, entry.width, entry.height);
          entry.x = nextRect.left;
          entry.y = nextRect.top;
          entry.width = nextRect.width;
          entry.height = nextRect.height;
          applyFloatingRect(entry);
          return;
        }
        if (Number(entry.inlineHeight) > 0) {
          const maxInlineHeight = Math.max(180, Math.round(uiScope.sidebarPanelsHostEl?.clientHeight || 0) || 180);
          entry.inlineHeight = Math.min(Number(entry.inlineHeight), maxInlineHeight);
          updateShellPresentation(entry);
        }
      });
      persistPanelState();
    });

    syncPanels();
  }

  return {
    initialize,
    syncPanels,
    handleTabButtonClick,
    restoreAllWindows,
    isPanelFloating: (key) => getEntry(key)?.mode === "floating",
  };
}
