// 模态对话框焦点陷阱（WCAG 2.1.2：把焦点关在对话框内循环）。
// 自写 querySelectorAll 焦点循环，不引入新依赖。
// 纯决策核（trapTarget，可 node 单测）+ 薄 DOM 包装（handleTabTrap）。

// 可聚焦元素选择器（排除 disabled / tabindex=-1 / 隐藏）。
const FOCUSABLE_SELECTOR = [
  "a[href]",
  "button:not([disabled])",
  "input:not([disabled])",
  "select:not([disabled])",
  "textarea:not([disabled])",
  '[tabindex]:not([tabindex="-1"])',
].join(",");

// 取容器内当前可聚焦（且可见）的元素，按文档顺序。
export function focusableElements(container: HTMLElement): HTMLElement[] {
  const nodes = container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR);
  return Array.from(nodes).filter(
    (el) => el.offsetParent !== null || el === document.activeElement,
  );
}

export type TrapAction = "none" | "first" | "last" | "container";

// 纯决策：给定可聚焦数量、当前焦点在循环中的位置、容器是否含焦点、是否 Shift+Tab，
// 决定 Tab 应跳到何处（环回首/尾/容器）。-1 表示 active 不在 focusable 列表内。
export function trapTarget(args: {
  focusableCount: number;
  activeIndex: number; // active 在 focusable 中的下标；-1 = 不在列表
  containsActive: boolean; // 容器是否包含当前焦点
  shiftKey: boolean;
}): TrapAction {
  const { focusableCount, activeIndex, containsActive, shiftKey } = args;
  if (focusableCount === 0) return "container";
  if (shiftKey) {
    // 在首元素或焦点已逃出容器 → 环回到尾。
    if (activeIndex === 0 || !containsActive) return "last";
    return "none";
  }
  // 在尾元素或焦点已逃出容器 → 环回到首。
  if (activeIndex === focusableCount - 1 || !containsActive) return "first";
  return "none";
}

// 处理 Tab / Shift+Tab：在容器内循环。返回 true 表示已接管（已 preventDefault）。
export function handleTabTrap(
  container: HTMLElement,
  event: { key: string; shiftKey: boolean; preventDefault: () => void },
): boolean {
  if (event.key !== "Tab") return false;
  const focusable = focusableElements(container);
  const active = document.activeElement as HTMLElement | null;
  const containsActive = active !== null && container.contains(active);
  const activeIndex =
    active !== null ? focusable.indexOf(active) : -1;

  const action = trapTarget({
    focusableCount: focusable.length,
    activeIndex,
    containsActive,
    shiftKey: event.shiftKey,
  });

  switch (action) {
    case "none":
      return false;
    case "container":
      event.preventDefault();
      container.focus();
      return true;
    case "first":
      event.preventDefault();
      focusable[0]?.focus();
      return true;
    case "last":
      event.preventDefault();
      focusable[focusable.length - 1]?.focus();
      return true;
  }
}
