<script>
  // 자료실 — 분류 체계 + 트리 문서 관리
  import { onMount } from 'svelte';
  import { page } from '$app/stores';
  import { goto } from '$app/navigation';
  import { api, getAccessToken } from '$lib/api';
  import { addToast } from '$lib/stores/toast';
  import { ui } from '$lib/stores/uiState.svelte';
  import {
    ChevronRight,
    ChevronDown,
    FolderOpen,
    ExternalLink,
    Download,
    FileText,
    Upload,
    Plus,
    MoreHorizontal,
    FolderPlus,
    Pencil,
    Trash2,
  } from 'lucide-svelte';
  import DocumentCard from '$lib/components/DocumentCard.svelte';
  import FormatIcon from '$lib/components/FormatIcon.svelte';
  import Button from '$lib/components/ui/Button.svelte';
  import EmptyState from '$lib/components/ui/EmptyState.svelte';
  import Modal from '$lib/components/ui/Modal.svelte';
  import ConfirmDialog from '$lib/components/ui/ConfirmDialog.svelte';
  import Skeleton from '$lib/components/ui/Skeleton.svelte';
  import SuggestionReview from '$lib/components/SuggestionReview.svelte';

  // §2: /library 는 "자료실 + 승인 대기함" 2역할을 겸한다.
  // 승인/반려 후 자료실 목록·트리·facet count 를 재조회.
  function handleSuggestionChange() {
    loadTree();
    loadDocs();
    loadFacetCounts();
  }

  // ─── 상태 ───

  let tree = $state([]);
  let treeLoading = $state(true);
  let expanded = $state({});

  let docs = $state([]);
  let docsLoading = $state(false);
  let total = $state(0);

  let activePath = $derived($page.url.searchParams.get('path'));
  let activeSort = $derived($page.url.searchParams.get('sort') || 'updated_desc');
  let activeQ = $derived($page.url.searchParams.get('q') || '');
  let activePage = $derived(Number($page.url.searchParams.get('page')) || 1);

  const SORT_OPTIONS = [
    { value: 'updated_desc', label: '최근 수정순' },
    { value: 'title_asc', label: '제목순' },
    { value: 'created_desc', label: '등록순' },
  ];

  const ODF_FORMATS = ['ods', 'odt', 'odp', 'odoc', 'osheet'];
  const DEFAULT_LIBRARY_PATH = '미분류';
  const MAX_DEPTH = 5;
  const FACET_LABELS = { company: '회사', topic: '주제', year: '연도', doctype: '문서유형' };
  const FACET_KEYS = ['facet_company', 'facet_topic', 'facet_year', 'facet_doctype'];

  // ─── Facet 상태 ───

  let facetCounts = $state({ company: [], topic: [], year: [], doctype: [] });
  let facetLoading = $state(false);

  let activeFacetCompany = $derived($page.url.searchParams.get('facet_company'));
  let activeFacetTopic = $derived($page.url.searchParams.get('facet_topic'));
  let activeFacetYear = $derived($page.url.searchParams.get('facet_year'));
  let activeFacetDoctype = $derived($page.url.searchParams.get('facet_doctype'));
  let activeUnread = $derived($page.url.searchParams.get('unread') === 'true');
  let hasAnyFacet = $derived(activeFacetCompany || activeFacetTopic || activeFacetYear || activeFacetDoctype);

  // ─── 카테고리 CRUD 상태 ───

  let addParentPath = $state(null);
  let newCategoryName = $state('');
  let addLoading = $state(false);

  let renameTarget = $state(null); // { path, name }
  let renameValue = $state('');
  let renameLoading = $state(false);

  let deleteTarget = $state(null); // { path, name }

  // 컨텍스트 메뉴
  let contextNode = $state(null);
  let showContextMenu = $state(false);
  let contextPos = $state({ x: 0, y: 0 });

  // ─── 데이터 로드 ───

  async function loadTree() {
    treeLoading = true;
    try {
      tree = await api('/library/tree');
    } catch {
      addToast('error', '자료실 트리 로딩 실패');
    } finally {
      treeLoading = false;
    }
  }

  async function loadDocs() {
    docsLoading = true;
    try {
      const params = new URLSearchParams();
      if (activePath) params.set('path', activePath);
      if (activeSort) params.set('sort', activeSort);
      if (activeQ) params.set('q', activeQ);
      if (activeFacetCompany) params.set('facet_company', activeFacetCompany);
      if (activeFacetTopic) params.set('facet_topic', activeFacetTopic);
      if (activeFacetYear) params.set('facet_year', activeFacetYear);
      if (activeFacetDoctype) params.set('facet_doctype', activeFacetDoctype);
      if (activeUnread) params.set('unread', 'true');
      params.set('page', String(activePage));
      params.set('page_size', '20');
      const result = await api(`/documents/library?${params}`);
      docs = result.items;
      total = result.total;
    } catch {
      addToast('error', '자료실 문서 로딩 실패');
    } finally {
      docsLoading = false;
    }
  }

  async function loadFacetCounts() {
    facetLoading = true;
    try {
      const params = new URLSearchParams();
      if (activePath) params.set('library_path', activePath);
      if (activeQ) params.set('q', activeQ);
      if (activeFacetCompany) params.set('facet_company', activeFacetCompany);
      if (activeFacetTopic) params.set('facet_topic', activeFacetTopic);
      if (activeFacetYear) params.set('facet_year', activeFacetYear);
      if (activeFacetDoctype) params.set('facet_doctype', activeFacetDoctype);
      facetCounts = await api(`/library/facet-counts?${params}`);
    } catch {
      /* facet 실패해도 문서 목록은 정상 */
    } finally {
      facetLoading = false;
    }
  }

  onMount(() => {
    loadTree();
  });

  // 문서 목록: 모든 URL 파라미터 변경 시 재로드
  $effect(() => {
    // eslint-disable-next-line no-unused-expressions
    activePath, activeSort, activeQ, activePage, activeFacetCompany, activeFacetTopic, activeFacetYear, activeFacetDoctype, activeUnread;
    loadDocs();
  });

  // facet counts: path/q/facet 변경 시만 재집계 (page/sort 제외)
  $effect(() => {
    // eslint-disable-next-line no-unused-expressions
    activePath, activeQ, activeFacetCompany, activeFacetTopic, activeFacetYear, activeFacetDoctype;
    loadFacetCounts();
  });

  // 선택된 경로의 부모 자동 펼치기
  $effect(() => {
    if (activePath) {
      const parts = activePath.split('/');
      let path = '';
      for (const part of parts) {
        path = path ? `${path}/${part}` : part;
        expanded[path] = true;
      }
    }
  });

  let totalCount = $derived(tree.reduce((sum, n) => sum + n.count, 0));

  // ─── 네비게이션 ───

  function navigate(path) {
    const params = new URLSearchParams($page.url.searchParams);
    params.delete('page');
    if (path) {
      params.set('path', path);
    } else {
      params.delete('path');
    }
    const qs = params.toString();
    goto(`/library${qs ? '?' + qs : ''}`, { noScroll: true });
  }

  function setSort(sort) {
    const params = new URLSearchParams($page.url.searchParams);
    params.set('sort', sort);
    params.delete('page');
    goto(`/library?${params}`, { noScroll: true });
  }

  function setSearch(q) {
    const params = new URLSearchParams($page.url.searchParams);
    if (q) {
      params.set('q', q);
    } else {
      params.delete('q');
    }
    params.delete('page');
    goto(`/library?${params}`, { noScroll: true });
  }

  function toggleFacet(key, value) {
    const params = new URLSearchParams($page.url.searchParams);
    const current = params.get(key);
    if (current === String(value)) {
      params.delete(key);
    } else {
      params.set(key, String(value));
    }
    params.delete('page');
    goto(`/library?${params}`, { noScroll: true });
  }

  function clearAllFacets() {
    const params = new URLSearchParams($page.url.searchParams);
    for (const k of FACET_KEYS) params.delete(k);
    params.delete('page');
    goto(`/library?${params}`, { noScroll: true });
  }

  function toggleExpand(path) {
    expanded[path] = !expanded[path];
  }

  // ─── 문서 액션 ───

  function getEditUrl(doc) {
    if (doc.edit_url) return doc.edit_url;
    if (ODF_FORMATS.includes(doc.file_format)) return 'https://link.hyungi.net';
    return null;
  }

  function downloadOriginal(doc) {
    window.open(`/api/documents/${doc.id}/file?token=${getAccessToken()}&download=true`);
  }

  function downloadPdf(doc) {
    window.open(`/api/documents/${doc.id}/preview?token=${getAccessToken()}&download=true`);
  }

  // ─── 업로드 (버튼 + 드래그) ───

  const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
  let fileInput;
  let uploadingCount = $state(0);
  let dragging = $state(false);
  let dragCounter = 0;

  onMount(() => {
    function onDragEnter(e) { e.preventDefault(); dragCounter++; dragging = true; }
    function onDragOver(e) { e.preventDefault(); }
    function onDragLeave(e) { e.preventDefault(); dragCounter--; if (dragCounter <= 0) { dragging = false; dragCounter = 0; } }
    function onDrop(e) { e.preventDefault(); dragging = false; dragCounter = 0; uploadFiles(e.dataTransfer?.files); }
    window.addEventListener('dragenter', onDragEnter);
    window.addEventListener('dragover', onDragOver);
    window.addEventListener('dragleave', onDragLeave);
    window.addEventListener('drop', onDrop);
    return () => {
      window.removeEventListener('dragenter', onDragEnter);
      window.removeEventListener('dragover', onDragOver);
      window.removeEventListener('dragleave', onDragLeave);
      window.removeEventListener('drop', onDrop);
    };
  });

  function handleUpload(e) {
    uploadFiles(e.target.files);
    fileInput.value = '';
  }

  async function uploadFiles(fileList) {
    const allFiles = Array.from(fileList || []);
    const files = allFiles.filter(f => f.size <= MAX_UPLOAD_BYTES);
    const tooLarge = allFiles.filter(f => f.size > MAX_UPLOAD_BYTES);
    if (tooLarge.length > 0) {
      addToast('error', `100MB 초과 파일 ${tooLarge.length}건 제외됨`);
    }
    if (files.length === 0) return;

    uploadingCount = files.length;
    let success = 0;

    for (const file of files) {
      const formData = new FormData();
      formData.append('file', file);
      formData.append('doc_purpose', 'business');
      formData.append('library_path', activePath || DEFAULT_LIBRARY_PATH);
      if (activeFacetCompany) formData.append('facet_company', activeFacetCompany);
      if (activeFacetTopic) formData.append('facet_topic', activeFacetTopic);
      if (activeFacetYear) formData.append('facet_year', activeFacetYear);
      if (activeFacetDoctype) formData.append('facet_doctype', activeFacetDoctype);
      try {
        await api('/documents/', { method: 'POST', body: formData });
        success++;
      } catch {
        addToast('error', `${file.name} 업로드 실패`);
      }
    }

    uploadingCount = 0;

    if (success > 0) {
      addToast('success', `${success}건 업로드 완료`);
      loadTree();
      loadDocs();
    }
  }

  // ─── 카테고리 CRUD ───

  function openContextMenu(e, node) {
    e.stopPropagation();
    contextNode = node;
    contextPos = { x: e.clientX, y: e.clientY };
    showContextMenu = true;
  }

  function closeContextMenu() {
    showContextMenu = false;
    contextNode = null;
  }

  function openAddCategory(parentPath) {
    addParentPath = parentPath;
    newCategoryName = '';
    closeContextMenu();
    ui.openModal('add-category');
  }

  function openRenameCategory(node) {
    renameTarget = { path: node.path, name: node.name };
    renameValue = node.name;
    closeContextMenu();
    ui.openModal('rename-category');
  }

  function openDeleteCategory(node) {
    deleteTarget = { path: node.path, name: node.name };
    closeContextMenu();
    ui.openModal('delete-category');
  }

  async function createCategory() {
    const name = newCategoryName.trim();
    if (!name) return;
    const fullPath = addParentPath ? `${addParentPath}/${name}` : name;
    addLoading = true;
    try {
      await api('/library/categories', {
        method: 'POST',
        body: JSON.stringify({ path: fullPath }),
      });
      addToast('success', `"${name}" 분류 생성됨`);
      ui.closeModal('add-category');
      loadTree();
    } catch (err) {
      addToast('error', err?.detail || '분류 생성 실패');
    } finally {
      addLoading = false;
    }
  }

  async function renameCategory() {
    const name = renameValue.trim();
    if (!name || !renameTarget) return;
    renameLoading = true;
    try {
      const result = await api('/library/categories', {
        method: 'PATCH',
        body: JSON.stringify({ path: renameTarget.path, new_name: name }),
      });
      addToast('success', `"${renameTarget.name}" → "${name}" 변경됨`);
      ui.closeModal('rename-category');
      // 현재 선택 경로가 변경된 경우 URL 갱신
      if (activePath === renameTarget.path) {
        navigate(result.path);
      }
      loadTree();
      loadDocs();
    } catch (err) {
      addToast('error', err?.detail || '이름 변경 실패');
    } finally {
      renameLoading = false;
    }
  }

  async function deleteCategory() {
    if (!deleteTarget) return;
    try {
      await api(`/library/categories?path=${encodeURIComponent(deleteTarget.path)}`, {
        method: 'DELETE',
      });
      addToast('success', `"${deleteTarget.name}" 분류 삭제됨`);
      ui.closeModal('delete-category');
      if (activePath === deleteTarget.path) {
        navigate(null);
      }
      loadTree();
    } catch (err) {
      addToast('error', err?.detail || '삭제 실패');
    }
  }

  // 전역 클릭으로 컨텍스트 메뉴 닫기
  onMount(() => {
    function onClickOutside() { showContextMenu = false; }
    window.addEventListener('click', onClickOutside);
    return () => window.removeEventListener('click', onClickOutside);
  });

  // ─── 검색 debounce ───

  let searchInput = $state(activeQ);
  let searchTimer;

  function handleSearchInput(e) {
    searchInput = e.target.value;
    clearTimeout(searchTimer);
    searchTimer = setTimeout(() => setSearch(searchInput), 300);
  }

  // 페이지네이션
  let totalPages = $derived(Math.ceil(total / 20));
