<template>
  <div class="app-wrapper">
    <!-- 全局初始加载遮罩层 - 在数据完全加载前显示 -->
    <Transition name="global-overlay-fade">
      <div v-if="isInitialLoading" class="global-loading-overlay">
        <div class="global-loading-overlay__content">
          <img src="/全局加载动画.gif" alt="加载中" class="global-loading-overlay__gif" />
          <p class="global-loading-overlay__text">正在加载数据，请稍候...</p>
        </div>
      </div>
    </Transition>

    <LoginView v-if="!isAuthenticated" :notice="authNotice" @success="handleLoginSuccess" @clear-notice="clearAuthNotice" />

    <template v-else>
      <!-- 全局请求加载条（顶部） -->
      <Transition name="topbar-fade">
        <div v-if="hasPendingRequest" class="global-loading-bar">
          <div class="global-loading-bar__progress"></div>
        </div>
      </Transition>
      <Transition name="notice">
        <div v-if="sessionWarning" class="global-notice global-notice--warning" role="status">
          {{ sessionWarning }}
        </div>
      </Transition>
      <Transition name="notice">
        <div v-if="forbiddenNotice" class="global-notice global-notice--danger" role="alert">
          {{ forbiddenNotice }}
        </div>
      </Transition>
      <Transition name="notice">
        <div v-if="errorMessage" class="global-notice global-notice--error" role="alert">
          {{ errorMessage }}
        </div>
      </Transition>
      <Transition name="notice">
        <div v-if="successMessage" class="global-notice global-notice--success" role="status">
          {{ successMessage }}
        </div>
      </Transition>
      <section class="search-topbar">
        <div class="search-topbar__input">
          <input
            v-model="searchKeyword"
            type="search"
            placeholder="🔍 输入关键词搜索..."
            @focus="isSearchFocused = true"
            @blur="isSearchFocused = false"
            @keydown.enter="handleHeroSearch"
          />
        </div>
        
        <!-- 黑夜/白天模式切换按钮 -->
        <button
          type="button"
          class="theme-toggle-btn"
          :class="{ 'theme-toggle-btn--dark': isDarkMode }"
          @click="toggleDarkMode"
          :title="isDarkMode ? '切换到白天模式' : '切换到黑夜模式'"
        >
          <svg class="theme-toggle-btn__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <g v-if="!isDarkMode" class="theme-icon--sun">
              <circle cx="12" cy="12" r="5"></circle>
              <line x1="12" y1="1" x2="12" y2="3"></line>
              <line x1="12" y1="21" x2="12" y2="23"></line>
              <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
              <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
              <line x1="1" y1="12" x2="3" y2="12"></line>
              <line x1="21" y1="12" x2="23" y2="12"></line>
              <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
              <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
            </g>
            <g v-if="isDarkMode" class="theme-icon--moon">
              <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
            </g>
          </svg>
        </button>
        
        <!-- 快捷入口按钮和悬浮面板 -->
        <div class="quick-access-wrapper" ref="quickAccessWrapperRef" @mouseenter="handleQuickAccessEnter" @mouseleave="handleQuickAccessWrapperLeave">
          <button
            type="button"
            class="quick-access-btn"
            :class="{ active: showQuickAccessPanel }"
            @click.stop="toggleQuickAccessPanel"
            title="快捷入口"
          >
            <svg class="quick-access-btn__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <rect x="3" y="3" width="7" height="7" rx="1"/>
              <rect x="14" y="3" width="7" height="7" rx="1"/>
              <rect x="3" y="14" width="7" height="7" rx="1"/>
              <rect x="14" y="14" width="7" height="7" rx="1"/>
            </svg>
          </button>
          
          <!-- 快捷入口悬浮面板 -->
          <Transition name="quick-access-fade">
            <div v-if="showQuickAccessPanel" class="quick-access-panel" @mouseenter="handleQuickAccessPanelEnter" @mouseleave="handleQuickAccessPanelLeave" @click.stop>
              <div class="quick-access-panel__header">
                <h3>快捷入口</h3>
                <div class="quick-icons__actions">
                  <!-- 内外网切换 -->
                  <div class="network-toggle" title="切换内外网链接">
                    <button
                      type="button"
                      :class="['network-toggle__btn', { 'network-toggle__btn--active': networkMode === 'external' }]"
                      @click="networkMode = 'external'"
                    >外网</button>
                    <button
                      type="button"
                      :class="['network-toggle__btn', { 'network-toggle__btn--active': networkMode === 'internal' }]"
                      @click="networkMode = 'internal'"
                    >内网</button>
                  </div>
                  <button v-if="!hasSearched" type="button" class="quick-icons__manage" @click="showIconManageDialog = true" title="管理图标">⚙️</button>
                </div>
              </div>
              <div v-if="iconList.length" class="quick-icons__grid">
                <a
                  v-for="icon in iconList"
                  :key="icon.id"
                  :href="getIconLink(icon)"
                  target="_blank"
                  rel="noopener noreferrer"
                  class="quick-icons__item"
                  :title="icon.description || icon.name"
                >
                  <div class="quick-icons__thumb">
                    <img
                      :src="icon.logoUrl || getIconFavicon(icon.url)"
                      :alt="icon.name"
                      @error="(e) => { e.target.style.display = 'none'; e.target.nextElementSibling.style.display = 'flex' }"
                    />
                    <span class="quick-icons__fallback" style="display:none">{{ (icon.name || '?')[0] }}</span>
                  </div>
                  <span class="quick-icons__label">{{ icon.name }}</span>
                </a>
              </div>
              <div v-else class="quick-icons__empty">
                <span>暂无快捷图标，点击 ⚙️ 添加</span>
              </div>
            </div>
          </Transition>
        </div>
        
        <!-- 个人中心下拉菜单 -->
        <div class="user-menu" ref="userMenuRef">
          <button type="button" class="user-menu__trigger" @click="showUserMenu = !showUserMenu">
            <img 
              v-if="currentUser.avatar" 
              :src="currentUser.avatar" 
              :alt="currentUser.username"
              class="user-menu__avatar"
            />
            <div v-else class="user-menu__avatar user-menu__avatar--fallback">
              {{ currentUser.username?.charAt(0)?.toUpperCase() || 'U' }}
            </div>
            <span class="user-menu__name">{{ currentUser.username || '用户' }}</span>
            <span class="user-menu__arrow">▼</span>
          </button>
          
          <div v-if="showUserMenu" class="user-menu__dropdown">
            <div class="user-menu__header">
              <img 
                v-if="currentUser.avatar" 
                :src="currentUser.avatar" 
                :alt="currentUser.username"
                class="user-menu__avatar--lg"
              />
              <div v-else class="user-menu__avatar user-menu__avatar--fallback user-menu__avatar--lg">
                {{ currentUser.username?.charAt(0)?.toUpperCase() || 'U' }}
              </div>
              <div class="user-menu__info">
                <div class="user-menu__username">{{ currentUser.username }}</div>
              </div>
              <button type="button" class="user-menu__edit-avatar" @click="handleChangeAvatar" title="修改头像">
                📷
              </button>
            </div>
            <div class="user-menu__divider"></div>
            <button type="button" class="user-menu__item" @click="switchToPoster">
              <span class="user-menu__icon">🎬</span>
              <span>海报展示</span>
            </button>
            <button type="button" class="user-menu__item" @click="handleSwitchToManage">
              <span class="user-menu__icon">📋</span>
              <span>控制台</span>
            </button>
            <button type="button" class="user-menu__item" @click="handleTmdbConfig">
              <span class="user-menu__icon">⚙️</span>
              <span>TMDB 配置</span>
            </button>
            <button type="button" class="user-menu__item" @click="handleSettings">
              <span class="user-menu__icon">🛠️</span>
              <span>设置</span>
            </button>
            <button type="button" class="user-menu__item" @click="handleExport">
              <span class="user-menu__icon">📤</span>
              <span>导出数据</span>
            </button>
            <button type="button" class="user-menu__item" @click="handleReset">
              <span class="user-menu__icon">🔄</span>
              <span>恢复初始数据</span>
            </button>
            <div class="user-menu__divider"></div>
            <button type="button" class="user-menu__item user-menu__item--danger" @click="handleLogout">
              <span class="user-menu__icon">🚪</span>
              <span>退出登录</span>
            </button>
          </div>
        </div>
      </section>

      <header class="topbar dashboard-topbar">
        <div class="dashboard-brand">
          <strong>{{ currentView === 'manage' ? '控制台' : '海报展示' }}</strong>
          <span>{{ currentView === 'manage' ? '管理网盘资源与账号数据' : '根据网盘内容进行 TMDB 海报刮削展示' }}</span>
        </div>
      </header>

      <section v-if="currentView === 'poster' && !hasSearched && !isSearchFocused" class="hero-carousel" aria-label="每日推荐海报">
        <!-- 主海报横幅 -->
        <Transition name="hero-fade" mode="out-in">
          <article
            v-if="featuredPosters.length"
            :key="activeFeaturedPoster.id"
            class="hero-main"
            :class="{ 
              'hero-loading': !heroImageLoaded, 
              'hero-loaded': heroImageLoaded,
              'hero-wide': isWidePoster(activeFeaturedPoster.posterUrl)
            }"
            :style="buildHeroStyle(activeFeaturedPoster)"
            tabindex="0"
            @mouseenter="pauseFeaturedAutoplay"
            @mouseleave="resumeFeaturedAutoplay"
            @focusin="pauseFeaturedAutoplay"
            @focusout="resumeFeaturedAutoplay"
            @keydown.left.prevent="showPrevFeaturedPoster"
            @keydown.right.prevent="showNextFeaturedPoster"
          >
            <!-- 海报图片层 -->
            <img 
              v-if="activeFeaturedPoster.posterUrl" 
              :src="activeFeaturedPoster.posterUrl" 
              :alt="activeFeaturedPoster.title" 
              class="hero-bg-image"
              @load="heroImageLoaded = true"
            />
            
            <div class="hero-main__overlay">
              <div class="hero-main__badge">每日推荐</div>
              <div class="hero-main__info">
                <h2 class="hero-main__title">{{ activeFeaturedPoster.title }}</h2>
                <p class="hero-main__desc">{{ activeFeaturedPoster.subtitle || activeFeaturedPoster.description || '根据网盘资源内容自动匹配海报展示。' }}</p>
                <div class="hero-main__meta">
                  <span v-if="activeFeaturedPoster.accountName" class="hero-main__meta-item">📁 {{ activeFeaturedPoster.accountName }}</span>
                  <span v-if="activeFeaturedPoster.owner" class="hero-main__meta-item">👤 {{ activeFeaturedPoster.owner }}</span>
                  <span v-if="activeFeaturedPoster.status" class="hero-main__meta-item">{{ activeFeaturedPoster.status === '已删除' ? '🗑️' : '✅' }} {{ activeFeaturedPoster.status }}</span>
                </div>
              </div>
            </div>
          </article>
        </Transition>
        
        <!-- 小海报卡片 - 悬浮于大海报右下角 -->
        <div class="hero-sub" :class="{ 'hero-sub--above': featuredPosters.length }">
          <div 
            v-for="(poster, index) in featuredPosters" 
            :key="poster.id"
            class="hero-sub__card"
            :class="{ active: activeFeaturedPoster.id === poster.id }"
            @click="setFeaturedPoster(index)"
          >
            <div class="hero-sub__thumb" :style="buildSubThumbStyle(poster.posterUrl)">
              <span class="hero-sub__title">{{ poster.title }}</span>
            </div>
          </div>
        </div>
      </section>

      <section v-if="isLoading" class="loading-panel">
        <div class="loading-card">
          <div class="loading-spinner"></div>
          <span>正在加载数据…</span>
        </div>
      </section>

      <!-- 搜索聚焦时的提示 -->
      <Transition name="fade" mode="out-in">
        <section v-if="isSearchFocused && !hasSearched" class="search-focus-hint" key="search-hint">
          <div class="search-focus-content">
            <div class="search-focus-icon">✨</div>
            <h3>输入搜索关键词</h3>
            <p>搜索标题、标签、资源库或维护人</p>
          </div>
        </section>
      </Transition>


      <template v-if="!isLoading && currentView === 'manage' && !isSearchFocused">
        <!-- 快捷图标区域 -->
        <section ref="quickIconsRef" v-if="iconList.length || !hasSearched" class="quick-icons">
          <div class="quick-icons__header">
            <h3>快捷入口</h3>
            <div class="quick-icons__actions">
              <!-- 内外网切换 -->
              <div class="network-toggle" title="切换内外网链接">
                <button
                  type="button"
                  :class="['network-toggle__btn', { 'network-toggle__btn--active': networkMode === 'external' }]"
                  @click="networkMode = 'external'"
                >外网</button>
                <button
                  type="button"
                  :class="['network-toggle__btn', { 'network-toggle__btn--active': networkMode === 'internal' }]"
                  @click="networkMode = 'internal'"
                >内网</button>
              </div>
              <button v-if="!hasSearched" type="button" class="quick-icons__manage" @click="showIconManageDialog = true" title="管理图标">⚙️ 管理</button>
            </div>
          </div>
          <div v-if="iconList.length" class="quick-icons__grid">
            <a
              v-for="icon in iconList"
              :key="icon.id"
              :href="getIconLink(icon)"
              target="_blank"
              rel="noopener noreferrer"
              class="quick-icons__item"
              :title="icon.description || icon.name"
            >
              <div class="quick-icons__thumb">
                <img
                  :src="icon.logoUrl || getIconFavicon(icon.url)"
                  :alt="icon.name"
                  @error="(e) => { e.target.style.display = 'none'; e.target.nextElementSibling.style.display = 'flex' }"
                />
                <span class="quick-icons__fallback" style="display:none">{{ (icon.name || '?')[0] }}</span>
              </div>
              <span class="quick-icons__label">{{ icon.name }}</span>
            </a>
          </div>
          <div v-else-if="!hasSearched" class="quick-icons__empty">
            <span>暂无快捷图标，点击 ⚙️ 添加</span>
          </div>
        </section>

        <section class="manage-layout">
          <!-- 左侧：网盘管理 -->
          <section class="manage-main" aria-label="网盘管理内容">
            <div class="content-toolbar">
              <div>
                <h2 v-if="!hasSearched">网盘管理</h2>
                <h2 v-else>搜索结果</h2>
                <p v-if="!hasSearched">维护资源标题、资源库、海报地址和 TMDB 刮削所需内容。</p>
                <p v-else>找到 {{ filteredRows.length }} 个匹配“{{ searchKeyword }}”的结果</p>
              </div>
            <div class="top-actions content-actions">
              <button v-if="!hasSearched" type="button" class="secondary" @click="handleOpenLogDialog">登录日志</button>
              <!-- 只有管理员（userType=2）才能看到用户管理按钮 -->
              <button v-if="!hasSearched && currentUser.userType === 2" type="button" class="secondary" @click="showUserManageDialog = true">用户管理</button>
              <!-- 网盘管理按钮：统一的增删改查入口 -->
              <button v-if="!hasSearched" type="button" class="secondary" @click="showDriveManageDialog = true">网盘管理</button>
              <button v-else type="button" class="secondary" @click="hasSearched = false; searchKeyword = ''">清空搜索</button>
            </div>
            </div>
            
            <!-- 网盘标签页 -->
            <div v-if="!hasSearched" class="drive-tabs">
              <div 
                v-for="sheet in displaySheets" 
                :key="sheet.id"
                class="drive-tab"
                :class="{ active: activeSheetId === sheet.id }"
                @click="activeSheetId = sheet.id"
                @mouseenter="(e) => showTabTooltip(sheet.id, e)"
                @mouseleave="hideTabTooltip"
              >
                <!-- 网盘Logo -->
                <img
                  v-if="sheet.icon"
                  :src="sheet.icon"
                  :alt="sheet.name"
                  class="drive-tab__logo"
                />
                <div v-else class="drive-tab__logo drive-tab__logo--fallback">📁</div>
                <span class="drive-tab__name">{{ sheet.name }}</span>
              </div>
            </div>
          
          <!-- 文件夹树形展示 -->
          <div ref="folderTreeContainerRef" class="folder-tree-container">
            <!-- 创建根级文件夹按钮 -->
            <div v-if="!hasSearched" class="folder-tree-actions">
              <button type="button" class="folder-tree-actions__btn" @click="handleAddFolder(null)">
                + 创建根文件夹
              </button>
              <button 
                type="button" 
                class="folder-tree-actions__btn folder-tree-actions__btn--multi" 
                :class="{ active: multiSelectMode }"
                @click="toggleMultiSelectMode"
              >
                ☑️ 多选删除
              </button>
              <button type="button" class="folder-tree-actions__btn" @click="handleOpenScraperLogDialog">
                📋 刮削日志
              </button>
              <button 
                type="button" 
                class="folder-tree-actions__btn folder-tree-actions__btn--refresh"
                @click="handleRefreshFolders"
                title="刷新当前网盘文件夹列表"
              >
                🗂️ 刷新文件夹
              </button>
              <button 
                type="button" 
                class="folder-tree-actions__btn"
                @click="handleRescrapeAll"
                title="重新刮削所有电影文件"
              >
                🔄 重新刮削
              </button>
              <button 
                v-if="multiSelectMode"
                type="button" 
                class="folder-tree-actions__btn folder-tree-actions__btn--multi" 
                :class="{ active: multiSelectMode }"
                :disabled="selectedIds.size === 0"
                @click="handleBatchScrape"
                title="批量刮削选中的电影文件"
              >
                🎬 批量刮削
              </button>
            </div>
            
            <!-- 搜索结果为空时的提示 -->
            <div v-if="hasSearched && filteredRows.length === 0" class="empty-search-result">
              <div class="empty-search-content">
                <div class="empty-search-icon">🔍</div>
                <h3>空空如也</h3>
                <p>没有找到匹配“{{ searchKeyword }}”的内容</p>
                <button type="button" class="secondary" @click="hasSearched = false; searchKeyword = ''">
                  清空搜索
                </button>
              </div>
            </div>
            
            <FolderTree
              v-else
              :tree-data="folderTreeData"
              :expanded-folder-ids="expandedFolderIds"
              :multi-select-mode="multiSelectMode"
              :selected-ids="selectedIds"
              @add-folder="handleAddFolder"
              @add-file="handleAddFile"
              @expand="handleExpandFolder"
              @edit="openEditRecord"
              @delete="handleDeleteNode"
              @toggle-select="handleToggleSelect"
              @batch-delete="handleBatchDelete"
              @toggle-multi-select-exit="toggleMultiSelectMode"
              @scrape-status="handleScrapeStatus"
              @rescrape="handleScrapeMovie"
            />
          </div>
          </section>
          
          <!-- 右侧：重复内容 -->
          <aside class="duplicate-panel" aria-label="重复内容">
            <header>
              <h2>重复内容</h2>
              <span>{{ duplicateItems.length }}</span>
            </header>
            <div v-if="duplicateItems.length" class="duplicate-list">
              <div v-for="dup in duplicateItems" :key="dup.id" class="duplicate-item">
                <strong>{{ dup.title }}</strong>
                <ul>
                  <li v-for="loc in dup.locations" :key="loc">{{ loc }}</li>
                </ul>
              </div>
            </div>
            <div v-else class="empty-note">当前没有检测到重复标题。</div>
          </aside>
        </section>
        
      <!-- 网盘操作全局加载遮罩层 -->
      <!-- 在添加/删除/重命名网盘时显示，覆盖整个管理区域防止重复操作 -->
      <Transition name="loading-fade">
        <div v-if="driveLoading" class="drive-loading-overlay">
          <div class="drive-loading-spinner">
            <!-- CSS旋转动画加载指示器 -->
            <div class="drive-spinner"></div>
            <p class="drive-loading-text">正在处理，请稍候...</p>
          </div>
        </div>
      </Transition>

      <!-- 刮削状态对话框 -->
      <SimpleDialog :open="showScrapeStatusDialog" title="刮削状态" @close="showScrapeStatusDialog = false">
        <div v-if="scrapeStatusData" class="scrape-status-panel">
          <div class="scrape-status-header">
            <h3>{{ scrapeStatusData.title }}</h3>
            <span class="scrape-status-badge" :class="`scrape-status-badge--${scrapeStatusData.status}`">
              {{ getStatusText(scrapeStatusData.status) }}
            </span>
          </div>
          <div v-if="scrapeStatusData.message" class="scrape-status-message">
            {{ scrapeStatusData.message }}
          </div>
          <!-- 失败时显示详细错误信息 -->
          <div v-if="scrapeStatusData.status === 'failed' && scrapeStatusData.error" class="scrape-status-error">
            <strong>失败原因：</strong>{{ scrapeStatusData.error }}
          </div>
          <div v-if="scrapeStatusData.posterUrl" class="scrape-status-poster">
            <img :src="scrapeStatusData.posterUrl" alt="海报" />
          </div>
          <div v-if="scrapeStatusData.backdropUrl" class="scrape-status-backdrop">
            <img :src="scrapeStatusData.backdropUrl" alt="背景图" />
          </div>
          <div v-if="scrapeStatusData.rating" class="scrape-status-meta">
            <span>评分: {{ scrapeStatusData.rating }}</span>
          </div>
          <div v-if="scrapeStatusData.category" class="scrape-status-meta">
            <span>分类: {{ scrapeStatusData.category }}</span>
          </div>
          <div v-if="scrapeStatusData.tags" class="scrape-status-tags">
            <span v-for="tag in (typeof scrapeStatusData.tags === 'string' ? scrapeStatusData.tags.split(',') : scrapeStatusData.tags)" :key="tag">{{ tag }}</span>
          </div>
          <!-- 电影简介 -->
          <div v-if="scrapeStatusData.overview" class="scrape-status-overview">
            <span class="scrape-status-label">简介：</span>
            <span class="scrape-status-value">{{ scrapeStatusData.overview }}</span>
          </div>
          <!-- 导演 -->
          <div v-if="scrapeStatusData.directors" class="scrape-status-meta">
            <span>导演：{{ scrapeStatusData.directors }}</span>
          </div>
          <!-- 主演 -->
          <div v-if="scrapeStatusData.casts" class="scrape-status-meta">
            <span>主演：{{ scrapeStatusData.casts }}</span>
          </div>
        </div>
        <div v-else class="scrape-status-loading">
          正在查询刮削状态...
        </div>
      </SimpleDialog>
      </template>

      <template v-else-if="!isLoading && currentView === 'poster' && !hasSearched && !isSearchFocused">
        <section class="poster-showcase">
          <div class="content-toolbar">
            <div>
              <h2>海报展示</h2>
              <p>根据网盘管理中的标题与海报地址进行 TMDB 匹配展示，可作为刮削结果预览。</p>
            </div>
          </div>
          <div v-if="loadingPosterShowcase" class="poster-list-loading">
            正在加载海报数据...
          </div>
          <div v-else class="poster-list poster-list--showcase">
            <div v-for="item in posterShowcaseData" :key="item.id" class="poster-card-wrap">
              <PosterCard :item="item" :readonly="true" />
            </div>
          </div>
        </section>
      </template>
      
      <!-- 海报展示的搜索结果 -->
      <Transition name="fade" mode="out-in">
        <template v-if="hasSearched && currentView === 'poster'">
          <section class="poster-showcase" key="search-result">
            <div class="content-toolbar">
              <div>
                <h2>搜索结果</h2>
                <p>找到 {{ filteredPosterShowcaseData.length }} 个匹配“{{ searchKeyword }}”的结果</p>
              </div>
              <button type="button" class="secondary" @click="hasSearched = false; searchKeyword = ''">
                清空搜索
              </button>
            </div>
            <div v-if="filteredPosterShowcaseData.length > 0" class="poster-list poster-list--showcase">
              <div v-for="item in filteredPosterShowcaseData" :key="item.id" class="poster-card-wrap">
                <PosterCard :item="item" :readonly="true" />
              </div>
            </div>
            <div v-else class="empty-search-result">
              <div class="empty-search-content">
                <div class="empty-search-icon">🔍</div>
                <h3>空空如也</h3>
                <p>没有找到匹配“{{ searchKeyword }}”的内容</p>
                <button type="button" class="secondary" @click="hasSearched = false; searchKeyword = ''">
                  清空搜索
                </button>
              </div>
            </div>
          </section>
        </template>
      </Transition>

      <FolderDialog
        :open="showFolderDialog"
        :folder="editingFolder"
        @close="closeFolderDialog"
        @save="handleFolderDialogSave"
      />

      <RecordDialog
        :open="showRecordDialog"
        :editing-record="editingRecord"
        :sheets="displaySheets"
        :active-sheet-id="activeSheetId"
        @close="closeRecordDialog"
        @save="handleSaveRecord"
        @delete="handleDeleteRecord"
      />

      <!-- 刮削日志对话框 -->
      <SimpleDialog :open="showScraperLogDialog" title="刮削日志" @close="showScraperLogDialog = false">
        <div class="log-panel">
          <div v-if="loadingScraperLogs" class="log-loading">
            正在加载刮削日志...
          </div>
          <div v-else-if="scraperLogs.length === 0" class="log-empty">
            暂无刮削日志
          </div>
          <template v-else>
            <div class="scraper-log-list">
              <div v-for="log in scraperLogs" :key="log.id" class="log-entry log-entry--expandable">
                <!-- 日志头部（可点击展开） -->
                <div class="log-entry__header" @click="toggleLogDetail(log.id)">
                  <div class="log-entry__summary">
                    <span class="log-entry__toggle">{{ log.showDetail ? '▾' : '▸' }}</span>
                    <span class="log-entry__time">{{ log.scanTime || '-' }}</span>
                    <span class="log-entry__desc">{{ getStatusText(log.status) || '刮削任务执行记录' }}</span>
                  </div>
                  <div class="log-entry__stats">
                    <span class="stat-total">总共: {{ log.totalMovies || 0 }}</span>
                    <span class="stat-success">成功: {{ log.successCount || 0 }}</span>
                    <span class="stat-fail">失败: {{ log.failCount || 0 }}</span>
                  </div>
                </div>
                
                <!-- 详细日志（可展开） -->
                <Transition name="slide-down">
                  <div v-if="log.showDetail && log.detailList && log.detailList.length > 0" class="log-entry__detail">
                    <template v-for="(item, idx) in log.detailList" :key="idx">
                      <div class="detail-item detail-item--user">
                        <span class="detail-item__name">👤 {{ item.username || '用户 ' + (item.userId || idx) }}</span>
                        <span class="detail-item__status detail-item__status--info">
                          处理: {{ item.processed || 0 }} | 成功: {{ item.success || 0 }} | 失败: {{ item.failed || 0 }} | 跳过: {{ item.skipped || 0 }}
                        </span>
                      </div>
                      <!-- 显示具体的消息列表 -->
                      <div v-if="item.messages && item.messages.length > 0" class="detail-item__messages">
                        <div v-for="(msg, msgIdx) in item.messages" :key="msgIdx" class="message-item">
                          <span :class="['message-item__indicator', getMessageStatus(msg)]">
                            {{ getMessageStatus(msg) === 'success' ? '✓' : getMessageStatus(msg) === 'failed' ? '✗' : '⊘' }}
                          </span>
                          <span class="message-item__text">{{ msg }}</span>
                        </div>
                      </div>
                    </template>
                  </div>
                </Transition>
              </div>
            </div>
            
            <!-- 分页信息和加载更多按钮 -->
            <div v-if="scraperLogPagination.total > 0" class="log-pagination">
              <div class="pagination-info">
                已加载 {{ scraperLogs.length }} / {{ scraperLogPagination.total }} 条
              </div>
              <button 
                v-if="scraperLogPagination.hasMore" 
                type="button" 
                class="load-more-btn"
                @click="handleLoadMoreScraperLogs"
                :disabled="loadingScraperLogs"
              >
                {{ loadingScraperLogs ? '加载中...' : '加载更多' }}
              </button>
              <div v-else class="no-more">—— 已加载全部 ——</div>
            </div>
          </template>
        </div>
      </SimpleDialog>

      <SimpleDialog :open="showLogDialog" title="登录日志" @close="showLogDialog = false">
        <div class="log-panel">
          <!-- 筛选区域 -->
          <div class="log-filter">
            <div class="filter-row">
              <input
                v-model="logFilter.username"
                type="text"
                placeholder="搜索用户名"
                class="filter-input"
              />
              <select v-model="logFilter.result" class="filter-select">
                <option value="">全部结果</option>
                <option value="success">成功</option>
                <option value="fail">失败</option>
              </select>
              <button type="button" class="filter-search-btn" @click="applyLogFilter">搜索</button>
            </div>
            <div class="filter-row">
              <input
                v-model="logFilter.startDate"
                type="date"
                class="filter-input"
                placeholder="开始日期"
              />
              <input
                v-model="logFilter.endDate"
                type="date"
                class="filter-input"
                placeholder="结束日期"
              />
              <button type="button" class="filter-reset-btn" @click="resetLogFilter">重置</button>
            </div>
          </div>
          
          <div class="log-integrity" :class="{ ok: logIntegrity }">
            {{ logIntegrity ? '日志完整性校验通过' : '日志完整性校验失败' }}
          </div>
          <div v-if="loadingLogs" class="log-loading">
            正在加载日志...
          </div>
          <div v-else-if="loginLogs.length === 0" class="log-empty">
            {{ logPagination.pageNo === 1 ? '暂无登录日志' : '没有符合筛选条件的日志' }}
          </div>
          <template v-else>
            <div class="login-log-list">
              <div v-for="log in loginLogs" :key="log.id" class="log-entry">
                <div class="log-entry__header">
                  <div>
                    <div class="log-time">{{ log.time }}</div>
                    <div class="log-account">账号：{{ log.username }}</div>
                  </div>
                  <span class="log-result" :class="`log-result--${log.result}`">
                    {{ log.result === 'success' ? '成功' : '失败' }}
                  </span>
                </div>
                <div class="log-detail">IP：{{ log.ip }}</div>
                <div class="log-detail">设备：{{ log.device }}</div>
                <div v-if="log.failReason" class="log-detail log-detail--fail">
                  失败原因：{{ log.failReason }}
                </div>
                <div class="log-detail log-detail--ua">UA：{{ log.rawUserAgent }}</div>
              </div>
            </div>
            
            <!-- 分页信息和加载更多按钮 -->
            <div v-if="logPagination.total > 0" class="log-pagination">
              <div class="pagination-info">
                已加载 {{ loginLogs.length }} / {{ logPagination.total }} 条
              </div>
              <button 
                v-if="logPagination.hasMore" 
                type="button" 
                class="load-more-btn"
                @click="handleLoadMoreLogs"
                :disabled="loadingLogs"
              >
                {{ loadingLogs ? '加载中...' : '加载更多' }}
              </button>
              <div v-else class="no-more">—— 已加载全部 ——</div>
            </div>
          </template>
        </div>
      </SimpleDialog>

      <SimpleDialog :open="showTmdbDialog" title="TMDB 配置" @close="showTmdbDialog = false">
        <div class="tmdb-config">
          <label>
            <span>接口密钥</span>
            <input
              v-model="tmdbConfig.apiKey"
              type="text"
              autocomplete="off"
              placeholder="请输入 TMDB API Key"
            />
          </label>
          <p>接口密钥保存在后端，登录成功后会自动同步当前账号的 TMDB Key。</p>
          <div class="tmdb-config__status" :class="{ ready: tmdbConfig.hasKey }">
            {{ tmdbConfig.hasKey ? '当前账号已绑定 TMDB API Key' : '当前账号尚未绑定 TMDB API Key' }}
          </div>
        </div>
        <template #footer>
          <button type="button" class="secondary" @click="showTmdbDialog = false">取消</button>
          <button type="button" @click="persistTmdbConfig">保存</button>
        </template>
      </SimpleDialog>

      <!-- 用户管理对话框（仅管理员可见） -->
      <UserManageDialog
        :open="showUserManageDialog"
        @close="showUserManageDialog = false"
        @success="handleUserManageSuccess"
        @error="setErrorMessage"
      />

      <!-- 图标管理对话框 -->
      <IconManageDialog
        :open="showIconManageDialog"
        @close="showIconManageDialog = false"
        @success="setSuccessMessage"
        @error="setErrorMessage"
        @refresh="loadIconList"
      />

      <!-- 网盘管理对话框：统一的增删改查入口 -->
      <DriveManageDialog
        :open="showDriveManageDialog"
        @close="showDriveManageDialog = false"
        @success="handleDriveManageSuccess"
        @refresh="handleDriveListRefresh"
        @error="setErrorMessage"
      />

      <!-- 统一确认弹窗 -->
      <ConfirmDialog
        :open="showConfirmDialog"
        :title="confirmConfig.title"
        :message="confirmConfig.message"
        :hint="confirmConfig.hint"
        :icon="confirmConfig.icon"
        :confirm-text="confirmConfig.confirmText"
        :cancel-text="confirmConfig.cancelText"
        :danger="confirmConfig.danger"
        @confirm="handleConfirmDialogConfirm"
        @cancel="showConfirmDialog = false"
      />

      <!-- 设置页面 -->
      <Transition name="fade">
        <div v-if="showSettingsView" class="settings-overlay" @click.self="showSettingsView = false">
          <div class="settings-container">
            <button type="button" class="settings-close" @click="showSettingsView = false" title="关闭设置">✕</button>
            <SettingsView />
          </div>
        </div>
      </Transition>

      <!-- 网盘标签页悬浮描述提示框（Teleport到body，避免被父容器overflow裁剪） -->
      <Teleport to="body">
        <Transition name="tooltip-fade">
          <div v-if="activeTabTooltipId && activeTabTooltipSheet" class="app-tooltip" :style="tabTooltipStyle">
            <div class="app-tooltip__header">
              <span class="app-tooltip__icon">💾</span>
              <span class="app-tooltip__title">{{ activeTabTooltipSheet.name }}</span>
            </div>
            <div class="app-tooltip__body">
              <p v-if="activeTabTooltipSheet.description" class="app-tooltip__desc">{{ activeTabTooltipSheet.description }}</p>
              <span v-else class="app-tooltip__empty">暂无描述</span>
            </div>
            <div class="app-tooltip__arrow"></div>
          </div>
        </Transition>
      </Teleport>
    </template>
  </div>
