import { clampFloatingWindowRect } from "./floatingWindowUtils";
import {
  CHANGELOG_SECTIONS,
  CHANGELOG_SPLASH_FOOTNOTE,
  CHANGELOG_SPLASH_KICKER,
  CHANGELOG_SPLASH_TITLE,
  CHANGELOG_SPLASH_VERSION,
} from "./changelogData";
import type { ChangelogItem } from "./changelogData";

const CHANGELOG_SPLASH_WINDOW_KEY = "changelogSplash";
const CHANGELOG_SPLASH_STORAGE_KEY = `worldshaper:studio:changelog-seen:${CHANGELOG_SPLASH_VERSION}`;
const DEFAULT_WIDTH = 700;
const DEFAULT_HEIGHT = 560;
const MIN_WIDTH = 520;
const MIN_HEIGHT = 360;

type LayerRect = {
  left: number;
  top: number;
  width: number;
  height: number;
};

type PersistedWindowState = {
  visible?: boolean;
  x?: number;
  y?: number;
  width?: number;
  height?: number;
};

type ControllerScope = {
  uiScope?: {
    toolWindowLayerEl?: HTMLElement | null;
    editorBodyEl?: HTMLElement | null;
  };
  sessionScope?: {
    getPersistedToolWindowState?: (key: string) => PersistedWindowState | null;
    setPersistedToolWindowState?: (key: string, value: Record<string, unknown>) => void;
    persistPopupSessionLayout?: () => void;
  };
  persistPopupSessionLayout?: () => void;
};

type SplashState = {
  visible: boolean;
  x: number;
  y: number;
  width: number;
  height: number;
  shellEl: HTMLDivElement | null;
  titleEl: HTMLDivElement | null;
  metaEl: HTMLDivElement | null;
  sectionListEl: HTMLDivElement | null;
  actionBtnEl: HTMLButtonElement | null;
  resizeEl: HTMLDivElement | null;
  nextZIndex: number;
};

type OpenOptions = {
  center?: boolean;
  markSeen?: boolean;
};

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

