import { useState, useEffect, useRef } from 'react'
import { invoke } from '@tauri-apps/api/core'
import { listen } from '@tauri-apps/api/event'
import './App.css'
import { App as AppType, Config, Category } from './types'
import AppCard from './components/AppCard'
import GridWithFill from './components/GridWithFill'
import AddAppDialog from './components/AddAppDialog'
import CategoryManager from './components/CategoryManager'

function App() {
  const [config, setConfig] = useState<Config>({ apps: [], categories: [] })
  const configRef = useRef(config)
  // iconKey → base64 data URL (populated by batch_read_icons + ensure_icon)
  const [iconUrls, setIconUrls] = useState<Record<string, string>>({})
  const [selectedCategory, setSelectedCategory] = useState<string>('all')
  const [isAddDialogOpen, setIsAddDialogOpen] = useState(false)
  const [addDialogMode, setAddDialogMode] = useState<'folder' | 'program'>('folder')
  const [editingApp, setEditingApp] = useState<AppType | null>(null)
  const [showCategoryManager, setShowCategoryManager] = useState(false)
  const [draggedApp, setDraggedApp] = useState<AppType | null>(null)
  const [dragOverApp, setDragOverApp] = useState<string | null>(null)
  const [draggedCategory, setDraggedCategory] = useState<string | null>(null)
  const [dragOverCategory, setDragOverCategory] = useState<string | null>(null)
  const [autoLaunch, setAutoLaunch] = useState(false)
  const [showSettings, setShowSettings] = useState(false)
  const [isExternalDragOver, setIsExternalDragOver] = useState(false)
  const dragStartPos = useRef<{ x: number; y: number } | null>(null)
  const longPressTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
  const isDragging = useRef(false)
  const dragType = useRef<'app' | 'category' | null>(null)
  // Stable refs for use inside event listeners
  const selectedCategoryRef = useRef(selectedCategory)

  useEffect(() => { configRef.current = config }, [config])
  useEffect(() => { selectedCategoryRef.current = selectedCategory }, [selectedCategory])

  // 加载配置
  useEffect(() => {
    loadConfig()
    loadAutoLaunchStatus()
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  // 监听主进程发来的右键菜单动作
  useEffect(() => {
    const unlisten = listen<{ action: 'edit' | 'remove'; id: string }>(
      'app-context-action',
      ({ payload }) => {
        const current = configRef.current
        const app = current.apps.find(a => a.id === payload.id)
        if (!app) return
        if (payload.action === 'edit') {
          openEditDialog(app)
        } else if (payload.action === 'remove') {
          void removeAppById(payload.id)
        }
      }
    )
    return () => {
      unlisten.then(fn => fn())
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  // 拖放添加：监听从外部（资源管理器）拖入的文件/文件夹
  useEffect(() => {
    const unEnter = listen<{ paths: string[] }>('tauri://drag-enter', () => {
      setIsExternalDragOver(true)
    })
    const unLeave = listen('tauri://drag-leave', () => {
      setIsExternalDragOver(false)
    })
    const unDrop = listen<{ paths: string[] }>('tauri://drag-drop', async (event) => {
      setIsExternalDragOver(false)
      const paths = event.payload.paths
      if (!paths || paths.length === 0) return

      const current = configRef.current
      const targetCategory =
        selectedCategoryRef.current === 'all' ? 'default' : selectedCategoryRef.current

      const newApps: AppType[] = []
      let counter = Date.now()

      for (const filePath of paths) {
        const id = String(counter++)
        const iconKey = id
        const base = filePath.split(/[/\\]/).pop() || filePath
        const name = base.replace(/\.(exe|bat|cmd|ps1|vbs|lnk)$/i, '') || base

        let shortcutPath: string | undefined
        try {
          const res = await invoke<{ success: boolean; shortcutPath: string }>(
            'create_shortcut', { id, targetPath: filePath, name }
          )
          shortcutPath = res.shortcutPath
        } catch { shortcutPath = undefined }

        // 后台提取图标，完成后立即更新显示
        invoke<{ success: boolean; iconUrl: string | null }>(
          'ensure_icon', { iconKey, fromPath: shortcutPath || filePath }
        ).then(res => {
          if (res.success && res.iconUrl) {
            setIconUrls(prev => ({ ...prev, [iconKey]: res.iconUrl! }))
          }
        }).catch(() => {})

        newApps.push({ id, name, targetPath: filePath, shortcutPath, iconKey, category: targetCategory })
      }

      const newConfig = { ...current, apps: [...current.apps, ...newApps] }
      await invoke('save_config', { config: newConfig })
      setConfig(newConfig)
    })

    return () => {
      unEnter.then(fn => fn())
      unLeave.then(fn => fn())
      unDrop.then(fn => fn())
    }
  }, [])

  const removeAppById = async (id: string) => {
    const current = configRef.current
    const app = current.apps.find(a => a.id === id)
    if (!app) return
    try {
      await invoke('delete_app_artifacts', {
        shortcutPath: app.shortcutPath,
        iconKey: app.iconKey || app.id,
      })
    } catch { /* ignore */ }
    const newConfig = { ...current, apps: current.apps.filter(a => a.id !== id) }
    await saveConfig(newConfig)
  }

  const loadAutoLaunchStatus = async () => {
    try {
      const enabled = await invoke<boolean>('get_auto_launch')
      setAutoLaunch(enabled)
    } catch (error) {
      console.error('获取开机自启状态失败:', error)
    }
  }

  const handleToggleAutoLaunch = async () => {
    try {
      const newValue = !autoLaunch
      await invoke('set_auto_launch', { enabled: newValue })
      setAutoLaunch(newValue)
    } catch (error) {
      console.error('设置开机自启失败:', error)
    }
  }

  /** 后台逐个提取还没有缓存的图标，提取完成后立即更新 iconUrls */
  const extractMissingIcons = (apps: AppType[], existingKeys: Set<string>) => {
    for (const app of apps) {
      const key = app.iconKey || app.id
      if (existingKeys.has(key)) continue
      const fromPath = app.shortcutPath || app.targetPath
      if (!fromPath) continue
      invoke<{ success: boolean; iconUrl: string | null }>(
        'ensure_icon', { iconKey: key, fromPath }
      ).then(res => {
        if (res.success && res.iconUrl) {
          setIconUrls(prev => ({ ...prev, [key]: res.iconUrl! }))
        }
      }).catch(() => {})
    }
  }

  const handleRefreshIcons = async () => {
    if (config.apps.length === 0) { alert('当前没有应用'); return }
    if (!confirm(`确定要刷新所有应用的图标吗？这可能需要一些时间。`)) return

    try {
      let successCount = 0
      for (const app of config.apps) {
        const fromPath = app.shortcutPath || app.targetPath
        const res = await invoke<{ success: boolean; iconUrl: string | null }>(
          'ensure_icon', { iconKey: app.iconKey || app.id, fromPath }
        )
        if (res.success && res.iconUrl) {
          successCount++
          setIconUrls(prev => ({ ...prev, [app.iconKey || app.id]: res.iconUrl! }))
        }
      }
      alert(`图标刷新完成！成功: ${successCount}/${config.apps.length}`)
    } catch (error) {
      alert('刷新图标失败: ' + error)
    }
  }

  const migrateAndMaybeSaveConfig = async (rawConfig: unknown) => {
    const root = (typeof rawConfig === 'object' && rawConfig !== null)
      ? (rawConfig as Record<string, unknown>) : {}

    const appsRaw: unknown[] = Array.isArray(root.apps) ? root.apps : []
    const categoriesRaw: unknown[] = Array.isArray(root.categories) ? root.categories : []

    let changed = false
    const migratedApps: AppType[] = []

    // Parallelize shortcut creation for faster first-run startup
    const settled = await Promise.allSettled(appsRaw.map(async (a) => {
      const item = (typeof a === 'object' && a !== null) ? (a as Record<string, unknown>) : {}
      const id = String(item.id ?? Date.now())
      const name = String(item.name ?? '未命名')
      const category = String(item.category ?? 'default')
      const description = typeof item.description === 'string' ? item.description : undefined
      const targetPath = String(item.targetPath ?? item.exePath ?? '')
      const iconKey = String(item.iconKey ?? id)
      let shortcutPath: string | undefined =
        typeof item.shortcutPath === 'string' ? item.shortcutPath : undefined
      let needsSave = false

      if (item.targetPath === undefined && typeof item.exePath === 'string') needsSave = true
      if (item.iconKey === undefined) needsSave = true

      if (!shortcutPath && targetPath) {
        try {
          const res = await invoke<{ success: boolean; shortcutPath: string }>(
            'create_shortcut', { id, targetPath, name }
          )
          shortcutPath = res.shortcutPath
          needsSave = true
        } catch { shortcutPath = undefined }
      }
      return { app: { id, name, targetPath, shortcutPath, iconKey, category, description }, needsSave }
    }))

    for (const r of settled) {
      if (r.status === 'fulfilled') {
        migratedApps.push(r.value.app)
        if (r.value.needsSave) changed = true
      }
    }

    const migrated: Config = { apps: migratedApps, categories: categoriesRaw as Category[] }
    if (changed) await invoke('save_config', { config: migrated })
    return migrated
  }

  const loadConfig = async () => {
    try {
      const loadedConfig = await invoke<Config>('load_config')
      const migrated = await migrateAndMaybeSaveConfig(loadedConfig)
      setConfig(migrated)
      // Step 1: Load already-cached icons immediately (fast disk read, no PowerShell)
      const iconKeys = migrated.apps.map(a => a.iconKey || a.id)
      const existing = await invoke<Record<string, string>>(
        'batch_read_icons', { iconKeys }
      ).catch(() => ({} as Record<string, string>))
      if (Object.keys(existing).length > 0) {
        setIconUrls(prev => ({ ...prev, ...existing }))
      }
      // Step 2: Extract any missing icons in the background (may run PowerShell)
      const existingSet = new Set(Object.keys(existing))
      extractMissingIcons(migrated.apps, existingSet)
    } catch (error) {
      console.error('加载配置失败:', error)
    }
  }

  const saveConfig = async (newConfig: Config) => {
    try {
      await invoke('save_config', { config: newConfig })
      setConfig(newConfig)
    } catch (error) {
      console.error('保存配置失败:', error)
    }
  }

  const handleLaunchApp = async (app: AppType) => {
    try {
      const openPath = app.shortcutPath || app.targetPath
      await invoke('launch_app', { path: openPath })
    } catch (error) {
      console.error('启动应用失败:', error)
      alert('启动应用失败: ' + error)
    }
  }

  const handleAddApp = async (appData: Omit<AppType, 'id' | 'shortcutPath' | 'iconKey'>) => {
    const id = Date.now().toString()
    const iconKey = id

    const shortcutRes = await invoke<{ success: boolean; shortcutPath: string }>(
      'create_shortcut',
      { id, targetPath: appData.targetPath, name: appData.name }
    )
    const shortcutPath = shortcutRes.shortcutPath
    const iconRes = await invoke<{ success: boolean; iconUrl: string | null }>(
      'ensure_icon', { iconKey, fromPath: shortcutPath || appData.targetPath }
    )
    if (iconRes.success && iconRes.iconUrl) {
      setIconUrls(prev => ({ ...prev, [iconKey]: iconRes.iconUrl! }))
    }

    const newApp: AppType = { ...appData, id, shortcutPath, iconKey }
    const newConfig = { ...config, apps: [...config.apps, newApp] }
    await saveConfig(newConfig)
  }

  const handleEditApp = async (appData: Omit<AppType, 'id' | 'shortcutPath' | 'iconKey'>) => {
    if (!editingApp) return
    const id = editingApp.id
    const iconKey = editingApp.iconKey || id
    let shortcutPath = editingApp.shortcutPath
    const targetChanged = editingApp.targetPath !== appData.targetPath

    if (targetChanged) {
      try {
        const res = await invoke<{ success: boolean; shortcutPath: string }>(
          'create_shortcut', { id, targetPath: appData.targetPath, name: appData.name }
        )
        shortcutPath = res.shortcutPath
      } catch {
        shortcutPath = undefined
      }
    }

    const iconRes = await invoke<{ success: boolean; iconUrl: string | null }>(
      'ensure_icon', { iconKey, fromPath: shortcutPath || appData.targetPath }
    )
    if (iconRes.success && iconRes.iconUrl) {
      setIconUrls(prev => ({ ...prev, [iconKey]: iconRes.iconUrl! }))
    }

    const updatedApps = config.apps.map(app =>
      app.id === id ? { ...appData, id, shortcutPath, iconKey } : app
    )
    await saveConfig({ ...config, apps: updatedApps })
    setEditingApp(null)
  }

  const handleDeleteApp = async (app: AppType) => {
    try {
      await invoke('delete_app_artifacts', {
        shortcutPath: app.shortcutPath,
        iconKey: app.iconKey || app.id,
      })
    } catch { /* ignore */ }
    await saveConfig({ ...config, apps: config.apps.filter(a => a.id !== app.id) })
  }

  const handleAddCategory = async (name: string) => {
    const newCategory: Category = { id: Date.now().toString(), name }
    await saveConfig({ ...config, categories: [...config.categories, newCategory] })
  }

  const handleDeleteCategory = async (id: string) => {
    if (id === 'default') { alert('默认分类不能删除'); return }
    if (confirm('确定要删除这个分类吗？该分类下的应用将移至默认分类。')) {
      await saveConfig({
        ...config,
        categories: config.categories.filter(c => c.id !== id),
        apps: config.apps.map(app => app.category === id ? { ...app, category: 'default' } : app),
      })
    }
  }

  const handleAppContextMenu = async (app: AppType) => {
    try {
      await invoke('show_app_context_menu', {
        id: app.id,
        name: app.name,
        shortcutPath: app.shortcutPath,
        targetPath: app.targetPath,
      })
    } catch { /* ignore */ }
  }

  const handleClearAllApps = async () => {
    if (config.apps.length === 0) { alert('当前没有应用可以清空'); return }
    if (confirm(`确定要清空所有应用吗？这将删除 ${config.apps.length} 个应用，此操作不可恢复！`)) {
      try {
        for (const app of config.apps) {
          await invoke('delete_app_artifacts', {
            shortcutPath: app.shortcutPath,
            iconKey: app.iconKey || app.id,
          })
        }
      } catch { /* ignore */ }
      await saveConfig({ ...config, apps: [] })
      alert('已清空所有应用！')
    }
  }

  const handleExportConfig = async () => {
    try {
      const result = await invoke<{ success: boolean }>('export_config')
      if (result.success) alert('配置导出成功！')
    } catch (error) {
      console.error('导出配置失败:', error)
      alert('导出配置失败: ' + error)
    }
  }

  const handleImportConfig = async () => {
    try {
      const result = await invoke<{ success: boolean; config?: Config }>('import_config')
      if (result.success && result.config) {
        await loadConfig()
        alert('配置导入成功！')
      }
    } catch (error) {
      console.error('导入配置失败:', error)
      alert('导入配置失败: ' + error)
    }
  }

  const openEditDialog = (app: AppType) => {
    setEditingApp(app)
    setIsAddDialogOpen(true)
  }

  const closeDialog = () => {
    setIsAddDialogOpen(false)
    setEditingApp(null)
  }

  // 拖拽开始
  const handleDragStart = (app: AppType, e: React.MouseEvent | React.TouchEvent) => {
    e.preventDefault()
    const pos = 'touches' in e
      ? { x: e.touches[0].clientX, y: e.touches[0].clientY }
      : { x: e.clientX, y: e.clientY }
    dragStartPos.current = pos
    longPressTimer.current = setTimeout(() => {
      isDragging.current = true
      dragType.current = 'app'
      setDraggedApp(app)
    }, 300)
  }

  const handleCategoryDragStart = (categoryId: string, e: React.MouseEvent | React.TouchEvent) => {
    if (categoryId === 'all') return
    e.preventDefault()
    const pos = 'touches' in e
      ? { x: e.touches[0].clientX, y: e.touches[0].clientY }
      : { x: e.clientX, y: e.clientY }
    dragStartPos.current = pos
    longPressTimer.current = setTimeout(() => {
      isDragging.current = true
      dragType.current = 'category'
      setDraggedCategory(categoryId)
    }, 300)
  }

  const handleDragMove = (e: React.MouseEvent | React.TouchEvent) => {
    if (!isDragging.current) return
    const pos = 'touches' in e
      ? { x: e.touches[0].clientX, y: e.touches[0].clientY }
      : { x: e.clientX, y: e.clientY }
    if (dragType.current === 'app' && draggedApp) {
      const element = document.elementFromPoint(pos.x, pos.y)
      const appCard = element?.closest('.app-card') as HTMLElement
      if (appCard) {
        const appId = appCard.dataset.appId
        if (appId && appId !== draggedApp.id) setDragOverApp(appId)
      } else {
        setDragOverApp(null)
      }
    }
  }

  const handleCategoryDragMove = (e: React.MouseEvent | React.TouchEvent) => {
    if (!isDragging.current || dragType.current !== 'category' || !draggedCategory) return
    const pos = 'touches' in e
      ? { x: e.touches[0].clientX, y: e.touches[0].clientY }
      : { x: e.clientX, y: e.clientY }
    const element = document.elementFromPoint(pos.x, pos.y)
    const tab = element?.closest('.category-tab') as HTMLElement
    if (tab) {
      const catId = tab.dataset.categoryId
      if (catId && catId !== draggedCategory && catId !== 'all') setDragOverCategory(catId)
    } else {
      setDragOverCategory(null)
    }
  }

  const handleDragEnd = async () => {
    if (longPressTimer.current) { clearTimeout(longPressTimer.current); longPressTimer.current = null }
    if (isDragging.current && dragType.current === 'app' && draggedApp && dragOverApp) {
      const apps = [...config.apps]
      const di = apps.findIndex(a => a.id === draggedApp.id)
      const ti = apps.findIndex(a => a.id === dragOverApp)
      if (di !== -1 && ti !== -1) {
        const temp = apps[di]; apps[di] = apps[ti]; apps[ti] = temp
        await saveConfig({ ...config, apps })
      }
    }
    isDragging.current = false
    dragType.current = null
    setDraggedApp(null)
    setDragOverApp(null)
    dragStartPos.current = null
  }

  const handleCategoryDragEnd = async () => {
    if (longPressTimer.current) { clearTimeout(longPressTimer.current); longPressTimer.current = null }
    if (isDragging.current && dragType.current === 'category' && draggedCategory && dragOverCategory) {
      const categories = [...config.categories]
      const di = categories.findIndex(c => c.id === draggedCategory)
      const ti = categories.findIndex(c => c.id === dragOverCategory)
      if (di !== -1 && ti !== -1) {
        const temp = categories[di]; categories[di] = categories[ti]; categories[ti] = temp
        await saveConfig({ ...config, categories })
      }
    }
    isDragging.current = false
    dragType.current = null
    setDraggedCategory(null)
    setDragOverCategory(null)
    dragStartPos.current = null
  }

  const handleDragCancel = () => {
    if (longPressTimer.current) { clearTimeout(longPressTimer.current); longPressTimer.current = null }
    isDragging.current = false
    dragType.current = null
    setDraggedApp(null)
    setDragOverApp(null)
    setDraggedCategory(null)
    setDragOverCategory(null)
  }

  const filteredApps = selectedCategory === 'all'
    ? config.apps
    : config.apps.filter(app => app.category === selectedCategory)

  return (
    <div className="app-container" onDragOver={(e) => e.preventDefault()} onDrop={(e) => e.preventDefault()}>
      {/* 自定义标题栏 */}
      <div className="titlebar" data-tauri-drag-region>
        <div className="titlebar-left">
          <span className="titlebar-title">SproutLauncher</span>
        </div>
        <div className="titlebar-center">
          <div className="toolbar-group">
            <button className="toolbar-btn" onClick={() => setShowCategoryManager(!showCategoryManager)} title="管理分类">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <path d="M4 6h16M4 12h16M4 18h16" />
              </svg>
            </button>
            <button
              className="toolbar-btn"
              onClick={() => { setEditingApp(null); setAddDialogMode('folder'); setIsAddDialogOpen(true) }}
              title="添加文件夹"
            >
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <path d="M12 5v14M5 12h14" />
              </svg>
            </button>
            <button
              className="toolbar-btn"
              onClick={() => { setEditingApp(null); setAddDialogMode('program'); setIsAddDialogOpen(true) }}
              title="添加程序/脚本"
            >
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
                <polyline points="14 2 14 8 20 8" />
                <line x1="12" y1="18" x2="12" y2="12" />
                <line x1="9" y1="15" x2="15" y2="15" />
              </svg>
            </button>
          </div>
          <div className="toolbar-divider"></div>
          <div className="toolbar-group">
            <button className="toolbar-btn" onClick={handleExportConfig} title="导出配置">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" />
              </svg>
            </button>
            <button className="toolbar-btn" onClick={handleImportConfig} title="导入配置">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12" />
              </svg>
            </button>
          </div>
          <div className="toolbar-divider"></div>
          <div className="toolbar-group">
            <button className="toolbar-btn" onClick={handleRefreshIcons} title="刷新图标">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0118.8-4.3M22 12.5a10 10 0 01-18.8 4.2" />
              </svg>
            </button>
            <button className="toolbar-btn danger" onClick={handleClearAllApps} title="清空所有">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6h14z" />
              </svg>
            </button>
          </div>
          <div className="toolbar-divider"></div>
          <div className="toolbar-group">
            <button className="toolbar-btn" onClick={() => setShowSettings(!showSettings)} title="设置">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <circle cx="12" cy="12" r="3" />
                <path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z" />
              </svg>
            </button>
          </div>
        </div>
        <div className="titlebar-controls">
          <button className="control-btn minimize" onClick={() => invoke('minimize_window')} title="最小化">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
              <path d="M5 12h14" />
            </svg>
          </button>
          <button className="control-btn maximize" onClick={() => invoke('maximize_window')} title="最大化">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
              <rect x="4" y="4" width="16" height="16" rx="1" />
            </svg>
          </button>
          <button className="control-btn close" onClick={() => invoke('close_window')} title="关闭">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
              <path d="M6 6l12 12M6 18L18 6" />
            </svg>
          </button>
        </div>
      </div>

      {/* 主内容区 */}
      <div className="main-content">
        {/* 侧边栏 */}
        <div
          className="sidebar"
          onMouseMove={handleCategoryDragMove}
          onMouseUp={handleCategoryDragEnd}
          onMouseLeave={handleDragCancel}
          onTouchMove={handleCategoryDragMove}
          onTouchEnd={handleCategoryDragEnd}
        >
          <div className="sidebar-section">
            <div
              className={`category-tab ${selectedCategory === 'all' ? 'active' : ''}`}
              data-category-id="all"
              onClick={() => setSelectedCategory('all')}
            >
              全部应用 ({config.apps.length})
            </div>
            {config.categories.map(cat => (
              <div
                key={cat.id}
                data-category-id={cat.id}
                className={`category-tab ${selectedCategory === cat.id ? 'active' : ''} ${draggedCategory === cat.id ? 'dragging' : ''} ${dragOverCategory === cat.id ? 'drag-over' : ''}`}
                onClick={() => !isDragging.current && setSelectedCategory(cat.id)}
                onMouseDown={(e) => handleCategoryDragStart(cat.id, e)}
                onTouchStart={(e) => handleCategoryDragStart(cat.id, e)}
              >
                {cat.name} ({config.apps.filter(app => app.category === cat.id).length})
              </div>
            ))}
          </div>
        </div>

        {/* 应用网格 */}
        <div
          className="content-area"
          onMouseMove={handleDragMove}
          onMouseUp={handleDragEnd}
          onMouseLeave={handleDragCancel}
          onTouchMove={handleDragMove}
          onTouchEnd={handleDragEnd}
        >
          {showCategoryManager && (
            <CategoryManager
              categories={config.categories}
              onAddCategory={handleAddCategory}
              onDeleteCategory={handleDeleteCategory}
            />
          )}

          {showSettings && (
            <div className="settings-panel">
              <h3>设置</h3>
              <div className="settings-item">
                <span className="settings-label">开机自动启动</span>
                <label className="toggle-switch">
                  <input type="checkbox" checked={autoLaunch} onChange={handleToggleAutoLaunch} />
                  <span className="toggle-slider"></span>
                </label>
              </div>
              <div className="settings-hint">
                提示：点击窗口关闭按钮会最小化到系统托盘，右键托盘图标选择"退出 SproutLauncher"可退出程序。
              </div>
            </div>
          )}

          {filteredApps.length === 0 ? (
            <div className="empty-state"><p>暂无应用</p></div>
          ) : selectedCategory === 'all' ? (
            <div className="categorized-apps">
              {config.categories.map(cat => {
                const categoryApps = config.apps.filter(app => app.category === cat.id)
                if (categoryApps.length === 0) return null
                return (
                  <div key={cat.id} className="category-section">
                    <div className="category-title">{cat.name}</div>
                    <GridWithFill
                      keyPrefix={cat.id}
                      items={categoryApps}
                      renderItem={(app) => (
                        <AppCard
                          key={app.id}
                          app={app}
                          iconUrl={iconUrls[app.iconKey || app.id]}
                          onLaunch={handleLaunchApp}
                          onEdit={openEditDialog}
                          onDelete={handleDeleteApp}
                          onContextMenu={(a) => void handleAppContextMenu(a)}
                          isDragging={draggedApp?.id === app.id}
                          isDragOver={dragOverApp === app.id}
                          onDragStart={(e) => handleDragStart(app, e)}
                        />
                      )}
                    />
                  </div>
                )
              })}
            </div>
          ) : (
            <GridWithFill
              keyPrefix={`single-${selectedCategory}`}
              items={filteredApps}
              renderItem={(app) => (
                <AppCard
                  key={app.id}
                  app={app}
                  iconUrl={iconUrls[app.iconKey || app.id]}
                  onLaunch={handleLaunchApp}
                  onEdit={openEditDialog}
                  onDelete={handleDeleteApp}
                  onContextMenu={(a) => void handleAppContextMenu(a)}
                  isDragging={draggedApp?.id === app.id}
                  isDragOver={dragOverApp === app.id}
                  onDragStart={(e) => handleDragStart(app, e)}
                />
              )}
            />
          )}
        </div>
      </div>

      {/* 添加/编辑对话框 */}
      <AddAppDialog
        isOpen={isAddDialogOpen}
        onClose={closeDialog}
        onSave={editingApp ? handleEditApp : handleAddApp}
        categories={config.categories}
        editApp={editingApp}
        mode={addDialogMode}
      />

      {/* 外部文件拖入覆盖层 */}
      {isExternalDragOver && (
        <div className="external-drop-overlay">
          <div className="external-drop-box">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
              <path d="M12 16V8M8 12l4-4 4 4" />
              <path d="M20 16.5A4.5 4.5 0 0015.5 12H14a6 6 0 10-11.8 1.5" />
            </svg>
            <span>释放以添加到「{
              selectedCategory === 'all'
                ? '默认分类'
                : config.categories.find(c => c.id === selectedCategory)?.name ?? '默认分类'
            }」</span>
          </div>
        </div>
      )}
    </div>
  )
}

export default App