</template>

<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch, onUnmounted } from 'vue'
import LoginView from './components/LoginView.vue'
import PosterCard from './components/PosterCard.vue'
import RecordDialog from './components/RecordDialog.vue'
import FolderDialog from './components/FolderDialog.vue'
import FolderTree from './components/FolderTree.vue'
import SimpleDialog from './components/SimpleDialog.vue'
import UserManageDialog from './components/UserManageDialog.vue'
import DriveManageDialog from './components/DriveManageDialog.vue'
import ConfirmDialog from './components/ConfirmDialog.vue'
import IconManageDialog from './components/IconManageDialog.vue'
import SettingsView from './components/SettingsView.vue'
import {
  batchScrapeAll,
  batchScrapeMovies,
  createDrive,
  createFolderInDrive,
  createMovieInDrive,
  deleteDrive,
  deleteFolderInDrive,
  deleteMovieInDrive,
  deleteRecord,
  exportData,
  fetchAllMovies,
  fetchBatchScrapeStatus,
  fetchDashboardData,
  fetchDriveList,
  fetchFolderMovies,
  fetchFolders,
  fetchIconList,
  fetchLoginLogs,
  fetchMovieDuplicates,
  fetchRecommendMovies,
  fetchScrapeStatus,
  fetchScraperLogs,
  fetchTmdbConfig,
  requestState,
  resetData,
  saveTmdbConfig as apiSaveTmdbConfig,
  scrapeMovie,
  updateDrive,
  updateFolderInDrive,
  updateMovieInDrive,
} from './api'
import { attachSheetName, buildTree, createLoginLog, createRecord, filterRows, flattenRows, mapMovieToRecord } from './utils/data'
import {
  AUTH_SESSION_STORAGE_KEY,
  AUTH_STORAGE_KEY,
  DASHBOARD_STORAGE_KEY,
  readStorage,
  removeStorage,
  writeStorage,
} from './utils/storage'