</script>

<div class="p-4 lg:p-6">
  <!-- breadcrumb -->
  <div class="flex flex-wrap items-center gap-2 text-sm mb-4 text-dim">
    <a href="/documents" class="hover:text-text">문서</a>
    <span class="text-faint">/</span>
    <span class="text-text">자료실</span>
    {#if activePath}
      {#each activePath.split('/') as segment, i}
        <span class="text-faint">/</span>
        <button
          type="button"
          onclick={() => navigate(activePath.split('/').slice(0, i + 1).join('/'))}
          class="hover:text-text min-w-0 truncate max-w-[40vw]"
        >
          {segment}
        </button>
      {/each}
    {/if}
  </div>

  <!-- 승인 대기함 (§2) — ai_suggestion.proposed_category='library' 문서 -->
  <div class="max-w-[1680px] mx-auto mb-4">
    <SuggestionReview
      proposedCategory="library"
      onChange={handleSuggestionChange}
    />
  </div>

  <div class="max-w-[1680px] mx-auto grid grid-cols-1 lg:grid-cols-12 gap-6">
    <!-- 왼쪽: 트리 (5/12) -->
    <aside class="lg:col-span-5 xl:col-span-4">
      <div class="bg-surface border border-default rounded-card p-3">
        <h2 class="text-xs font-semibold text-dim uppercase tracking-wider mb-2">분류</h2>

        <!-- 전체 + 새 분류 버튼 -->
        <div class="flex items-center mb-1">
          <button
            onclick={() => navigate(null)}
            class="flex-1 flex items-center justify-between px-3 py-2 rounded-md text-sm transition-colors
              {!activePath ? 'bg-accent/15 text-accent' : 'text-text hover:bg-surface-hover'}"
          >
            <span class="flex items-center gap-2">
              <FolderOpen size={16} />
              전체
            </span>
            {#if totalCount > 0}
              <span class="text-xs text-dim">{totalCount}</span>
            {/if}
          </button>
          <button
            onclick={() => openAddCategory(null)}
            class="p-1.5 rounded hover:bg-surface-hover text-dim hover:text-accent ml-1"
            title="새 분류 추가"
          >
            <Plus size={14} />
          </button>
        </div>

        <!-- 트리 -->
        {#if treeLoading}
          {#each Array(3) as _}
            <div class="h-8 bg-bg rounded-md animate-pulse mx-1 mb-1"></div>
          {/each}
        {:else if tree.length === 0}
          <p class="text-xs text-dim px-3 py-4">
            아직 자료실에 등록된 문서가 없습니다.<br />
            문서 상세 페이지에서 자료실 경로를 추가하세요.
          </p>
        {:else}
          <nav>
            {#each tree as node}
              {#snippet treeNode(n, depth)}
                {@const isActive = activePath === n.path}
                {@const isParent = activePath?.startsWith(n.path + '/')}
                {@const hasChildren = n.has_children || n.children.length > 0}
                {@const isExpanded = expanded[n.path]}
                {@const nodeDepth = n.path.split('/').length}

                <div class="group flex items-center" style="padding-left: {depth * 16}px">
                  {#if hasChildren}
                    <button
                      onclick={() => toggleExpand(n.path)}
                      class="p-0.5 rounded hover:bg-surface-hover text-dim"
                    >
                      {#if isExpanded}
                        <ChevronDown size={14} />
                      {:else}
                        <ChevronRight size={14} />
                      {/if}
                    </button>
                  {:else}
                    <span class="w-5"></span>
                  {/if}

                  <button
                    onclick={() => navigate(n.path)}
                    class="flex-1 min-w-0 flex items-center justify-between px-2 py-1.5 rounded-md text-sm transition-colors
                      {isActive
                      ? 'bg-accent/15 text-accent'
                      : isParent
                        ? 'text-text'
                        : 'text-dim hover:bg-surface-hover hover:text-text'}"
                  >
                    <span class="truncate min-w-0">{n.name}</span>
                    <span class="text-xs text-dim shrink-0 ml-2">{n.count}</span>
                  </button>

                  <!-- 카테고리 액션 메뉴 -->
                  <button
                    onclick={(e) => openContextMenu(e, n)}
                    class="p-0.5 rounded hover:bg-surface-hover text-dim opacity-0 group-hover:opacity-100 transition-opacity"
                    title="분류 관리"
                  >
                    <MoreHorizontal size={14} />
                  </button>
                </div>

                {#if hasChildren && isExpanded}
                  {#each n.children as child}
                    {@render treeNode(child, depth + 1)}
                  {/each}
                {/if}
              {/snippet}
              {@render treeNode(node, 0)}
            {/each}
          </nav>
        {/if}
      </div>

      <!-- Facet 필터 패널 -->
      {#if facetCounts.company.length > 0 || facetCounts.topic.length > 0 || facetCounts.year.length > 0 || facetCounts.doctype.length > 0}
        <div class="bg-surface border border-default rounded-card p-3 mt-3">
          <div class="flex items-center justify-between mb-2">
            <h2 class="text-xs font-semibold text-dim uppercase tracking-wider">탐색 축</h2>
            {#if hasAnyFacet}
              <button
                onclick={clearAllFacets}
                class="text-[10px] text-dim hover:text-accent"
              >
                초기화
              </button>
            {/if}
          </div>

          {#each Object.entries(FACET_LABELS) as [key, label]}
            {@const items = facetCounts[key] || []}
            {@const activeValue = key === 'company' ? activeFacetCompany : key === 'topic' ? activeFacetTopic : key === 'year' ? activeFacetYear : activeFacetDoctype}
            {@const paramKey = `facet_${key}`}
            {#if items.length > 0}
              <div class="mb-2.5">
                <h3 class="text-[10px] text-faint uppercase mb-1">{label}</h3>
                <div class="flex flex-wrap gap-1">
                  {#each items as item}
                    {@const isActive = activeValue === String(item.value)}
                    {@const isDisabled = item.count === 0 && !isActive}
                    <button
                      onclick={() => toggleFacet(paramKey, item.value)}
                      disabled={isDisabled}
                      class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] border transition-colors
                        {isActive
                          ? 'bg-accent/15 border-accent/30 text-accent'
                          : isDisabled
                            ? 'border-default text-faint cursor-not-allowed opacity-50'
                            : 'border-default text-dim hover:border-accent/30 hover:text-text'}"
                    >
                      <span class="truncate max-w-[120px]">{item.value}</span>
                      <span class="text-[9px] {isActive ? 'text-accent/70' : 'text-faint'}">{item.count}</span>
                    </button>
                  {/each}
                </div>
              </div>
            {/if}
          {/each}
        </div>
      {/if}
    </aside>

    <!-- 오른쪽: 문서 목록 (7/12) -->
    <main class="lg:col-span-7 xl:col-span-8">
      <!-- 컨트롤 바 -->
      <div class="flex flex-wrap items-center gap-2 mb-4">
        <!-- 업로드 -->
        <input type="file" multiple bind:this={fileInput} onchange={handleUpload} class="hidden" />
        <Button
          variant="secondary"
          size="sm"
          icon={Upload}
          onclick={() => fileInput.click()}
          disabled={uploadingCount > 0}
        >
          {uploadingCount > 0 ? `업로드 중 (${uploadingCount})` : '업로드'}
        </Button>

        <!-- 업로드 시 적용될 facet 미리보기 -->
        {#if hasAnyFacet}
          <span class="text-[10px] text-faint">
            적용: {[
              activeFacetCompany && `회사:${activeFacetCompany}`,
              activeFacetTopic && `주제:${activeFacetTopic}`,
              activeFacetYear && `연도:${activeFacetYear}`,
              activeFacetDoctype && `유형:${activeFacetDoctype}`,
            ].filter(Boolean).join(' / ')}
          </span>
        {/if}

        <!-- 정렬 -->
        <select
          value={activeSort}
          onchange={(e) => setSort(e.target.value)}
          class="px-2 py-1.5 bg-surface border border-default rounded text-xs text-text outline-none focus:border-accent"
        >
          {#each SORT_OPTIONS as opt}
            <option value={opt.value}>{opt.label}</option>
          {/each}
        </select>

        <!-- 안 본 자료만 토글 -->
        <button
          type="button"
          onclick={() => {
            const params = new URLSearchParams($page.url.searchParams);
            if (activeUnread) params.delete('unread');
            else params.set('unread', 'true');
            params.delete('page');
            goto(`/library?${params}`, { noScroll: true });
          }}
          style="touch-action: manipulation; user-select: none; -webkit-tap-highlight-color: transparent;"
          class="px-2 py-1.5 rounded text-xs border transition-colors
            {activeUnread ? 'bg-accent/15 text-accent border-accent/40' : 'bg-surface text-dim border-default hover:text-text'}"
          aria-pressed={activeUnread}
        >
          {activeUnread ? '✓ 안 본 자료만' : '안 본 자료만'}
        </button>

        <!-- 제목 검색 -->
        <input
          type="text"
          value={searchInput}
          oninput={handleSearchInput}
          placeholder="제목 검색..."
          class="flex-1 min-w-[120px] max-w-[240px] px-2 py-1.5 bg-surface border border-default rounded text-xs text-text outline-none focus:border-accent"
        />

        <span class="text-xs text-dim ml-auto">{total}건</span>
      </div>

      <!-- 문서 목록 -->
      {#if docsLoading}
        <div class="space-y-2">
          {#each Array(5) as _}
            <Skeleton h="h-20" rounded="lg" />
          {/each}
        </div>
      {:else if docs.length === 0}
        <EmptyState
          icon={FolderOpen}
          title={activePath ? `"${activePath}" 경로에 문서가 없습니다` : '자료실이 비어 있습니다'}
          description="문서 상세 페이지에서 자료실 경로를 추가하세요."
        />
      {:else}
        <div class="space-y-2">
          {#each docs as doc (doc.id)}
            <div class="bg-surface border border-default rounded-lg hover:border-accent/50 transition-colors">
              <!-- 카드 상단: 문서 정보 -->
              <a
                href="/documents/{doc.id}"
                class="flex items-start gap-3 p-3"
              >
                <div class="shrink-0 mt-0.5 text-dim">
                  <FormatIcon format={doc.file_format} size={18} />
                </div>
                <div class="flex-1 min-w-0">
                  <p class="text-sm font-medium truncate hover:text-accent">{doc.title || '제목 없음'}</p>
                  <div class="flex items-center gap-2 mt-0.5 text-[11px] text-dim">
                    <span>{doc.file_format}</span>
                    {#if doc.updated_at}
                      <span class="text-faint">·</span>
                      <span>
                        {new Date(doc.updated_at).toLocaleDateString('ko-KR', {
                          year: 'numeric',
                          month: 'short',
                          day: 'numeric',
                        })}
                      </span>
                    {/if}
                  </div>
                </div>
              </a>

              <!-- 카드 하단: 액션 버튼 -->
              <div class="flex items-center gap-1.5 px-3 pb-2">
                {#if getEditUrl(doc)}
                  <a
                    href={getEditUrl(doc)}
                    target="_blank"
                    rel="noopener noreferrer"
                    class="inline-flex items-center gap-1 px-2 py-1 text-[11px] text-dim hover:text-accent hover:bg-surface-hover rounded transition-colors"
                  >
                    <ExternalLink size={12} /> 편집
                  </a>
                {/if}
                <button
                  type="button"
                  onclick={() => downloadOriginal(doc)}
                  class="inline-flex items-center gap-1 px-2 py-1 text-[11px] text-dim hover:text-accent hover:bg-surface-hover rounded transition-colors"
                >
                  <Download size={12} /> 원본
                </button>
                {#if doc.preview_status === 'ready'}
                  <button
                    type="button"
                    onclick={() => downloadPdf(doc)}
                    class="inline-flex items-center gap-1 px-2 py-1 text-[11px] text-dim hover:text-accent hover:bg-surface-hover rounded transition-colors"
                  >
                    <FileText size={12} /> PDF
                  </button>
                {:else if doc.preview_status === 'processing'}
                  <span class="inline-flex items-center gap-1 px-2 py-1 text-[11px] text-faint" title="미리보기 생성 중">
                    <FileText size={12} /> PDF 생성 중
                  </span>
                {/if}
              </div>
            </div>
          {/each}
        </div>

        <!-- 페이지네이션 -->
        {#if totalPages > 1}
          <div class="flex items-center justify-center gap-2 mt-6">
            {#if activePage > 1}
              <Button
                variant="ghost"
                size="sm"
                onclick={() => {
                  const p = new URLSearchParams($page.url.searchParams);
                  p.set('page', String(activePage - 1));
                  goto(`/library?${p}`, { noScroll: true });
                }}
              >
                이전
              </Button>
            {/if}
            <span class="text-xs text-dim">{activePage} / {totalPages}</span>
            {#if activePage < totalPages}
              <Button
                variant="ghost"
                size="sm"
                onclick={() => {
                  const p = new URLSearchParams($page.url.searchParams);
                  p.set('page', String(activePage + 1));
                  goto(`/library?${p}`, { noScroll: true });
                }}
              >
                다음
              </Button>
            {/if}
          </div>
        {/if}
      {/if}
    </main>
  </div>
</div>

<!-- 컨텍스트 메뉴 -->
{#if showContextMenu && contextNode}
  {@const cn = contextNode}
  {@const cnDepth = cn.path.split('/').length}
  <div
    class="fixed z-50 bg-surface border border-default rounded-lg shadow-xl py-1 min-w-[160px]"
    style="left: {contextPos.x}px; top: {contextPos.y}px"
    onclick={(e) => e.stopPropagation()}
  >
    {#if cnDepth < MAX_DEPTH}
      <button
        onclick={() => openAddCategory(cn.path)}
        class="w-full text-left px-3 py-1.5 text-xs text-text hover:bg-surface-hover flex items-center gap-2"
      >
        <FolderPlus size={12} /> 하위 분류 추가
      </button>
    {/if}
    {#if cn.is_category && !cn.is_system && !cn.has_children}
      <button
        onclick={() => openRenameCategory(cn)}
        class="w-full text-left px-3 py-1.5 text-xs text-text hover:bg-surface-hover flex items-center gap-2"
      >
        <Pencil size={12} /> 이름 변경
      </button>
    {/if}
    {#if cn.is_category && !cn.is_system && !cn.has_children && cn.count === 0}
      <button
        onclick={() => openDeleteCategory(cn)}
        class="w-full text-left px-3 py-1.5 text-xs text-error hover:bg-surface-hover flex items-center gap-2"
      >
        <Trash2 size={12} /> 삭제
      </button>
    {/if}
    {#if cn.is_system || cn.has_children}
      <p class="px-3 py-1.5 text-[10px] text-faint">
        {cn.is_system ? '시스템 분류' : '하위 분류 있음'}
      </p>
    {/if}
  </div>
{/if}

<!-- 분류 추가 모달 -->
<Modal id="add-category" title={addParentPath ? `"${addParentPath}" 하위 분류 추가` : '새 분류 추가'} size="sm">
  <form onsubmit={(e) => { e.preventDefault(); createCategory(); }}>
    <label class="block text-xs text-dim mb-1">분류 이름</label>
    <input
      bind:value={newCategoryName}
      placeholder="분류 이름 입력"
      class="w-full px-3 py-2 bg-bg border border-default rounded text-sm text-text outline-none focus:border-accent"
      autofocus
    />
    {#if addParentPath}
      <p class="text-[10px] text-faint mt-1">경로: {addParentPath}/{newCategoryName || '...'}</p>
    {/if}
  </form>
  {#snippet footer()}
    <Button variant="ghost" size="sm" onclick={() => ui.closeModal('add-category')}>취소</Button>
    <Button variant="primary" size="sm" onclick={createCategory} disabled={!newCategoryName.trim() || addLoading}>
      {addLoading ? '생성 중...' : '생성'}
    </Button>
  {/snippet}
</Modal>

<!-- 이름 변경 모달 -->
<Modal id="rename-category" title="분류 이름 변경" size="sm">
  <form onsubmit={(e) => { e.preventDefault(); renameCategory(); }}>
    <label class="block text-xs text-dim mb-1">새 이름</label>
    <input
      bind:value={renameValue}
      placeholder="새 이름 입력"
      class="w-full px-3 py-2 bg-bg border border-default rounded text-sm text-text outline-none focus:border-accent"
      autofocus
    />
    {#if renameTarget}
      <p class="text-[10px] text-faint mt-1">현재: {renameTarget.name}</p>
    {/if}
  </form>
  {#snippet footer()}
    <Button variant="ghost" size="sm" onclick={() => ui.closeModal('rename-category')}>취소</Button>
    <Button variant="primary" size="sm" onclick={renameCategory} disabled={!renameValue.trim() || renameLoading}>
      {renameLoading ? '변경 중...' : '변경'}
    </Button>
  {/snippet}
</Modal>

<!-- 삭제 확인 다이얼로그 -->
<ConfirmDialog
  id="delete-category"
  title="분류 삭제"
  message={deleteTarget ? `"${deleteTarget.name}" 분류를 삭제하시겠습니까?` : ''}
  tone="danger"
  onconfirm={deleteCategory}
/>

<!-- 드래그 업로드 오버레이 -->
{#if dragging}
  <div class="fixed inset-0 z-50 bg-accent/10 border-2 border-dashed border-accent flex items-center justify-center">
    <div class="bg-surface rounded-xl px-8 py-6 shadow-xl text-center">
      <Upload size={32} class="mx-auto mb-2 text-accent" />
      <p class="text-sm font-medium text-accent">자료실에 파일 업로드</p>
      {#if activePath}
        <p class="text-xs text-dim mt-1">경로: {activePath}</p>
      {/if}
    </div>
  </div>
{/if}