export function createChangelogSplashWindowController(scope: ControllerScope) {
  let initialized = false;
  const uiScope = (scope.uiScope || scope) as NonNullable<ControllerScope["uiScope"]>;
  const sessionScope = (scope.sessionScope || scope) as NonNullable<ControllerScope["sessionScope"]>;
  const persistedState = typeof sessionScope.getPersistedToolWindowState === "function"
    ? sessionScope.getPersistedToolWindowState(CHANGELOG_SPLASH_WINDOW_KEY)
    : null;
  const state: SplashState = {
    visible: false,
    x: Number(persistedState?.x) || 88,
    y: Number(persistedState?.y) || 56,
    width: Number(persistedState?.width) || DEFAULT_WIDTH,
    height: Number(persistedState?.height) || DEFAULT_HEIGHT,
    shellEl: null,
    titleEl: null,
    metaEl: null,
    sectionListEl: null,
    actionBtnEl: null,
    resizeEl: null,
    nextZIndex: 132,
  };

  function getLayerRect(): LayerRect {
    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(CHANGELOG_SPLASH_WINDOW_KEY, {
        visible: state.visible === true,
        mode: "floating",
        x: state.x,
        y: state.y,
        width: state.width,
        height: state.height,
        order: 994,
      });
    }
    scope.persistPopupSessionLayout?.();
    sessionScope.persistPopupSessionLayout?.();
  }

  function markSeen() {
    try {
      window.localStorage.setItem(CHANGELOG_SPLASH_STORAGE_KEY, "1");
    } catch {
      // Ignore localStorage write errors and keep the splash functional.
    }
  }

  function hasSeenCurrentVersion() {
    try {
      return window.localStorage.getItem(CHANGELOG_SPLASH_STORAGE_KEY) === "1";
    } catch {
      return false;
    }
  }

  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.shellEl.style.left = `${Math.round(state.x)}px`;
    state.shellEl.style.top = `${Math.round(state.y)}px`;
    state.shellEl.style.width = `${Math.round(state.width)}px`;
    state.shellEl.style.height = `${Math.round(state.height)}px`;
  }

  function centerWindow() {
    const layerRect = getLayerRect();
    const centeredLeft = Math.max(0, ((Number(layerRect.width) || DEFAULT_WIDTH) - state.width) / 2);
    const centeredTop = Math.max(0, ((Number(layerRect.height) || DEFAULT_HEIGHT) - state.height) / 2);
    const nextRect = clampWindowRect(layerRect, centeredLeft, centeredTop, state.width, state.height);
    state.x = nextRect.left;
    state.y = nextRect.top;
    state.width = nextRect.width;
    state.height = nextRect.height;
  }

  function refresh() {
    if (state.titleEl) {
      state.titleEl.textContent = CHANGELOG_SPLASH_TITLE;
    }
    if (state.metaEl) {
      state.metaEl.textContent = `Release ${CHANGELOG_SPLASH_VERSION}`;
    }
    if (!state.sectionListEl) {
      return;
    }
    state.sectionListEl.innerHTML = "";
    CHANGELOG_SECTIONS.forEach((section) => {
      const sectionEl = document.createElement("section");
      sectionEl.className = "changelog-splash-section";
      const headingEl = document.createElement("h3");
      headingEl.className = "changelog-splash-section-title";
      headingEl.textContent = section.title;
      const listEl = document.createElement("ul");
      listEl.className = "changelog-splash-bullets";
      section.items.forEach((item) => {
        const itemEl = document.createElement("li");
        const normalizedItem: ChangelogItem = item;
        if (typeof normalizedItem === "string") {
          itemEl.textContent = normalizedItem;
        } else {
          const textEl = document.createElement("div");
          textEl.textContent = normalizedItem.text;
          itemEl.appendChild(textEl);
          if (normalizedItem.note) {
            const noteEl = document.createElement("div");
            noteEl.className = "changelog-splash-bullet-note";
            noteEl.textContent = normalizedItem.note;
            itemEl.appendChild(noteEl);
          }
        }
        listEl.appendChild(itemEl);
      });
      sectionEl.appendChild(headingEl);
      sectionEl.appendChild(listEl);
      state.sectionListEl?.appendChild(sectionEl);
    });
  }

  function ensureShell() {
    if (state.shellEl && state.shellEl.isConnected) {
      return state.shellEl;
    }
    const shellEl = document.createElement("div");
    shellEl.className = "tool-popout-window changelog-splash-window hidden";
    const titlebarEl = document.createElement("div");
    titlebarEl.className = "tool-popout-titlebar";
    titlebarEl.innerHTML =
      `<div class="tool-popout-title">What's New</div>` +
      `<div class="tool-popout-hint">Shown once per release</div>` +
      `<button class="tool-popout-close-btn" type="button" aria-label="Close changelog">X</button>`;
    const bodyEl = document.createElement("div");
    bodyEl.className = "tool-popout-body";
    const cardEl = document.createElement("div");
    cardEl.className = "changelog-splash-card";
    const heroEl = document.createElement("div");
    heroEl.className = "changelog-splash-hero";
    const kickerEl = document.createElement("div");
    kickerEl.className = "changelog-splash-kicker";
    kickerEl.textContent = CHANGELOG_SPLASH_KICKER;
    const titleEl = document.createElement("div");
    titleEl.className = "changelog-splash-title";
    const metaEl = document.createElement("div");
    metaEl.className = "changelog-splash-meta";
    heroEl.appendChild(kickerEl);
    heroEl.appendChild(titleEl);
    heroEl.appendChild(metaEl);
    const sectionListEl = document.createElement("div");
    sectionListEl.className = "changelog-splash-list";
    const footerEl = document.createElement("div");
    footerEl.className = "changelog-splash-footer";
    const footnoteEl = document.createElement("div");
    footnoteEl.className = "changelog-splash-footnote";
    footnoteEl.textContent = CHANGELOG_SPLASH_FOOTNOTE;
    const actionBtnEl = document.createElement("button");
    actionBtnEl.type = "button";
    actionBtnEl.className = "mini-btn";
    actionBtnEl.textContent = "Let's go";
    actionBtnEl.addEventListener("click", () => {
      close();
    });
    footerEl.appendChild(footnoteEl);
    footerEl.appendChild(actionBtnEl);
    cardEl.appendChild(heroEl);
    cardEl.appendChild(sectionListEl);
    cardEl.appendChild(footerEl);
    bodyEl.appendChild(cardEl);
    const resizeEl = document.createElement("div");
    resizeEl.className = "tool-popout-resize";
    const closeBtnEl = titlebarEl.querySelector<HTMLButtonElement>(".tool-popout-close-btn");
    shellEl.appendChild(titlebarEl);
    shellEl.appendChild(bodyEl);
    shellEl.appendChild(resizeEl);

    shellEl.addEventListener("pointerdown", () => {
      focusWindow();
    });
    titlebarEl.addEventListener("pointerdown", (event: PointerEvent) => {
      if (closeBtnEl && event.target instanceof Node && 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: PointerEvent) => {
        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();
    });
    resizeEl.addEventListener("pointerdown", (event: PointerEvent) => {
      if (event.button !== 0) {
        return;
      }
      event.preventDefault();
      focusWindow();
      const layerRect = getLayerRect();
      const startX = event.clientX;
      const startY = event.clientY;
      const originWidth = Number(state.width) || DEFAULT_WIDTH;
      const originHeight = Number(state.height) || DEFAULT_HEIGHT;
      const move = (moveEvent: PointerEvent) => {
        const nextRect = clampWindowRect(
          layerRect,
          state.x,
          state.y,
          Math.max(MIN_WIDTH, originWidth + (moveEvent.clientX - startX)),
          Math.max(MIN_HEIGHT, originHeight + (moveEvent.clientY - startY)),
        );
        state.width = nextRect.width;
        state.height = nextRect.height;
        applyWindowRect();
      };
      const up = () => {
        window.removeEventListener("pointermove", move);
        window.removeEventListener("pointerup", up);
        persistState();
      };
      window.addEventListener("pointermove", move);
      window.addEventListener("pointerup", up);
    });

    state.shellEl = shellEl;
    state.titleEl = titleEl;
    state.metaEl = metaEl;
    state.sectionListEl = sectionListEl;
    state.actionBtnEl = actionBtnEl;
    state.resizeEl = resizeEl;
    uiScope.toolWindowLayerEl?.appendChild(shellEl);
    applyWindowRect();
    shellEl.classList.toggle("hidden", state.visible !== true);
    refresh();
    return shellEl;
  }

  function open(options: OpenOptions = {}) {
    ensureShell();
    if (options.center === true) {
      centerWindow();
    } else {
      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;
    if (options.markSeen !== false) {
      markSeen();
    }
    refresh();
    state.shellEl?.classList.remove("hidden");
    applyWindowRect();
    focusWindow();
    persistState();
    return true;
  }

  function close() {
    state.visible = false;
    clearFocus();
    state.shellEl?.classList.add("hidden");
    persistState();
    return true;
  }

  function maybeOpenForCurrentVersion() {
    if (hasSeenCurrentVersion()) {
      return false;
    }
    return open({ markSeen: true, center: true });
  }

  function initialize() {
    if (initialized) {
      return;
    }
    initialized = true;
    ensureShell();
    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();
      persistState();
    });
    state.visible = false;
    state.shellEl?.classList.add("hidden");
  }

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