const authSession = readStorage(AUTH_SESSION_STORAGE_KEY, null)
const isAuthenticated = ref(Boolean(authSession?.token || readStorage(AUTH_STORAGE_KEY, false)))
const authNotice = ref('')
const forbiddenNotice = ref('')
const sessionWarning = ref('')
const rawData = ref(null)
const isLoading = ref(false)
const driveLoading = ref(false) // 网盘操作加载状态（添加/删除/重命名）
const isInitialLoading = ref(true) // 全局初始加载状态

/** 是否有API请求正在进行（来自全局请求计数器） */
const hasPendingRequest = computed(() => requestState.pendingCount > 0)
const searchKeyword = ref('')
const isSearchFocused = ref(false)
const hasSearched = ref(false)
const currentView = ref('poster')
const isDarkMode = ref(false) // 黑夜模式开关
const activeSheetId = ref('')
const showRecordDialog = ref(false)
const showFolderDialog = ref(false)
const editingFolder = ref(null)         // 编辑时传入的文件夹对象，null 表示新建
const folderDialogParentId = ref(0)     // 新建时的父文件夹ID
const folderTreeContainerRef = ref(null) // 文件夹树容器引用（用于保持滚动位置）
const quickIconsRef = ref(null) // 快捷入口区域引用（用于滚动定位）
const expandedFolderIds = reactive(new Set()) // 全局展开状态追踪，防止 buildTree 重建丢失展开
const multiSelectMode = ref(false) // 多选模式开关
const selectedIds = reactive(new Set()) // 多选选中的ID集合

// 确认弹窗状态
const showConfirmDialog = ref(false)
const confirmConfig = reactive({
  title: '确认操作',
  message: '',
  hint: '',
  icon: '⚠️',
  confirmText: '确认',
  cancelText: '取消',
  danger: false,
  onConfirm: () => {},
})
const showLogDialog = ref(false)
const loadingLogs = ref(false)
const logFilter = reactive({
  username: '',      // 用户名筛选
  result: '',        // 结果筛选：success/fail（留空表示全部）
  startDate: '',     // 开始日期
  endDate: '',       // 结束日期
})
// 登录日志分页参数
const logPagination = reactive({
  pageNo: 1,
  pageSize: 20,      // 默认每页20条
  total: 0,          // 总记录数
  hasMore: true,     // 是否还有更多数据
})
const showTmdbDialog = ref(false)
const showUserManageDialog = ref(false) // 用户管理对话框显示状态（仅管理员）
const showDriveManageDialog = ref(false) // 网盘管理对话框显示状态
const showIconManageDialog = ref(false)   // 图标管理对话框显示状态
const showScraperLogDialog = ref(false) // 刮削日志对话框显示状态
const loadingScraperLogs = ref(false) // 刮削日志加载状态
const scraperLogs = ref([]) // 刮削日志列表
const showScrapeStatusDialog = ref(false) // 刮削状态对话框显示状态
const scrapeStatusData = ref(null) // 刮削状态数据
const scrapeStatusLoading = ref(false) // 刮削状态加载状态
const scraperLogPagination = reactive({
  pageNo: 1,
  pageSize: 20,
  total: 0,
  hasMore: true,
}) // 刮削日志分页参数
const iconList = ref([])                  // 快捷图标列表
const networkMode = ref('external')       // 网络模式：'external' | 'internal'
const showUserMenu = ref(false)
const userMenuRef = ref(null)
const showQuickAccessPanel = ref(false)
const quickAccessWrapperRef = ref(null)
let quickAccessTimer = null // 悬浮延迟定时器（显示面板）
let quickAccessCloseTimer = null // 面板关闭延迟定时器
const editingRecord = ref(null)
const featuredPosterIndex = ref(0)

