<script lang="ts">
  // Modal stack 지원 — confirm 위에 nested 모달을 쌓을 수 있다.
  // z-index = z-modal + (stack 인덱스 * 2). backdrop / panel 둘 다 한 칸씩.
  // 최상단 modal만 focus trap 활성, 아래는 inert 처리.
  // native <dialog>를 쓰지 않는 이유: top-layer가 단일이라 stack을 지원하지 않음.
  import type { Snippet } from 'svelte';
  import { X } from 'lucide-svelte';
  import { ui } from '$lib/stores/uiState.svelte';
  import IconButton from './IconButton.svelte';

  type Size = 'sm' | 'md' | 'lg';

  interface Props {
    id: string;
    title?: string;
    size?: Size;
    closable?: boolean;
    children?: Snippet;
    footer?: Snippet;
    'aria-label'?: string;
  }

  let {
    id,
    title,
    size = 'md',
    closable = true,
    children,
    footer,
    'aria-label': ariaLabel,
  }: Props = $props();

  let open = $derived(ui.isModalOpen(id));
  let stackIndex = $derived(ui.modalIndex(id));
  let isTop = $derived(stackIndex === ui.modalStack.length - 1);

  // z-index 계산: backdrop과 panel을 별개의 stacking context로 두기 위해 *2
  let backdropZ = $derived(`calc(var(--z-modal) + ${stackIndex * 2})`);
  let panelZ = $derived(`calc(var(--z-modal) + ${stackIndex * 2 + 1})`);

  const sizeClass: Record<Size, string> = {
    sm: 'max-w-sm',
    md: 'max-w-md',
    lg: 'max-w-2xl',
  };

  // 패널 ref + focus trap
  let panelEl: HTMLDivElement | undefined = $state();

  // open 토글 시: 열릴 때 패널 안 첫 focusable로 포커스 이동
  $effect(() => {
    if (!open || !isTop || !panelEl) return;
    const focusables = panelEl.querySelectorAll<HTMLElement>(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    const first = focusables[0];
    if (first) first.focus();
  });

  function close() {
    if (closable) ui.closeModal(id);
  }

  // 최상단 modal 안에서만 Tab 사이클을 가둔다
  function onKeydown(e: KeyboardEvent) {
    if (!isTop || !panelEl) return;
    if (e.key !== 'Tab') return;
    const focusables = Array.from(
      panelEl.querySelectorAll<HTMLElement>(
        'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
      )
    );
    if (focusables.length === 0) return;
    const first = focusables[0];
    const last = focusables[focusables.length - 1];
    if (e.shiftKey && document.activeElement === first) {
      e.preventDefault();
      last.focus();
    } else if (!e.shiftKey && document.activeElement === last) {
      e.preventDefault();
      first.focus();
    }
  }
</script>

{#if open}
  <!-- backdrop -->
  <div
    class="fixed inset-0 bg-scrim transition-opacity"
    style="z-index: {backdropZ}"
    aria-hidden="true"
  ></div>

  <!-- panel container (centers modal) -->
  <div
    class="fixed inset-0 flex items-center justify-center p-4"
    style="z-index: {panelZ}"
    role="dialog"
    aria-modal="true"
    aria-label={ariaLabel ?? title}
    inert={!isTop ? true : undefined}
    onkeydown={onKeydown}
    tabindex="-1"
  >
    <div
      bind:this={panelEl}
      class={[
        'w-full bg-surface border border-default rounded-card shadow-xl',
        'flex flex-col max-h-[90vh]',
        sizeClass[size],
      ].join(' ')}
    >
      {#if title || closable}
        <header class="flex items-center justify-between px-5 py-3 border-b border-default shrink-0">
          {#if title}
            <h2 class="text-sm font-semibold text-text">{title}</h2>
          {:else}
            <span></span>
          {/if}
          {#if closable}
            <IconButton icon={X} aria-label="닫기" onclick={close} size="sm" />
          {/if}
        </header>
      {/if}

      <div class="flex-1 min-h-0 overflow-y-auto px-5 py-4 text-sm text-text">
        {@render children?.()}
      </div>

      {#if footer}
        <footer class="flex items-center justify-end gap-2 px-5 py-3 border-t border-default shrink-0">
          {@render footer()}
        </footer>
      {/if}
    </div>
  </div>
{/if}
