// SPEC: PJ2026-010405 云端控制台.
// Implementation reference: draft-2026-07-13-p0-cloud-console.

import { nextTick, onBeforeUnmount, type Ref, watch } from "vue";

interface OverlaySurfaceOptions {
  modal?: boolean;
  trapFocus?: boolean;
  closeOnEscape?: boolean;
  initialFocusSelector?: string;
}

const FOCUSABLE_SELECTOR = [
  "a[href]",
  "button:not([disabled])",
  "input:not([disabled])",
  "select:not([disabled])",
  "textarea:not([disabled])",
  "[tabindex]:not([tabindex='-1'])"
].join(",");

let overlayIdCounter = 0;
let scrollLockCount = 0;
const overlayStack: symbol[] = [];

export function nextOverlayId(prefix: string): string {
  overlayIdCounter += 1;
  return `${prefix}-${overlayIdCounter}`;
}

export function useOverlaySurface(
  open: Ref<boolean>,
  surface: Ref<HTMLElement | null>,
  requestClose: () => void,
  options: OverlaySurfaceOptions = {}
): { isTopmost: () => boolean } {
  const modal = options.modal ?? true;
  const trapFocus = options.trapFocus ?? modal;
  const closeOnEscape = options.closeOnEscape ?? true;
  let restoreTarget: HTMLElement | null = null;
  let ownsScrollLock = false;
  const overlayToken = Symbol("overlay-surface");

  watch(open, async (visible) => {
    if (!visible) {
      deactivate();
      return;
    }
    restoreTarget = document.activeElement instanceof HTMLElement ? document.activeElement : null;
    pushOverlay(overlayToken);
    if (modal) acquireScrollLock();
    window.addEventListener("keydown", onKeydown, true);
    document.addEventListener("focusin", onFocusIn, true);
    await nextTick();
    const target = options.initialFocusSelector
      ? surface.value?.querySelector<HTMLElement>(options.initialFocusSelector)
      : firstFocusable(surface.value);
    (target ?? surface.value)?.focus({ preventScroll: true });
  }, { immediate: true });

  onBeforeUnmount(deactivate);

  function onKeydown(event: KeyboardEvent): void {
    if (!open.value || !surface.value || !isTopmost()) return;
    if (event.key === "Escape" && closeOnEscape) {
      event.preventDefault();
      event.stopPropagation();
      requestClose();
      return;
    }
    if (event.key !== "Tab" || !trapFocus) return;
    const focusable = Array.from(surface.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(isFocusable);
    if (focusable.length === 0) {
      event.preventDefault();
      surface.value.focus({ preventScroll: true });
      return;
    }
    const first = focusable[0];
    const last = focusable.at(-1);
    if (!first || !last) return;
    if (event.shiftKey && document.activeElement === first) {
      event.preventDefault();
      last.focus();
    } else if (!event.shiftKey && document.activeElement === last) {
      event.preventDefault();
      first.focus();
    }
  }

  function onFocusIn(event: FocusEvent): void {
    if (!open.value || !trapFocus || !surface.value || !isTopmost()) return;
    if (event.target instanceof Node && surface.value.contains(event.target)) return;
    (firstFocusable(surface.value) ?? surface.value).focus({ preventScroll: true });
  }

  function isTopmost(): boolean {
    return overlayStack.at(-1) === overlayToken;
  }

  function acquireScrollLock(): void {
    if (ownsScrollLock) return;
    ownsScrollLock = true;
    scrollLockCount += 1;
    document.body.classList.add("console-scroll-locked");
  }

  function deactivate(): void {
    window.removeEventListener("keydown", onKeydown, true);
    document.removeEventListener("focusin", onFocusIn, true);
    removeOverlay(overlayToken);
    if (ownsScrollLock) {
      ownsScrollLock = false;
      scrollLockCount = Math.max(0, scrollLockCount - 1);
      if (scrollLockCount === 0) document.body.classList.remove("console-scroll-locked");
    }
    const target = restoreTarget;
    restoreTarget = null;
    if (target?.isConnected) void nextTick(() => target.focus({ preventScroll: true }));
  }

  return { isTopmost };
}

function pushOverlay(token: symbol): void {
  removeOverlay(token);
  overlayStack.push(token);
}

function removeOverlay(token: symbol): void {
  const index = overlayStack.lastIndexOf(token);
  if (index >= 0) overlayStack.splice(index, 1);
}

function firstFocusable(surface: HTMLElement | null): HTMLElement | null {
  return Array.from(surface?.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR) ?? []).find(isFocusable) ?? null;
}

function isFocusable(element: HTMLElement): boolean {
  return !element.hasAttribute("disabled") && element.getAttribute("aria-hidden") !== "true" && element.tabIndex >= 0;
}