// 网盘标签页悬浮提示状态
const activeTabTooltipId = ref(null)
const tabTooltipPos = ref({ top: 0, left: 0 })
let tabTooltipTimer = null

// 当前悬浮的网盘数据（用于显示提示框内容）
const activeTabTooltipSheet = computed(() =>
  displaySheets.value.find(s => s.id === activeTabTooltipId.value) || null
)

// 提示框定位样式（position: fixed）
const tabTooltipStyle = computed(() => ({
  position: 'fixed',
  top: `${tabTooltipPos.value.top}px`,
  left: `${tabTooltipPos.value.left}px`,
  transform: 'translateX(-50%)',
  zIndex: 99999,
}))

function showTabTooltip(sheetId, event) {
  if (tabTooltipTimer) clearTimeout(tabTooltipTimer)
  // 立即获取元素位置（在 mouseenter 时 event.currentTarget 可用）
  const el = event.currentTarget
  const rect = el.getBoundingClientRect()
  tabTooltipPos.value = {
    top: rect.bottom + 10,
    left: rect.left + rect.width / 2,
  }
  tabTooltipTimer = setTimeout(() => {
    activeTabTooltipId.value = sheetId
  }, 200)
}

function hideTabTooltip() {
  if (tabTooltipTimer) {
    clearTimeout(tabTooltipTimer)
    tabTooltipTimer = null
  }
  activeTabTooltipId.value = null
}
const featuredAutoplayDelay = 5000
let featuredAutoplayTimer = 0
const successMessage = ref('')
const errorMessage = ref('')

// 每日推荐电影数据（从后端接口获取）
const recommendMovies = ref([])
const loadingRecommend = ref(false)

// 海报图片加载状态
const heroImageLoaded = ref(false)
let heroImagePreloadTimer = null

// 当前用户信息
const currentUser = reactive({
  username: authSession?.username || '',
  userId: authSession?.userId || null,
  avatar: authSession?.avatar || '',
  userType: authSession?.userType || 0, // 0: 普通用户, 1: VIP, 2: 管理员
  email: authSession?.email || '',
  phone: authSession?.phone || '',
  realName: authSession?.realName || '',
  hasTmdbKey: authSession?.hasTmdbKey || false,
})

const tmdbConfig = reactive({
  apiKey: '',
  hasKey: false,
})

const displaySheets = computed(() => attachSheetName(rawData.value?.sheets || []))
const activeSheet = computed(() => displaySheets.value.find((s) => s.id === activeSheetId.value) || displaySheets.value[0])
const allRows = computed(() => activeSheet.value?.rows || [])
const totalRows = computed(() => allRows.value.length)
const filteredRows = computed(() => filterRows(allRows.value, searchKeyword.value))
const duplicateItems = ref([])

// 文件夹树数据（将当前 sheet 的数据转换为树形结构）
const folderTreeData = computed(() => {
  // 如果已搜索，使用过滤后的数据
  const rows = hasSearched.value ? filteredRows.value : (allRows.value || [])
  return buildTree(rows)
})

// 海报展示数据（从后端获取，支持排序和分页）
const posterShowcaseData = ref([]) // 从后端获取的海报展示数据
const loadingPosterShowcase = ref(false) // 海报展示加载状态

// 过滤后的海报展示数据（用于搜索）
const filteredPosterShowcaseData = computed(() => {
  if (!searchKeyword.value.trim()) {
    return posterShowcaseData.value
  }
  const keyword = searchKeyword.value.toLowerCase()
  return posterShowcaseData.value.filter(item => {
    const title = (item.title || '').toLowerCase()
    const originalName = (item.originalName || '').toLowerCase()
    const description = (item.description || '').toLowerCase()
    return title.includes(keyword) || originalName.includes(keyword) || description.includes(keyword)
  })
})

/**
 * 加载海报展示数据（从后端获取所有电影）
 * @param {string} sortBy - 排序字段：createTime/updateTime/rating/title
 * @param {string} sortOrder - 排序方式：asc/desc
 */
async function loadPosterShowcase(sortBy = 'updateTime', sortOrder = 'desc') {
  loadingPosterShowcase.value = true
  try {
    const result = await fetchAllMovies({ pageNo: 1, pageSize: 50, sortBy, sortOrder })
    if (result?.code === 200 && Array.isArray(result?.data?.list)) {
      posterShowcaseData.value = result.data.list
      console.log(`海报展示加载成功，共 ${result.data.list.length} 条`)
    } else {
      console.warn('海报展示返回数据异常', result)
      posterShowcaseData.value = []
    }
  } catch (err) {
    console.error('加载海报展示失败', err)
    posterShowcaseData.value = []
  } finally {
    loadingPosterShowcase.value = false
  }
}

const loginLogs = computed(() => rawData.value?.loginLogs || [])

const logIntegrity = computed(() => loginLogs.value.length > 0)

// 每日推荐海报池（优先使用后端返回的推荐电影数据）
const posterPool = computed(() => {
  // 如果有后端返回的推荐电影数据，使用它
  if (recommendMovies.value && recommendMovies.value.length > 0) {
    return recommendMovies.value
  }
  // 否则使用降级默认数据（包含 posterUrl 字段以确保 buildHeroStyle 能正确判断）
  return [{
    id: 'default-poster',
    title: '每日推荐',
    subtitle: '暂无新片上映',
    description: '当前没有可推荐的海报，请稍后再试。',
    posterUrl: '', // 空字符串会触发 buildHeroStyle 显示默认海报
    status: '正常',
  }]
})

// 精选海报（从 posterPool 中取前 3 条）
const featuredPosters = computed(() => {
  const rows = posterPool.value
  // 如果只有默认数据（1条），直接返回
  if (rows.length <= 1) {
    return rows
  }
  // 从后端数据中取前 3 条
  return rows.slice(0, 3)
})
const activeFeaturedPoster = computed(() => featuredPosters.value[featuredPosterIndex.value] || featuredPosters.value[0] || createRecord())

// 判断是否有有效的海报数据
const hasValidFeaturedPosters = computed(() => {
  return featuredPosters.value.length > 0 && featuredPosters.value.some(p => p.posterUrl && p.posterUrl.trim() !== '')
})

/**
 * 判断海报是否为横向图片（基于 URL 特征或宽高比）
 * @param {string|object} poster - 海报 URL 或海报对象
 * @returns {boolean}
 */
function isWidePoster(poster) {
  // 如果传入的是对象，取 posterUrl
  const url = typeof poster === 'object' ? poster?.posterUrl : poster
  
  // 如果 URL 为空，不是横向海报
  if (!url || url.trim() === '') return false
  
  // 如果 URL 包含 backdrop 关键字，通常是横向背景图
  if (url.toLowerCase().includes('backdrop')) return true
  
  // 如果 URL 包含 wide、horizontal、landscape 等关键字
  if (url.toLowerCase().match(/wide|horizontal|landscape|banner/)) return true
  
  // 默认按竖向海报处理
  return false
}

/**
 * 构建大海报样式（不再使用 background-image，改用 img 标签）
 * @param {object} poster - 海报对象 { id, title, posterUrl, ... }
 * @returns {object} Vue 样式对象
 */
function buildHeroStyle(poster) {
  // 返回空对象，因为图片现在由 <img> 标签处理
  return {}
}

function buildSubThumbStyle(poster) {
  // 当没有有效海报数据时，使用默认海报
  if (!poster || poster.trim() === '') {
    return {
      backgroundImage: `linear-gradient(180deg, rgba(8, 15, 29, 0.3), rgba(8, 15, 29, 0.7)), url('/暂无新片上映海报生成.png')`,
      backgroundSize: 'cover',
      backgroundPosition: 'center',
    }
  }
  return {
    backgroundImage: `linear-gradient(180deg, rgba(8, 15, 29, 0.3), rgba(8, 15, 29, 0.7)), url(${poster})`,
    backgroundSize: 'cover',
    backgroundPosition: 'center',
  }
}

function showPrevFeaturedPoster() {
  if (!featuredPosters.value.length) return
  featuredPosterIndex.value = (featuredPosterIndex.value - 1 + featuredPosters.value.length) % featuredPosters.value.length
  restartFeaturedAutoplay()
}

function showNextFeaturedPoster() {
  if (!featuredPosters.value.length) return
  featuredPosterIndex.value = (featuredPosterIndex.value + 1) % featuredPosters.value.length
  restartFeaturedAutoplay()
}

function setFeaturedPoster(index) {
  featuredPosterIndex.value = index
  restartFeaturedAutoplay()
}

function pauseFeaturedAutoplay() {
  window.clearInterval(featuredAutoplayTimer)
}

function resumeFeaturedAutoplay() {
  if (featuredPosters.value.length <= 1) return
  window.clearInterval(featuredAutoplayTimer)
  featuredAutoplayTimer = window.setInterval(() => {
    featuredPosterIndex.value = (featuredPosterIndex.value + 1) % featuredPosters.value.length
  }, featuredAutoplayDelay)
}

function restartFeaturedAutoplay() {
  pauseFeaturedAutoplay()
  resumeFeaturedAutoplay()
}

function setSuccessMessage(message) {
  successMessage.value = message
  window.clearTimeout(setSuccessMessage.timer)
  setSuccessMessage.timer = window.setTimeout(() => {
    successMessage.value = ''
  }, 1500)
}
setSuccessMessage.timer = 0

function setErrorMessage(message) {
  errorMessage.value = message
  window.clearTimeout(setErrorMessage.timer)
  setErrorMessage.timer = window.setTimeout(() => {
    errorMessage.value = ''
  }, 1500)
}
setErrorMessage.timer = 0

function setForbiddenNotice(message) {
  forbiddenNotice.value = message
  window.clearTimeout(setForbiddenNotice.timer)
  setForbiddenNotice.timer = window.setTimeout(() => {
    forbiddenNotice.value = ''
  }, 3200)
}
setForbiddenNotice.timer = 0

function setSessionWarning(message) {
  sessionWarning.value = message
  window.clearTimeout(setSessionWarning.timer)
  setSessionWarning.timer = window.setTimeout(() => {
    sessionWarning.value = ''
  }, 4200)
}
setSessionWarning.timer = 0

function syncActiveSheet() {
  if (!displaySheets.value.length) {
    activeSheetId.value = ''
    return
  }
  const exists = displaySheets.value.some((sheet) => sheet.id === activeSheetId.value)
  if (!exists) {
    activeSheetId.value = displaySheets.value[0].id
  }
}

async function loadDashboard() {
  isLoading.value = true
  try {
    // 先从后端加载网盘列表（优先使用后端数据）
    await Promise.all([
      loadDriveList(),
      loadIconList(),
    ])
    
    // 加载 TMDB 配置
    syncTmdbConfigFromServer()
    syncActiveSheet()
    
    // 如果在管理视图，加载当前网盘的文件夹
    if (currentView.value === 'manage' && activeSheetId.value) {
      await loadDriveFolders(activeSheetId.value)
    }
  } catch (err) {
    console.error('加载数据失败', err)
    // 直接显示后端错误，不使用降级数据
    setErrorMessage(err?.message || '加载数据失败，请检查网络连接')
    // 初始化为空数据
    rawData.value = { sheets: [] }
    syncActiveSheet()
  } finally {
    isLoading.value = false
    // 数据加载完成后隐藏全局遮罩层
    isInitialLoading.value = false
  }
}

// 加载网盘列表
async function loadDriveList() {
  try {
    const result = await fetchDriveList({ pageNo: 1, pageSize: 100 })
    
    if (result?.code === 200 && result?.data) {
      // 后端返回的网盘列表格式可能是 { list: [...] } 或直接是数组
      const driveList = Array.isArray(result.data) ? result.data : (result.data.list || [])
      
      // 将后端返回的网盘数据转换为前端需要的格式
      // 兼容后端返回 logoData / icon / logo / logoUrl 等字段名
      const sheets = driveList.map(drive => ({
        id: drive.id,
        name: drive.name,
        description: drive.description || drive.desc || '',
        icon: drive.logoData || drive.icon || drive.logo || drive.logoUrl || '',
        rows: [], // 网盘下的文件列表，后续需要从其他接口获取
      }))
      
      // 初始化 rawData（如果还没有）
      if (!rawData.value) {
        rawData.value = { sheets: [] }
      }
      
      // 更新 sheets 数据
      rawData.value.sheets = sheets
      
      console.log(`成功加载 ${sheets.length} 个网盘`)
      
      // 同步激活的网盘 ID
      syncActiveSheet()
    } else {
      console.warn('网盘列表返回数据异常', result)
      throw new Error(result?.message || '获取网盘列表失败')
    }
  } catch (err) {
    console.error('加载网盘列表失败', err)
    throw err // 向上抛出错误，让 loadDashboard 处理降级逻辑
  }
}

/**
 * 加载当前网盘下的所有文件夹（从后端，递归加载子文件夹）
 * 加载后更新对应 sheet 的 rows，保持树形显示最新
 */
async function loadDriveFolders(driveId) {
  if (!driveId) return
  try {
    const rootItems = await fetchFolderLevelItems(driveId, 0)

    if (!rawData.value) rawData.value = { sheets: [] }
    const targetSheet = rawData.value.sheets.find(s => s.id === driveId)
    if (targetSheet) {
      targetSheet.rows = rootItems
    }

    console.log(`成功加载网盘[${driveId}]根目录，共 ${rootItems.length} 条`)
  } catch (err) {
    console.error('加载文件夹列表失败', err)
  }
}

async function loadDuplicateItems() {
  try {
    const res = await fetchMovieDuplicates()
    const groups = res?.data?.duplicateGroups || []
    duplicateItems.value = groups.map((group, index) => ({
      id: `duplicate-${group.title || index}-${index}`,
      title: group.title || '未命名电影',
      locations: Array.isArray(group.movies)
        ? group.movies
            .map((movie) => movie?.folderPath || '未提供路径')
            .filter(Boolean)
        : [],
    }))
  } catch (err) {
    console.error('加载重复内容失败', err)
    duplicateItems.value = []
  }
}

/**
 * 加载某个文件夹层级下的直接子文件夹和电影文件，并批量获取刮削状态
 */
async function fetchFolderLevelItems(driveId, folderId) {
  const targetSheet = rawData.value?.sheets?.find((sheet) => sheet.id === driveId)
  const sheetName = targetSheet?.name || activeSheet.value?.name || ''
  const [foldersRes, moviesRes] = await Promise.all([
    fetchFolders(driveId, { parentId: folderId }),
    fetchFolderMovies(driveId, folderId, { pageNo: 1, pageSize: 100 }),
  ])

  const folderList = normalizeListData(foldersRes?.data)
  const movieList = normalizeListData(moviesRes?.data)

  const folderItems = folderList.map((folder) => ({
    id: folder.id,
    title: folder.name || folder.title || '',
    parentId: folderId || null,  // 强制使用请求的 folderId 作为 parentId，确保子项正确连接到当前文件夹下
    description: folder.description || '',
    sortWeight: folder.sortWeight ?? 0,
    type: 'folder',
    createdAt: folder.createdAt || folder.createTime || '',
    sheetName,
    childrenLoaded: false,
    isLoading: false,
  }))

  // 提取电影ID列表，用于批量查询刮削状态
  const movieIds = movieList.map(movie => movie.id).filter(Boolean)
  
  const movieItems = movieList.map((movie) => mapMovieToRecord({
    ...movie,
    driveId,
    folderId,
  }, sheetName))

  // 批量获取刮削状态
  if (movieIds.length > 0) {
    try {
      const scrapeStatusRes = await fetchBatchScrapeStatus(movieIds)
      const scrapeStatusMap = new Map()
      
      if (scrapeStatusRes?.code === 200 && Array.isArray(scrapeStatusRes?.data)) {
        scrapeStatusRes.data.forEach(status => {
          scrapeStatusMap.set(status.movieId, status)
        })
      }
      
      // 将刮削状态附加到电影节点上
      movieItems.forEach(item => {
        const statusData = scrapeStatusMap.get(item.id)
        if (statusData) {
          item.scrapeStatus = statusData.status || 'unknown'
          item.scrapeMessage = statusData.message || ''
          item.scrapePosterUrl = statusData.posterUrl || ''
          item.scrapeBackdropUrl = statusData.backdropUrl || ''
          item.scrapeRating = statusData.rating
          item.scrapeCategory = statusData.category
          item.scrapeTags = statusData.tags
        }
      })
    } catch (err) {
      console.warn('批量获取刮削状态失败，但不影响文件夹加载', err)
    }
  }

  return [...folderItems, ...movieItems]
}

function normalizeListData(data) {
  if (Array.isArray(data)) return data
  return data?.list || data?.records || []
}

function collectDescendantIds(rows, parentId) {
  const descendantIds = new Set()
  const queue = [parentId]

  while (queue.length) {
    const currentParentId = queue.shift()
    const children = rows.filter((item) => item.parentId === currentParentId)
    children.forEach((child) => {
      if (!descendantIds.has(child.id)) {
        descendantIds.add(child.id)
        if (child.type === 'folder') {
          queue.push(child.id)
        }
      }
    })
  }

  return descendantIds
}

function setFolderLoadingState(driveId, folderId, isLoading) {
  const targetSheet = rawData.value?.sheets?.find((sheet) => sheet.id === driveId)
  if (!targetSheet || !Array.isArray(targetSheet.rows)) return

  targetSheet.rows = targetSheet.rows.map((item) =>
    item.id === folderId && item.type === 'folder'
      ? { ...item, isLoading }
      : item,
  )
}

async function handleExpandFolder(node) {
  if (!node || node.type !== 'folder' || node.childrenLoaded || node.isLoading) return

  const driveId = activeSheetId.value
  if (!driveId) return

  // 保存当前滚动位置
  const scrollTop = folderTreeContainerRef.value?.scrollTop || 0

  setFolderLoadingState(driveId, node.id, true)

  try {
    const childrenItems = await fetchFolderLevelItems(driveId, node.id)
    const targetSheet = rawData.value?.sheets?.find((sheet) => sheet.id === driveId)
    if (!targetSheet) return

    // 先将当前文件夹加入全局展开状态，确保buildTree重建时能保持展开
    expandedFolderIds.add(node.id)

    const rows = targetSheet.rows || []
    const descendantIds = collectDescendantIds(rows, node.id)
    const remainingRows = rows.filter((item) => !descendantIds.has(item.id))

    targetSheet.rows = [
      ...remainingRows.map((item) =>
        item.id === node.id && item.type === 'folder'
          ? { ...item, isLoading: false, childrenLoaded: true }
          : item,
      ),
      ...childrenItems,
    ]

    // 恢复滚动位置（等待 DOM 更新后）
    await nextTick()
    if (folderTreeContainerRef.value) {
      folderTreeContainerRef.value.scrollTop = scrollTop
    }
  } catch (err) {
    setFolderLoadingState(driveId, node.id, false)
    console.error(`加载文件夹[${node.title}]内容失败`, err)
    setErrorMessage(err?.payload?.message || err?.message || '加载文件夹内容失败')
  }
}

function persistDashboard(message = '已自动保存本地数据') {
  if (!rawData.value) return
  writeStorage(DASHBOARD_STORAGE_KEY, rawData.value)
  setSuccessMessage(message)
}

function clearAuthState(notice = '') {
  isAuthenticated.value = false
  authNotice.value = notice
  forbiddenNotice.value = ''
  sessionWarning.value = ''
  searchKeyword.value = ''
  writeStorage(AUTH_STORAGE_KEY, false)
  removeStorage(AUTH_SESSION_STORAGE_KEY)
}

function clearAuthNotice() {
  authNotice.value = ''
}

function getSessionExpiresAt(expireIn) {
  const seconds = Number(expireIn || 0)
  if (!seconds || Number.isNaN(seconds) || seconds <= 0) return null
  return Date.now() + seconds * 1000
}

function handleUnauthorized(event) {
  clearAuthState(event?.detail?.reason || '登录状态已失效，请重新登录')
}

function handleForbidden(event) {
  setForbiddenNotice(event?.detail?.reason || '当前账号无权执行此操作')
}

function handleSessionExpiring(event) {
  setSessionWarning(event?.detail?.reason || '登录状态将在 10 分钟内过期，请注意及时重新登录')
}

function handleLoginSuccess(payload) {
  // 适配新的后端响应结构：token等字段在 data 对象内
  const userData = payload?.data || payload
  
  const session = {
    token: userData?.token || '',
    tokenType: userData?.tokenType || 'Bearer',
    expireIn: userData?.expireIn || 0,
    expiresAt: getSessionExpiresAt(userData?.expireIn),
    username: userData?.username || '',
    userId: userData?.id || null,
    avatar: userData?.avatarUrl || '',
    userType: userData?.userType || 0, // 0: 普通用户, 1: VIP, 2: 管理员
    email: userData?.email || '',
    phone: userData?.phone || '',
    realName: userData?.realName || '',
    hasTmdbKey: userData?.hasTmdbKey || false,
  }

  authNotice.value = ''
  isAuthenticated.value = Boolean(session.token)
  writeStorage(AUTH_STORAGE_KEY, isAuthenticated.value)
  writeStorage(AUTH_SESSION_STORAGE_KEY, session)
  
  // 更新当前用户信息
  currentUser.username = session.username
  currentUser.userId = session.userId
  currentUser.avatar = session.avatar
  currentUser.userType = session.userType
  
  syncTmdbConfigFromServer()
  const currentLogs = rawData.value?.loginLogs || []
  if (rawData.value) {
    rawData.value.loginLogs = [createLoginLog(navigator.userAgent, location.hostname || '127.0.0.1'), ...currentLogs]
    persistDashboard('登录成功，已记录日志')
  }
}

function handleLogout() {
  showUserMenu.value = false
  clearAuthState('')
}

function handleHeroSearch() {
  hasSearched.value = true
  if (!searchKeyword.value.trim()) {
    setErrorMessage('请输入搜索关键词')
    hasSearched.value = false
    return
  }
  setSuccessMessage(`已搜索：${searchKeyword.value}`)
}

/**
 * 用户管理操作成功回调
 * @param {string} message - 成功消息
 */
function handleUserManageSuccess(message = '操作成功') {
  setSuccessMessage(message)
}

/**
 * 网盘管理操作成功回调
 * @param {string} message - 成功提示消息
 */
function handleDriveManageSuccess(message = '操作成功') {
  setSuccessMessage(message)
}

/**
 * 网盘列表刷新回调
 * 当在网盘管理对话框中增删改网盘后，从后端重新加载最新的网盘列表数据
 */
async function handleDriveListRefresh() {
  try {
    await loadDriveList()
  } catch (err) {
    console.error('刷新网盘列表失败', err)
    setErrorMessage('刷新网盘列表失败')
  }
}

// 打开登录日志对话框并加载最新20条
async function handleOpenLogDialog() {
  showLogDialog.value = true
  
  // 重置分页参数
  logPagination.pageNo = 1
  logPagination.pageSize = 20
  logPagination.total = 0
  logPagination.hasMore = true
  
  // 清空之前的日志
  rawData.value.loginLogs = []
  
  // 加载第一页数据
  await loadMoreLogs()
}

// 加载更多日志（分页加载）
async function loadMoreLogs() {
  if (loadingLogs.value || !logPagination.hasMore) return
  
  loadingLogs.value = true
  
  try {
    // 从后端加载日志
    const params = { 
      pageNo: logPagination.pageNo, 
      pageSize: logPagination.pageSize 
    }
    
    // 只有管理员（userType=2）才能查询所有日志
    // 普通用户只能查看自己的日志
    if (currentUser.userType !== 2) {
      params.userId = currentUser.userId
    }
    
    // 添加筛选条件
    if (logFilter.username.trim()) {
      params.username = logFilter.username.trim()
    }
    // result字段：success=成功, fail=失败, 留空=全部
    if (logFilter.result) {
      params.result = logFilter.result
    }
    if (logFilter.startDate) {
      params.startDate = logFilter.startDate
    }
    if (logFilter.endDate) {
      params.endDate = logFilter.endDate
    }
    
    const logsRes = await fetchLoginLogs(params)
    
    if (Array.isArray(logsRes?.data?.list)) {
      const newLogs = logsRes.data.list
      
      // 追加到现有日志列表
      if (rawData.value?.loginLogs) {
        rawData.value.loginLogs.push(...newLogs)
      } else {
        rawData.value.loginLogs = newLogs
      }
      
      // 更新分页信息
      logPagination.total = logsRes.data.total || 0
      logPagination.hasMore = rawData.value.loginLogs.length < logPagination.total
      
      console.log(`成功加载第 ${logPagination.pageNo} 页，共 ${newLogs.length} 条日志，总计 ${logPagination.total} 条`)
    } else {
      console.warn('登录日志返回数据异常', logsRes)
      logPagination.hasMore = false
    }
  } catch (err) {
    console.error('加载登录日志失败', err)
    setErrorMessage(err?.message || '加载登录日志失败')
    logPagination.hasMore = false
  } finally {
    loadingLogs.value = false
  }
}

// 应用筛选条件（重新从后端搜索）
async function applyLogFilter() {
  // 重置分页
  logPagination.pageNo = 1
  logPagination.hasMore = true
  
  // 清空当前日志
  rawData.value.loginLogs = []
  
  // 重新加载
  await loadMoreLogs()
}

// 处理加载更多按钮点击
function handleLoadMoreLogs() {
  if (!logPagination.hasMore || loadingLogs.value) return
  
  // 页码+1
  logPagination.pageNo++
  
  // 加载下一页
  loadMoreLogs()
}

// 重置登录日志筛选条件
function resetLogFilter() {
  logFilter.username = ''
  logFilter.result = ''
  logFilter.startDate = ''
  logFilter.endDate = ''
  
  // 重新加载数据
  applyLogFilter()
}

// 打开刮削日志对话框并加载最新20条
async function handleOpenScraperLogDialog() {
  showScraperLogDialog.value = true
  
  // 重置分页参数
  scraperLogPagination.pageNo = 1
  scraperLogPagination.pageSize = 20
  scraperLogPagination.total = 0
  scraperLogPagination.hasMore = true
  
  // 清空之前的日志
  scraperLogs.value = []
  
  // 加载第一页数据
  await loadMoreScraperLogs()
}

// 加载更多刮削日志（分页加载）
async function loadMoreScraperLogs() {
  if (loadingScraperLogs.value || !scraperLogPagination.hasMore) return
  
  loadingScraperLogs.value = true
  
  try {
    const params = { 
      pageNo: scraperLogPagination.pageNo, 
      pageSize: scraperLogPagination.pageSize 
    }
    
    const logsRes = await fetchScraperLogs(params)
    
    console.log('[刮削日志] 后端返回原始数据:', logsRes)
    
    if (logsRes?.code === 200 && Array.isArray(logsRes?.data?.list)) {
      const newLogs = logsRes.data.list
      
      console.log('[刮削日志] 日志列表数据:', newLogs)
      console.log('[刮削日志] 第一条日志的字段:', Object.keys(newLogs[0] || {}))
      if (newLogs[0]) {
        console.log('[刮削日志] 第一条日志完整数据:', JSON.stringify(newLogs[0], null, 2))
      }
      
      // 标准化日志数据字段（兼容不同的字段名）
      const normalizedLogs = newLogs.map(log => {
        // 解析 detail_json 字符串为数组
        let detailList = []
        if (log.detail_json) {
          try {
            detailList = typeof log.detail_json === 'string' 
              ? JSON.parse(log.detail_json) 
              : log.detail_json
          } catch (e) {
            console.warn('解析 detail_json 失败:', e)
          }
        }
        
        return {
          ...log,
          // 确保 showDetail 属性可设置
          showDetail: false,
          // 后端字段映射（使用实际的后端字段名）
          totalMovies: log.total_processed ?? log.totalMovies ?? log.total ?? 0,
          successCount: log.total_success ?? log.successCount ?? log.success ?? 0,
          failCount: log.total_failed ?? log.failCount ?? log.failed ?? 0,
          totalSkipped: log.total_skipped ?? 0,
          scanTime: log.start_time ?? log.create_time ?? log.createTime ?? '-',
          endTime: log.end_time ?? '',
          durationMs: log.duration_ms ?? 0,
          status: log.status ?? 'unknown',
          triggerType: log.trigger_type ?? 'manual',
          // 解析后的详细信息列表
          detailList: detailList,
        }
      })
      
      // 追加到现有日志列表
      scraperLogs.value.push(...normalizedLogs)
      
      // 更新分页信息
      scraperLogPagination.total = logsRes.data.total || 0
      scraperLogPagination.hasMore = scraperLogs.value.length < scraperLogPagination.total
      
      console.log(`成功加载第 ${scraperLogPagination.pageNo} 页，共 ${newLogs.length} 条日志，总计 ${scraperLogPagination.total} 条`)
    } else {
      console.warn('刮削日志返回数据异常', logsRes)
      scraperLogPagination.hasMore = false
    }
  } catch (err) {
    console.error('加载刮削日志失败', err)
    setErrorMessage(err?.message || '加载刮削日志失败')
    scraperLogPagination.hasMore = false
  } finally {
    loadingScraperLogs.value = false
  }
}

// 处理加载更多按钮点击
function handleLoadMoreScraperLogs() {
  if (!scraperLogPagination.hasMore || loadingScraperLogs.value) return
  
  // 页码+1
  scraperLogPagination.pageNo++
  
  // 加载下一页
  loadMoreScraperLogs()
}

// 切换日志详情展开/收起
function toggleLogDetail(logId) {
  const log = scraperLogs.value.find(l => l.id === logId)
  if (log) {
    log.showDetail = !log.showDetail
  }
}

// TMDB 配置
function handleTmdbConfig() {
  showUserMenu.value = false
  showTmdbDialog.value = true
}

// 设置页面
const showSettingsView = ref(false)

function handleSettings() {
  showUserMenu.value = false
  showSettingsView.value = true
}

// 修改头像
function handleChangeAvatar() {
  showUserMenu.value = false
  const input = document.createElement('input')
  input.type = 'file'
  input.accept = 'image/*'
  input.onchange = (e) => {
    const file = e.target.files[0]
    if (!file) return
    
    // 这里可以上传到后端，目前仅本地预览
    const reader = new FileReader()
    reader.onload = (event) => {
      currentUser.avatar = event.target.result
      // 更新 storage
      const session = readStorage(AUTH_SESSION_STORAGE_KEY, {})
      session.avatar = currentUser.avatar
      writeStorage(AUTH_SESSION_STORAGE_KEY, session)
      setSuccessMessage('头像已更新')
    }
    reader.readAsDataURL(file)
  }
  input.click()
}

// 点击外部关闭菜单
function handleClickOutside(event) {
  if (userMenuRef.value && !userMenuRef.value.contains(event.target)) {
    showUserMenu.value = false
  }
  // 点击外部关闭快捷入口面板
  if (quickAccessWrapperRef.value && !quickAccessWrapperRef.value.contains(event.target)) {
    showQuickAccessPanel.value = false
    clearQuickAccessTimer()
  }
}

/**
 * 切换快捷入口面板显示/隐藏
 */
function toggleQuickAccessPanel() {
  showQuickAccessPanel.value = !showQuickAccessPanel.value
  clearQuickAccessTimers()
}

/**
 * 鼠标悬浮进入快捷入口区域 - 1秒后显示面板
 */
function handleQuickAccessEnter() {
  clearQuickAccessTimers()
  quickAccessTimer = window.setTimeout(() => {
    showQuickAccessPanel.value = true
  }, 1000)
}

/**
 * 鼠标悬浮进入面板 - 清除关闭定时器，保持面板显示
 */
function handleQuickAccessPanelEnter() {
  clearQuickAccessCloseTimer()
}

/**
 * 鼠标悬浮离开面板 - 延迟关闭面板
 */
function handleQuickAccessPanelLeave() {
  quickAccessCloseTimer = window.setTimeout(() => {
    showQuickAccessPanel.value = false
  }, 2000) // 2秒后关闭
}

/**
 * 鼠标离开整个包装器（按钮+面板）- 延迟关闭面板
 */
function handleQuickAccessWrapperLeave() {
  // 只有当面板正在显示时才启动关闭定时器
  if (showQuickAccessPanel.value) {
    quickAccessCloseTimer = window.setTimeout(() => {
      showQuickAccessPanel.value = false
    }, 2000) // 2秒后关闭
  }
}

/**
 * 清除快捷入口定时器（显示和关闭）
 */
function clearQuickAccessTimers() {
  clearQuickAccessTimer()
  clearQuickAccessCloseTimer()
}

/**
 * 清除快捷入口关闭定时器
 */
function clearQuickAccessCloseTimer() {
  if (quickAccessCloseTimer) {
    window.clearTimeout(quickAccessCloseTimer)
    quickAccessCloseTimer = null
  }
}

function openAddRecord() {
  editingRecord.value = null
  showRecordDialog.value = true
}

function openEditRecord(item) {
  if (item.type === 'folder') {
    // 文件夹：弹出编辑对话框
    editingFolder.value = item
    showFolderDialog.value = true
    return
  }
  
  // 文件：打开编辑对话框
  editingRecord.value = {
    id: item.id,
    title: item.title || '',
    originalName: item.originalName || '',
    description: item.description || '',
    category: item.category || '',
    directors: item.directors || '',
    casts: item.casts || '',
    updatedAt: item.updatedAt || '',
    poster: item.poster || '',
    tags: [...(item.tags || [])],
    score: item.score || 0,
    sheetId: item.driveId || displaySheets.value.find((sheet) => sheet.name === item.sheetName)?.id || activeSheetId.value,
    parentId: item.parentId || null,
    type: item.type || 'file',
  }
  showRecordDialog.value = true
}

// 打开文件夹对话框（新建）
function handleAddFolder(parentId) {
  if (!displaySheets.value || displaySheets.value.length === 0) {
    setErrorMessage('请先创建网盘，再创建文件夹')
    return
  }
  if (!activeSheet.value) {
    setErrorMessage('请先选择一个网盘')
    return
  }
  editingFolder.value = null
  folderDialogParentId.value = parentId || 0
  showFolderDialog.value = true
}

function closeFolderDialog() {
  showFolderDialog.value = false
  editingFolder.value = null
}

// 文件夹对话框保存（新建 or 编辑）
async function handleFolderDialogSave(formData) {
  const isEdit = !!editingFolder.value
  driveLoading.value = true
  try {
    let result
    if (isEdit) {
      result = await updateFolderInDrive(activeSheetId.value, editingFolder.value.id, {
        name: formData.name,
        description: formData.description,
        sortWeight: formData.sortWeight,
      })
    } else {
      result = await createFolderInDrive(activeSheetId.value, {
        name: formData.name,
        parentId: folderDialogParentId.value,
        description: formData.description,
        sortWeight: formData.sortWeight,
      })
    }
    
    if (result?.code === 200) {
      setSuccessMessage(isEdit ? `文件夹已更新为“${formData.name}”` : `文件夹“${formData.name}”创建成功`)
      await loadDriveFolders(activeSheetId.value)
      await loadDuplicateItems()
    } else {
      setErrorMessage(result?.message || (isEdit ? '更新文件夹失败' : '创建文件夹失败'))
    }
  } catch (err) {
    console.error(isEdit ? '更新文件夹失败' : '创建文件夹失败', err)
    const serverMessage = err?.payload?.message || err?.message || ''
    setErrorMessage(serverMessage || (isEdit ? '更新文件夹失败' : '创建文件夹失败'))
  } finally {
    driveLoading.value = false
    closeFolderDialog()
  }
}

// 添加文件（记录）
function handleAddFile(parentId) {
  editingRecord.value = {
    id: null,
    title: '',
    tags: [],
    score: 0,
    originalName: '',
    description: '',
    category: '',
    directors: '',
    casts: '',
    sheetId: activeSheetId.value,
    parentId: parentId || null,
    type: 'file',
  }
  showRecordDialog.value = true
}

// 删除节点（文件夹调用后端 API，文件目前保持本地操作）
function handleConfirmDialogConfirm() {
  showConfirmDialog.value = false
  if (typeof confirmConfig.onConfirm === 'function') {
    confirmConfig.onConfirm()
  }
}

function handleDeleteNode(node) {
  const isFolder = node.type === 'folder'
  Object.assign(confirmConfig, {
    title: '确认删除',
    message: `确定要删除 <strong>“${node.title}”</strong> 吗？`,
    hint: isFolder ? '注意：该文件夹下的所有子文件夹和文件都将被永久删除！此操作不可恢复。' : '',
    icon: '⚠️',
    confirmText: '确认删除',
    cancelText: '取消',
    danger: true,
    onConfirm: () => doDeleteNode(node),
  })
  showConfirmDialog.value = true
}

async function doDeleteNode(node) {
  if (node.type === 'folder') {
    // 文件夹删除：调用后端 API
    driveLoading.value = true
    try {
      const result = await deleteFolderInDrive(activeSheetId.value, node.id)
      
      if (result?.code === 200) {
        setSuccessMessage(`文件夹“${node.title}”已删除`)
        await loadDriveFolders(activeSheetId.value)
        await loadDuplicateItems()
      } else {
        setErrorMessage(result?.message || '删除文件夹失败')
      }
    } catch (err) {
      console.error('删除文件夹失败', err)
      const serverMessage = err?.payload?.message || err?.message || ''
      setErrorMessage(serverMessage || '删除文件夹失败')
    } finally {
      driveLoading.value = false
    }
  } else {
    // 文件删除：调用后端 API
    driveLoading.value = true
    try {
      const result = await deleteMovieInDrive(activeSheetId.value, node.id)
      if (result?.code === 200) {
        rawData.value.sheets = rawData.value.sheets.map((sheet) => ({
          ...sheet,
          rows: (sheet.rows || []).filter((item) => item.id !== node.id),
        }))
        setSuccessMessage(result?.message || `电影“${node.title}”已删除`)
        await loadDuplicateItems()
      } else {
        setErrorMessage(result?.message || '删除电影失败')
      }
    } catch (err) {
      console.error('删除电影失败', err)
      setErrorMessage(err?.payload?.message || err?.message || '删除电影失败')
    } finally {
      driveLoading.value = false
    }
  }
}

/**
 * 切换到海报展示视图
 */
async function switchToPoster() {
  showUserMenu.value = false
  currentView.value = 'poster'
  // 从后端加载海报展示数据
  await loadPosterShowcase()
}

/**
 * 切换海报展示/控制台视图
 */
function toggleView() {
  currentView.value = currentView.value === 'poster' ? 'manage' : 'poster'
  // 如果切换到控制台，需要加载数据
  if (currentView.value === 'manage') {
    handleSwitchToManage()
  }
}

/**
 * 切换到网盘管理视图
 * 每次切换时都从后端重新加载最新的网盘列表数据
 * 避免使用缓存数据导致与真实数据不一致
 */
async function handleSwitchToManage() {
  // 切换视图
  currentView.value = 'manage'
  
  // 从后端重新加载网盘列表（确保数据最新）
  try {
    await Promise.all([
      loadDriveList(),
      loadIconList(),
    ])
    // 加载当前网盘下的文件夹
    if (activeSheetId.value) {
      await loadDriveFolders(activeSheetId.value)
    }
    await loadDuplicateItems()
  } catch (err) {
    console.error('刷新网盘列表失败', err)
    setErrorMessage('刷新网盘列表失败，请稍后重试')
  }
}

/**
 * 加载快捷图标列表
 */
async function loadIconList() {
  try {
    const res = await fetchIconList()
    if (res?.code === 200 && res?.data) {
      const list = Array.isArray(res.data) ? res.data : (res.data.list || res.data.records || [])
      iconList.value = list.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
    } else {
      iconList.value = []
    }
  } catch (err) {
    console.error('加载图标列表失败', err)
    iconList.value = []
  }
}

/**
 * 获取网站 favicon
 */
function getIconFavicon(url) {
  try {
    const u = new URL(url)
    return `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=64`
  } catch {
    return ''
  }
}

/**
 * 根据当前网络模式返回对应的链接地址
 * @param {Object} icon - 图标对象
 */
function getIconLink(icon) {
  if (networkMode.value === 'internal' && icon.internalUrl) {
    return icon.internalUrl
  }
  return icon.url
}

// 添加新网盘（带加载状态）
async function handleAddSheet() {
  const sheetName = prompt('请输入网盘名称：')
  if (!sheetName || !sheetName.trim()) return
  
  // 设置加载状态，防止重复提交并显示加载动画
  driveLoading.value = true
  
  try {
    // 调用后端 API 创建网盘
    const result = await createDrive({
      name: sheetName.trim(),
      description: '',
      logoData: '',
    })
    
    if (result?.code === 200 && result?.data) {
      // 从后端获取最新的网盘列表
      await loadDriveList()
      setSuccessMessage(`网盘“${sheetName.trim()}”创建成功`)
    } else {
      setErrorMessage(result?.message || '创建网盘失败')
    }
  } catch (err) {
    console.error('创建网盘失败', err)
    
    // 智能错误信息展示：优先使用后端返回的消息
    const serverMessage = err?.payload?.message || err?.message || ''
    let errorMsg = serverMessage || '创建网盘失败，请稍后重试'
    
    // 根据消息内容进行智能匹配（与用户管理组件保持一致）
    if (serverMessage.includes('已存在') || serverMessage.includes('已被') || serverMessage.includes('已占用')) {
      errorMsg = serverMessage
    } else if (serverMessage.includes('不能为空') || serverMessage.includes('必填')) {
      errorMsg = `参数有误：${serverMessage}`
    } else if (serverMessage.includes('权限') || serverMessage.includes('无权')) {
      errorMsg = serverMessage
    }
    
    setErrorMessage(errorMsg)
  } finally {
    // 无论成功失败，都关闭加载状态
    driveLoading.value = false
  }
}

// 删除网盘
function handleDeleteSheet(sheet) {
  Object.assign(confirmConfig, {
    title: '确认删除',
    message: `确定要删除网盘 <strong>“${sheet.name}”</strong> 吗？`,
    hint: '注意：该网盘下的所有文件和文件夹都将被永久删除！此操作不可恢复。',
    icon: '⚠️',
    confirmText: '确认删除',
    cancelText: '取消',
    danger: true,
    onConfirm: () => doDeleteSheet(sheet),
  })
  showConfirmDialog.value = true
}

async function doDeleteSheet(sheet) {
  // 设置加载状态
  driveLoading.value = true
  
  try {
    // 调用后端 API 删除网盘
    const result = await deleteDrive(sheet.id)
    
    if (result?.code === 200) {
      // 从后端获取最新的网盘列表
      await loadDriveList()
      setSuccessMessage(`网盘“${sheet.name}”已删除`)
    } else {
      setErrorMessage(result?.message || '删除网盘失败')
    }
  } catch (err) {
    console.error('删除网盘失败', err)
    
    // 智能错误信息展示
    const serverMessage = err?.payload?.message || err?.message || ''
    let errorMsg = serverMessage || '删除网盘失败，请稍后重试'
    
    if (serverMessage.includes('不存在') || serverMessage.includes('已被删除')) {
      errorMsg = serverMessage
    } else if (serverMessage.includes('权限') || serverMessage.includes('无权')) {
      errorMsg = serverMessage
    }
    
    setErrorMessage(errorMsg)
  } finally {
    driveLoading.value = false
  }
}

// 重命名网盘（带加载状态）
async function handleRenameSheet(sheet) {
  const newName = prompt('请输入新的网盘名称：', sheet.name)
  if (!newName || !newName.trim() || newName.trim() === sheet.name) return
  
  // 设置加载状态
  driveLoading.value = true
  
  try {
    // 调用后端 API 更新网盘
    const result = await updateDrive(sheet.id, {
      name: newName.trim(),
      description: sheet.description,
      logoData: sheet.icon,
    })
    
    if (result?.code === 200) {
      // 从后端获取最新的网盘列表
      await loadDriveList()
      setSuccessMessage('网盘名称已更新')
    } else {
      setErrorMessage(result?.message || '更新网盘失败')
    }
  } catch (err) {
    console.error('更新网盘失败', err)
    
    // 智能错误信息展示
    const serverMessage = err?.payload?.message || err?.message || ''
    let errorMsg = serverMessage || '更新网盘失败，请稍后重试'
    
    if (serverMessage.includes('不存在') || serverMessage.includes('已被删除')) {
      errorMsg = serverMessage
    } else if (serverMessage.includes('权限') || serverMessage.includes('无权')) {
      errorMsg = serverMessage
    }
    
    setErrorMessage(errorMsg)
  } finally {
    driveLoading.value = false
  }
}

function closeRecordDialog() {
  showRecordDialog.value = false
  editingRecord.value = null
}

function upsertRecordInSheets(record) {
  const nextRecord = createRecord(record, activeSheet.value?.name)
  const targetSheetId = record.sheetId || activeSheetId.value

  rawData.value.sheets = rawData.value.sheets.map((sheet) => {
    const rows = [...(sheet.rows || [])]
    const filtered = rows.filter((item) => item.id !== nextRecord.id)
    if (sheet.id === targetSheetId) {
      filtered.unshift({ ...nextRecord, sheetName: undefined })
    }
    return { ...sheet, rows: filtered }
  })
}

async function handleSaveRecord(record) {
  try {
    if (record.id) {
      const driveId = record.sheetId || activeSheetId.value
      const result = await updateMovieInDrive(driveId, record.id, {
        title: record.title,
        originalName: record.originalName,
        description: record.description,
        rating: record.score,
        tags: Array.isArray(record.tags) ? record.tags.join(',') : '',
        category: record.category,
        directors: record.directors,
        casts: record.casts,
      })

      const updatedData = result?.data || {}
      const nextRecord = {
        ...record,
        title: updatedData.title || record.title,
        originalName: updatedData.originalName || record.originalName,
        description: updatedData.description ?? record.description,
        category: updatedData.category ?? record.category,
        directors: updatedData.directors ?? record.directors,
        casts: updatedData.casts ?? record.casts,
        score: Number(updatedData.rating ?? record.score ?? 0),
        tags: updatedData.tags ?? record.tags ?? [],
        updatedAt: updatedData.updateTime || updatedData.createTime || record.updatedAt || new Date().toLocaleString('zh-CN', { hour12: false }),
      }
      upsertRecordInSheets(nextRecord)
      closeRecordDialog()
      persistDashboard(result?.message || '电影信息已更新')
      await loadDuplicateItems()
    } else {
      const driveId = record.sheetId || activeSheetId.value
      const result = await createMovieInDrive(driveId, {
        title: record.title,
        folderId: record.parentId ?? 0,
      })

      const createdData = result?.data || {}
      const nextRecord = {
        ...record,
        id: createdData.id || `record-${Date.now()}`,
        title: createdData.title || record.title,
        originalName: createdData.originalName || record.originalName || record.title,
        updatedAt: createdData.updateTime || createdData.createTime || new Date().toLocaleString('zh-CN', { hour12: false }),
        poster: createdData.posterUrl || record.poster || '',
        score: Number(createdData.rating ?? record.score ?? 0),
        tags: createdData.tags ?? record.tags ?? [],
        category: createdData.category || record.category || '',
        description: createdData.description || record.description || '',
        parentId: createdData.folderId ?? record.parentId ?? null,
      }
      upsertRecordInSheets(nextRecord)
      closeRecordDialog()
      persistDashboard(result?.message || '电影创建成功')
      await loadDuplicateItems()
    }
  } catch (err) {
    console.error('保存失败', err)
    setErrorMessage(err?.payload?.message || err?.message || '保存失败')
  }
}

async function handleDeleteRecord(record) {
  try {
    const driveId = record.sheetId || activeSheetId.value
    const result = await deleteMovieInDrive(driveId, record.id)
    if (result?.code !== 200) {
      setErrorMessage(result?.message || '删除电影失败')
      return
    }

    rawData.value.sheets = rawData.value.sheets.map((sheet) => ({
      ...sheet,
      rows: (sheet.rows || []).filter((item) => item.id !== record.id),
    }))
    closeRecordDialog()
    setSuccessMessage(result?.message || '电影已删除')
    await loadDuplicateItems()
  } catch (err) {
    console.error('删除电影失败', err)
    setErrorMessage(err?.payload?.message || err?.message || '删除电影失败')
  }
}

async function handleExport() {
  showUserMenu.value = false
  try {
    await exportData()
    const blob = new Blob([JSON.stringify(rawData.value, null, 2)], { type: 'application/json;charset=utf-8' })
    const url = URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = url
    link.download = `wangpan-data-${Date.now()}.json`
    link.click()
    URL.revokeObjectURL(url)
    setSuccessMessage('已导出 JSON 文件')
  } catch (err) {
    console.error('导出失败', err)
  }
}

async function handleReset() {
  showUserMenu.value = false
  try {
    const res = await resetData()
    rawData.value = res?.data || []
    removeStorage(DASHBOARD_STORAGE_KEY)
    writeStorage(DASHBOARD_STORAGE_KEY, rawData.value)
    syncActiveSheet()
    setSuccessMessage('已恢复初始数据')
  } catch (err) {
    console.error('重置失败', err)
  }
}

async function persistTmdbConfig() {
  try {
    const payload = {
      tmdbApiKey: tmdbConfig.apiKey,
    }
    const result = await apiSaveTmdbConfig(payload)
    
    // 保存成功后，重新从后端获取最新的 TMDB 配置
    await syncTmdbConfigFromServer()
    
    // 展示后端返回的结果（优先使用后端返回的消息）
    const message = result?.message || (result?.success ? 'TMDB 配置保存成功' : 'TMDB 配置保存失败')
    if (result?.success) {
      setSuccessMessage(message)
    } else {
      setErrorMessage(message)
    }
    
    // 停留在配置页面，不关闭对话框，让用户可以看到更新后的 API Key
    // showTmdbDialog.value = false  // 注释掉，保持对话框打开
  } catch (err) {
    console.error('保存 TMDB 配置失败', err)
    setErrorMessage(err?.message || '保存 TMDB 配置失败')
  }
}

async function syncTmdbConfigFromServer() {
  try {
    const result = await fetchTmdbConfig()
    tmdbConfig.apiKey = result?.data?.tmdbApiKey || ''
    tmdbConfig.hasKey = Boolean(result?.data?.hasKey ?? tmdbConfig.apiKey)
  } catch (err) {
    console.error('同步 TMDB 配置失败', err)
  }
}

/**
 * 加载每日推荐电影（从后端接口获取）
 */
async function loadRecommendMovies() {
  loadingRecommend.value = true
  try {
    const result = await fetchRecommendMovies()
    
    if (result?.code === 200 && result?.data?.list && result.data.list.length > 0) {
      // 将后端返回的数据转换为前端使用的格式
      recommendMovies.value = result.data.list.map(movie => ({
        id: movie.id,
        title: movie.title || '未知标题',
        description: movie.description || '',
        posterUrl: movie.posterUrl || '',
        rating: movie.rating || 0,
        category: movie.category || '',
        tags: typeof movie.tags === 'string' ? movie.tags.split(',') : movie.tags || [],
        createTime: movie.createTime || '',
      }))
      console.log(`成功加载 ${recommendMovies.value.length} 条每日推荐`)
    } else {
      console.warn('每日推荐返回数据异常', result)
      // 后端无数据，保持空数组，使用默认海报
      recommendMovies.value = []
    }
  } catch (err) {
    console.error('加载每日推荐失败', err)
    // 接口报错，保持空数组，使用默认海报
    recommendMovies.value = []
    setErrorMessage(err?.message || '加载每日推荐失败')
  } finally {
    loadingRecommend.value = false
  }
}

watch(
  featuredPosters,
  (value) => {
    if (!value.length) {
      featuredPosterIndex.value = 0
      pauseFeaturedAutoplay()
      return
    }
    if (featuredPosterIndex.value >= value.length) {
      featuredPosterIndex.value = 0
    }
    resumeFeaturedAutoplay()
  },
  { immediate: true },
)

// 监听当前选中的海报变化，预加载图片
watch(
  () => activeFeaturedPoster.value?.posterUrl,
  (newPosterUrl) => {
    if (heroImagePreloadTimer) clearTimeout(heroImagePreloadTimer)
    
    // 重置加载状态
    heroImageLoaded.value = false
    
    // 如果没有海报URL，立即显示默认图片
    if (!newPosterUrl || newPosterUrl.trim() === '') {
      heroImageLoaded.value = true
      return
    }
    
    // 延迟一小段时间再加载，避免频繁切换时闪烁
    heroImagePreloadTimer = setTimeout(() => {
      preloadImage(newPosterUrl)
    }, 100)
  },
  { immediate: true },
)

// 预加载图片函数
function preloadImage(url) {
  if (!url || url.trim() === '') {
    heroImageLoaded.value = true
    return
  }
  
  const img = new Image()
  img.onload = () => {
    heroImageLoaded.value = true
  }
  img.onerror = () => {
    // 图片加载失败，显示默认海报
    heroImageLoaded.value = true
  }
  img.src = url
}

/**
 * 刷新当前网盘文件夹列表
 */
async function handleRefreshFolders() {
  const driveId = activeSheetId.value
  if (!driveId) {
    setErrorMessage('请先选择一个网盘')
    return
  }
  
  driveLoading.value = true
  try {
    await loadDriveFolders(driveId)
    setSuccessMessage('文件夹列表已刷新')
  } catch (err) {
    console.error('刷新文件夹列表失败', err)
    setErrorMessage(err?.message || '刷新文件夹列表失败')
  } finally {
    driveLoading.value = false
  }
}

// 刮削状态查询
function handleScrapeStatus(node) {
  // 只有文件类型才显示刮削状态
  if (node.type !== 'file') {
    setErrorMessage('只有电影文件才能查询刮削状态')
    return
  }
  
  showScrapeStatusDialog.value = true
  scrapeStatusData.value = null
  scrapeStatusLoading.value = true
  
  // 获取 movieId（node.id 就是电影ID）
  const movieId = node.id
  
  // 调用后端接口查询/触发刮削状态
  fetchScrapeStatus(movieId).then(result => {
    scrapeStatusLoading.value = false
    if (result?.code === 200 && result?.data) {
      const data = result.data
      scrapeStatusData.value = {
        movieId: data.movieId,
        title: data.title || node.title,
        status: data.status,
        message: data.message,
        posterUrl: data.posterUrl,
        backdropUrl: data.backdropUrl,
        rating: data.rating,
        category: data.category,
        tags: data.tags,
        overview: data.overview || '',
        casts: data.casts || '',
        directors: data.directors || '',
        error: data.error || data.message,
      }
    } else {
      setErrorMessage(result?.message || '查询刮削状态失败')
      showScrapeStatusDialog.value = false
    }
  }).catch(err => {
    scrapeStatusLoading.value = false
    console.error('查询刮削状态失败', err)
    setErrorMessage(err?.message || '查询刮削状态失败')
    showScrapeStatusDialog.value = false
  })
}

function getStatusText(status) {
  const statusMap = {
    scraping: '刮削中',
    success: '刮削成功',
    failed: '刮削失败',
    partial: '部分成功',
    skipped: '已跳过',
  }
  return statusMap[status] || '未知状态'
}

/**
 * 判断刮削消息的状态
 * @param {string} msg - 消息文本
 * @returns {string} 'success' | 'failed' | 'skipped'
 */
function getMessageStatus(msg) {
  if (!msg) return 'skipped'
  
  // 成功的标志
  if (msg.includes('刮削成功') || msg.includes('成功') || msg.includes('Successfully')) {
    return 'success'
  }
  
  // 失败的标志
  if (msg.includes('刮削失败') || msg.includes('失败') || 
      msg.includes('未找到') || msg.includes('错误') || 
      msg.includes('Error') || msg.includes('Failed') ||
      msg.includes('不存在') || msg.includes('跳过')) {
    return 'failed'
  }
  
  // 默认视为跳过（如：已存在、无需处理等）
  return 'skipped'
}

// 多选模式相关函数
function toggleMultiSelectMode() {
  multiSelectMode.value = !multiSelectMode.value
  if (!multiSelectMode.value) {
    // 退出多选模式时清空选中状态
    selectedIds.clear()
  }
}

function handleToggleSelect(nodeId) {
  if (selectedIds.has(nodeId)) {
    selectedIds.delete(nodeId)
  } else {
    selectedIds.add(nodeId)
  }
}

/**
 * 重新刮削所有电影文件
 */
function handleRescrapeAll() {
  Object.assign(confirmConfig, {
    title: '确认重新刮削',
    message: '确定要重新刮削当前网盘下的所有电影文件吗？',
    hint: '注意：这将重新发起对所有电影文件的 TMDB 海报刮削请求，可能需要较长时间。',
    icon: '🔄',
    confirmText: '确认刮削',
    cancelText: '取消',
    danger: false,
    onConfirm: () => doBatchScrapeAll(),
  })
  showConfirmDialog.value = true
}

/**
 * 执行批量刮削所有电影
 */
async function doBatchScrapeAll() {
  driveLoading.value = true
  try {
    const result = await batchScrapeAll()
    
    if (result?.code === 200 && result?.data) {
      const { successCount = 0, failCount = 0, total = 0 } = result.data
      setSuccessMessage(`重新刮削完成：成功 ${successCount} 个，失败 ${failCount} 个，共 ${total} 个`)
      
      // 刮削完成后重新加载数据
      await loadDriveFolders(activeSheetId.value)
      await loadDuplicateItems()
    } else {
      setErrorMessage(result?.message || '重新刮削失败')
    }
  } catch (err) {
    console.error('重新刮削失败', err)
    setErrorMessage(err?.payload?.message || err?.message || '重新刮削失败')
  } finally {
    driveLoading.value = false
  }
}

/**
 * 重新刮削单部电影
 */
async function handleScrapeMovie(node) {
  if (!node || node.type !== 'file') {
    setErrorMessage('只有电影文件才能重新刮削')
    return
  }
  
  driveLoading.value = true
  try {
    const result = await scrapeMovie(node.id)
    
    if (result?.code === 200) {
      setSuccessMessage(`电影“${node.title}”刮削请求已提交`)
      
      // 刮削完成后重新加载数据
      await loadDriveFolders(activeSheetId.value)
      await loadDuplicateItems()
    } else {
      setErrorMessage(result?.message || '刮削失败')
    }
  } catch (err) {
    console.error('刮削失败', err)
    setErrorMessage(err?.payload?.message || err?.message || '刮削失败')
  } finally {
    driveLoading.value = false
  }
}

/**
 * 批量刮削选中的电影文件
 */
async function handleBatchScrape() {
  if (selectedIds.size === 0) {
    setErrorMessage('请先选中要刮削的电影文件')
    return
  }
  
  // 过滤出电影文件（排除文件夹）
  const movieIds = []
  const targetSheet = rawData.value?.sheets?.find(s => s.id === activeSheetId.value)
  const allItems = targetSheet?.rows || []
  
  for (const id of selectedIds) {
    const item = allItems.find(r => r.id === id)
    if (item && item.type === 'file') {
      movieIds.push(id)
    }
  }
  
  if (movieIds.length === 0) {
    setErrorMessage('选中的项目不是电影文件，无法刮削')
    return
  }
  
  driveLoading.value = true
  try {
    const result = await batchScrapeMovies(movieIds)
    
    if (result?.code === 200 && result?.data) {
      const { successCount = 0, failCount = 0, total = 0 } = result.data
      setSuccessMessage(`批量刮削完成：成功 ${successCount} 个，失败 ${failCount} 个，共 ${total} 个`)
      
      // 刮削完成后重新加载数据
      await loadDriveFolders(activeSheetId.value)
      await loadDuplicateItems()
      
      // 清空选中状态并退出多选模式
      selectedIds.clear()
      multiSelectMode.value = false
    } else {
      setErrorMessage(result?.message || '批量刮削失败')
    }
  } catch (err) {
    console.error('批量刮削失败', err)
    setErrorMessage(err?.payload?.message || err?.message || '批量刮削失败')
  } finally {
    driveLoading.value = false
  }
}

async function handleBatchDelete(selectedIdSet) {
  if (!selectedIdSet || selectedIdSet.size === 0) {
    setErrorMessage('没有选中任何项目')
    return
  }
  
  const count = selectedIdSet.size
  Object.assign(confirmConfig, {
    title: '确认批量删除',
    message: `确定要删除选中的 <strong>${count}</strong> 个项目吗？`,
    hint: '注意：选中的所有文件夹和文件都将被永久删除！此操作不可恢复。',
    icon: '⚠️',
    confirmText: '确认删除',
    cancelText: '取消',
    danger: true,
    onConfirm: () => doBatchDelete(selectedIdSet),
  })
  showConfirmDialog.value = true
}

async function doBatchDelete(selectedIdSet) {
  driveLoading.value = true
  const driveId = activeSheetId.value
  if (!driveId) {
    setErrorMessage('请先选择一个网盘')
    driveLoading.value = false
    return
  }
  
  let successCount = 0
  let failCount = 0
  
  try {
    // 逐个删除选中的项目
    for (const id of selectedIdSet) {
      // 查找该项目
      const targetSheet = rawData.value?.sheets?.find(s => s.id === driveId)
      const item = targetSheet?.rows?.find(r => r.id === id)
      
      if (!item) {
        failCount++
        continue
      }
      
      try {
        let result
        if (item.type === 'folder') {
          // 删除文件夹
          result = await deleteFolderInDrive(driveId, id)
        } else {
          // 删除文件
          result = await deleteMovieInDrive(driveId, id)
        }
        
        if (result?.code === 200) {
          successCount++
        } else {
          failCount++
        }
      } catch (err) {
        console.error(`删除项目[id=${id}]失败`, err)
        failCount++
      }
    }
    
    // 删除完成后重新加载数据
    if (successCount > 0) {
      await loadDriveFolders(driveId)
      await loadDuplicateItems()
      // 清空选中状态并退出多选模式
      selectedIds.clear()
      multiSelectMode.value = false
      setSuccessMessage(`成功删除 ${successCount} 个项目${failCount > 0 ? `，${failCount} 个失败` : ''}`)
    } else {
      setErrorMessage('删除失败，请重试')
    }
  } catch (err) {
    console.error('批量删除失败', err)
    setErrorMessage(err?.message || '批量删除失败')
  } finally {
    driveLoading.value = false
  }
}

watch(
  () => rawData.value?.sheets,
  () => syncActiveSheet(),
  { deep: true },
)

// 切换网盘时自动加载该网盘下的文件夹
watch(activeSheetId, (newId) => {
  expandedFolderIds.clear() // 切换网盘时清空展开状态
  if (newId && currentView.value === 'manage') {
    loadDriveFolders(newId)
  }
})

onMounted(() => {
  const session = readStorage(AUTH_SESSION_STORAGE_KEY, null)
  if (session?.expiresAt && Date.now() >= session.expiresAt) {
    clearAuthState('登录已过期，请重新登录')
  }
  window.addEventListener('wangpan:unauthorized', handleUnauthorized)
  window.addEventListener('wangpan:forbidden', handleForbidden)
  window.addEventListener('wangpan:session-expiring', handleSessionExpiring)
  document.addEventListener('click', handleClickOutside)
  
  // 并行加载仪表盘数据、每日推荐和海报展示数据
  Promise.all([
    loadDashboard(),
    loadRecommendMovies(),
    loadPosterShowcase(),
  ]).catch(err => {
    console.error('初始化加载失败', err)
  })
  
  resumeFeaturedAutoplay()
})

// 黑夜/白天模式切换函数
function toggleDarkMode() {
  isDarkMode.value = !isDarkMode.value
  // 更新 body 的 class
  document.body.classList.toggle('dark-mode', isDarkMode.value)
  // 保存到 localStorage
  localStorage.setItem('darkMode', isDarkMode.value ? '1' : '0')
}

// 从 localStorage 读取黑夜模式偏好
function initDarkMode() {
  const savedMode = localStorage.getItem('darkMode')
  if (savedMode === '1') {
    isDarkMode.value = true
    document.body.classList.add('dark-mode')
  }
}

// 在 onMounted 之前调用初始化
initDarkMode()

onBeforeUnmount(() => {
  window.removeEventListener('wangpan:unauthorized', handleUnauthorized)
  window.removeEventListener('wangpan:forbidden', handleForbidden)
  window.removeEventListener('wangpan:session-expiring', handleSessionExpiring)
  document.removeEventListener('click', handleClickOutside)
  pauseFeaturedAutoplay()
})
</script>
