<script lang="ts">
  import { Trash, ClockCounterClockwise } from 'phosphor-svelte'
  import { untrack } from 'svelte'
  import { platformService } from '$lib/platform-service'
  import { toast } from '$lib/state/notifications.svelte'
  import { settingsState, updateSettings } from '$lib/state/settings.svelte'
  import { exportAppData, importAppData } from '$lib/core/backup'
  import { loadBackups, saveBackups, saveSettings, saveLibrary } from '$lib/core/persistence/persist'
  import type { BackupEntry } from '$lib/core/persistence/persist'
  import { DEFAULT_SETTINGS } from '$lib/types/settings'
  import { DEFAULT_READING_STATS } from '$lib/types/history'
  import { clearBlobCache } from '$lib/core/cache/imageCache'
  import { clearPageCache } from '$lib/request-manager'
  import { cache as queryCache } from '$lib/core/cache/queryCache'
  import { getAdapter } from '$lib/request-manager'
  import { requestManager } from '$lib/request-manager'
  import type { ValidateBackupResult, RestoreStatus } from '$lib/server-adapters/types'

  const supportsFilesystem = platformService.isSupported('filesystem')

  type ResetState = 'idle' | 'busy' | 'done' | 'error'
  interface ResetItem { key: string; label: string; desc: string; state: ResetState; error: string | null; confirm: boolean }

  let resetItems = $state<ResetItem[]>([
    { key: 'all-cache',       label: 'Clear all caches',      desc: 'Flushes the image blob cache, page cache, query cache, Moku disk cache, Suwayomi disk cache, and server image/thumbnail cache in one pass.', state: 'idle', error: null, confirm: false },
    { key: 'reading-history', label: 'Clear reading history', desc: 'Erases chapter history, read log, reading stats, and daily read counts.',                                                                    state: 'idle', error: null, confirm: true  },
    { key: 'moku-settings',   label: 'Reset Moku settings',   desc: 'Restores all app settings to their defaults. Does not affect library data.',                                                                   state: 'idle', error: null, confirm: true  },
    { key: 'suwayomi-data',   label: 'Reset Suwayomi data',   desc: 'Deletes the database, extensions, settings, and logs. Downloads and backups are preserved.',                                                   state: 'idle', error: null, confirm: true  },
  ])

  let confirming = $state<string | null>(null)

  function patchReset(key: string, update: Partial<ResetItem>) {
    resetItems = resetItems.map(i => i.key === key ? { ...i, ...update } : i)
  }

  function showExitCountdown(): Promise<void> {
    return new Promise(resolve => {
      const backdrop = document.createElement('div')
      backdrop.className = 's-backdrop'
      backdrop.style.cssText = 'z-index:99999'
      const modal = document.createElement('div')
      modal.style.cssText = 'background:var(--bg-surface);border:1px solid var(--border-base);border-radius:var(--radius-2xl);box-shadow:0 0 0 1px rgba(255,255,255,0.04) inset,0 24px 80px rgba(0,0,0,0.7);width:min(400px,calc(100vw - 40px));display:flex;flex-direction:column;overflow:hidden;animation:s-scale-in 0.2s cubic-bezier(0.16,1,0.3,1) both'
      const header = document.createElement('div')
      header.style.cssText = 'padding:var(--sp-4) var(--sp-5) var(--sp-3);border-bottom:1px solid var(--border-dim)'
      const title = document.createElement('p')
      title.style.cssText = 'margin:0;font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--text-primary);letter-spacing:0.01em'
      title.textContent = 'Reset complete'
      header.appendChild(title)
      const body = document.createElement('div')
      body.style.cssText = 'padding:var(--sp-4) var(--sp-5);display:flex;flex-direction:column;gap:var(--sp-2)'
      const sub = document.createElement('p')
      sub.style.cssText = 'margin:0;font-family:var(--font-ui);font-size:var(--text-xs);color:var(--text-faint);letter-spacing:var(--tracking-wide);line-height:var(--leading-snug)'
      sub.textContent = 'Moku will close so you can relaunch with the reset applied.'
      const counter = document.createElement('p')
      counter.style.cssText = 'margin:0;font-family:var(--font-ui);font-size:var(--text-xs);color:var(--text-faint);letter-spacing:var(--tracking-wide)'
      counter.textContent = 'Closing in 3…'
      body.append(sub, counter)
      const footer = document.createElement('div')
      footer.style.cssText = 'padding:var(--sp-3) var(--sp-5);border-top:1px solid var(--border-dim);display:flex;justify-content:flex-end'
      const btn = document.createElement('button')
      btn.className = 's-btn s-btn-danger'
      btn.textContent = 'Close now'
      footer.appendChild(btn)
      modal.append(header, body, footer)
      backdrop.appendChild(modal)
      document.body.appendChild(backdrop)
      let secs = 3
      const tick = setInterval(() => {
        secs--
        counter.textContent = secs > 0 ? `Closing in ${secs}…` : 'Closing…'
        if (secs <= 0) { clearInterval(tick); backdrop.remove(); resolve() }
      }, 1000)
      btn.addEventListener('click', () => { clearInterval(tick); backdrop.remove(); resolve() })
    })
  }

  async function clearAllCaches(): Promise<void> {
    clearBlobCache()
    clearPageCache()
    queryCache.clearAll()
    await Promise.all([
      platformService.clearMokuCache(),
      platformService.clearSuwayomiCache(),
      getAdapter().clearCachedImages({ cachedPages: true, cachedThumbnails: true, downloadedThumbnails: false }),
    ])
  }

  async function runReset(key: string) {
    confirming = null
    patchReset(key, { state: 'busy', error: null })
    try {
      switch (key) {
        case 'all-cache':
          await clearAllCaches()
          break
        case 'reading-history':
          await saveLibrary({ sessions: [], bookmarks: [], markers: [], dailyReadCounts: {} })
          break
        case 'moku-settings':
          localStorage.clear()
          await saveSettings({ settings: DEFAULT_SETTINGS, storeVersion: 2 })
          patchReset(key, { state: 'done' })
          await showExitCountdown()
          platformService.exitApp()
          return
        case 'suwayomi-data':
          localStorage.clear()
          await platformService.resetSuwayomiData()
          patchReset(key, { state: 'done' })
          await showExitCountdown()
          platformService.exitApp()
          return
      }
      patchReset(key, { state: 'done' })
      setTimeout(() => patchReset(key, { state: 'idle' }), 3000)
    } catch (e: any) {
      patchReset(key, { state: 'error', error: e?.message ?? String(e) })
    }
  }

  interface StorageInfo { manga_bytes: number; total_bytes: number; free_bytes: number; path: string }

  const isExternalServer = $derived.by(() => {
    const url = (settingsState.settings.serverUrl ?? 'http://localhost:4567').toLowerCase().trim()
    try {
      const host = new URL(url).hostname
      return host !== 'localhost' && host !== '127.0.0.1' && host !== '::1'
    } catch { return false }
  })

  let storageInfo    = $state<StorageInfo | null>(null)
  let storageLoading = $state(false)
  let storageError   = $state<string | null>(null)

  let downloadsPathInput   = $state(settingsState.settings.serverDownloadsPath ?? '')
  let localSourcePathInput = $state(settingsState.settings.serverLocalSourcePath ?? '')
  let pathsSaving          = $state(false)
  let pathsError           = $state<string | null>(null)
  let pathsFieldError      = $state<{ dl?: string; loc?: string }>({})
  let pathsSaved           = $state(false)

  let defaultDownloadsPath = $state('')
  $effect(() => {
    if (!supportsFilesystem) return
    if (!isExternalServer) {
      platformService.getDefaultDownloadsPath().then(p => { defaultDownloadsPath = p })
    } else {
      defaultDownloadsPath = ''
    }
  })

  let confirmedDownloadsPath   = $state(settingsState.settings.serverDownloadsPath ?? '')
  let confirmedLocalSourcePath = $state(settingsState.settings.serverLocalSourcePath ?? '')

  let migrateFrom     = $state<string | null>(null)
  let migrateTo       = $state<string | null>(null)
  let migrating       = $state(false)
  let migrateProgress = $state<{ done: number; total: number; current: string } | null>(null)
  let migrateError    = $state<string | null>(null)
  let migrateUnlisten: (() => void) | null = null

  let extraScanDirs     = $state<string[]>([...(settingsState.settings.extraScanDirs ?? [])])
  let newScanDir        = $state('')
  let multiStorageInfos = $state<(StorageInfo & { label: string })[]>([])
  let advStorageOpen    = $state(false)
  let backupSectionOpen = $state(false)
  let resetSectionOpen  = $state(false)

  async function fetchStorage() {
    if (!supportsFilesystem) return
    storageLoading = true; storageError = null
    try {
      const { downloadsPath: dl, localSourcePath: loc } = await getAdapter().getDownloadsPath()
      downloadsPathInput = dl; localSourcePathInput = loc
      confirmedDownloadsPath = dl; confirmedLocalSourcePath = loc
      updateSettings({ serverDownloadsPath: dl, serverLocalSourcePath: loc })
      if (isExternalServer) { multiStorageInfos = []; storageInfo = null; return }
      const effectiveDl = dl || defaultDownloadsPath
      const dirsToScan: { path: string; label: string }[] = []
      if (effectiveDl) dirsToScan.push({ path: effectiveDl, label: dl ? 'Downloads' : 'Downloads (default)' })
      if (loc && loc !== effectiveDl) dirsToScan.push({ path: loc, label: 'Local source' })
      for (const p of extraScanDirs) {
        if (p && !dirsToScan.find(d => d.path === p)) dirsToScan.push({ path: p, label: p })
      }
      if (dirsToScan.length === 0) { multiStorageInfos = []; storageInfo = null; return }
      const results = await Promise.allSettled(
        dirsToScan.map(d => platformService.getStorageInfo(d.path).then(info => ({ ...info, label: d.label })))
      )
      multiStorageInfos = results
        .filter((r): r is PromiseFulfilledResult<StorageInfo & { label: string }> => r.status === 'fulfilled')
        .map(r => r.value)
      storageInfo = multiStorageInfos[0] ?? null
    } catch (e: any) {
      storageError = e instanceof Error ? e.message : String(e)
    } finally { storageLoading = false }
  }

  async function validatePath(path: string): Promise<string | null> {
    if (!path.trim() || isExternalServer || !supportsFilesystem) return null
    try {
      const exists = await platformService.checkPathExists(path.trim())
      return exists ? null : 'Directory does not exist'
    } catch { return 'Could not check path' }
  }

  async function createDirectory(path: string): Promise<void> {
    if (isExternalServer) throw new Error('Cannot create directories on an external server')
    await platformService.createDirectory(path)
  }

  async function savePaths() {
    const dl  = downloadsPathInput.trim()
    const loc = localSourcePathInput.trim()
    pathsError = null; pathsFieldError = {}
    const [dlErr, locErr] = await Promise.all([validatePath(dl), validatePath(loc)])
    if (dlErr || locErr) { pathsFieldError = { ...(dlErr ? { dl: dlErr } : {}), ...(locErr ? { loc: locErr } : {}) }; return }
    pathsSaving = true
    try {
      await getAdapter().setDownloadsPath(dl)
      if (loc) await getAdapter().setLocalSourcePath(loc)
      updateSettings({ serverDownloadsPath: dl, serverLocalSourcePath: loc })
      if (supportsFilesystem && !isExternalServer) {
        const oldDl = confirmedDownloadsPath || defaultDownloadsPath
        const newDl = dl || defaultDownloadsPath
        if (newDl && oldDl && newDl !== oldDl) {
          const hadContent = await platformService.checkPathExists(oldDl)
          if (hadContent) { migrateFrom = oldDl; migrateTo = newDl }
        }
      }
      confirmedDownloadsPath = dl; confirmedLocalSourcePath = loc
      pathsSaved = true; setTimeout(() => pathsSaved = false, 2000)
      await fetchStorage()
    } catch (e: any) {
      pathsError = e?.message ?? 'Failed to save paths'
    } finally { pathsSaving = false }
  }

  async function startMigration() {
    if (!migrateFrom || !migrateTo) return
    migrating = true; migrateError = null; migrateProgress = { done: 0, total: 0, current: '' }
    migrateUnlisten = await platformService.onMigrateProgress(p => { migrateProgress = p })
    try {
      await platformService.migrateDownloads(migrateFrom, migrateTo)
      migrateFrom = null; migrateTo = null; migrateProgress = null
      await fetchStorage()
    } catch (e: any) {
      migrateError = e?.message ?? 'Migration failed'
    } finally { migrating = false; migrateUnlisten?.(); migrateUnlisten = null }
  }

  function dismissMigration() { migrateFrom = null; migrateTo = null; migrateError = null; migrateProgress = null }

  async function browseDownloadsFolder() {
    const picked = await platformService.pickFolder()
    if (picked) { downloadsPathInput = picked; pathsFieldError = { ...pathsFieldError, dl: undefined }; await savePaths() }
  }

  async function browseLocalSourceFolder() {
    const picked = await platformService.pickFolder()
    if (picked) { localSourcePathInput = picked; pathsFieldError = { ...pathsFieldError, loc: undefined }; await savePaths() }
  }

  async function browseExtraScanDir() {
    const picked = await platformService.pickFolder()
    if (picked) { newScanDir = picked; addExtraScanDir() }
  }

  function addExtraScanDir() {
    const dir = newScanDir.trim()
    if (!dir || extraScanDirs.includes(dir)) return
    extraScanDirs = [...extraScanDirs, dir]
    updateSettings({ extraScanDirs }); newScanDir = ''; fetchStorage()
  }

  function removeExtraScanDir(path: string) {
    extraScanDirs = extraScanDirs.filter(d => d !== path)
    updateSettings({ extraScanDirs }); fetchStorage()
  }

  function fmtBytes(bytes: number): string {
    if (bytes === 0) return '0 B'
    const units = ['B', 'KB', 'MB', 'GB', 'TB']
    const i = Math.floor(Math.log(bytes) / Math.log(1024))
    return `${(bytes / Math.pow(1024, i)).toFixed(i >= 2 ? 1 : 0)} ${units[i]}`
  }

  let backupLoading = $state(false)
  let backupError   = $state<string | null>(null)
  let backupList    = $state<(BackupEntry & { deleting?: boolean })[]>([])

  async function loadBackupList() {
    backupList = (await loadBackups()).map(b => ({ ...b }))
  }

  async function saveBackupList() {
    await saveBackups(backupList.map(({ url, name }) => ({ url, name })))
  }

  async function createBackup() {
    backupLoading = true; backupError = null
    try {
      const { url } = await getAdapter().createBackup()
      const name = url.split('/').pop() ?? url
      backupList = [{ url, name }, ...backupList]
      await saveBackupList()
    } catch (e: any) { backupError = e?.message ?? 'Failed to create backup' }
    finally { backupLoading = false }
  }

  async function deleteBackup(url: string) {
    backupList = backupList.map(b => b.url === url ? { ...b, deleting: true } : b)
    try {
      await getAdapter().deleteBackup(url)
      backupList = backupList.filter(b => b.url !== url)
      await saveBackupList()
    } catch (e: any) {
      backupList = backupList.map(b => b.url === url ? { ...b, deleting: false } : b)
      backupError = e?.message ?? 'Failed to delete backup'
    }
  }

  async function downloadBackup(backup: BackupEntry) {
    try {
      const blob = await getAdapter().downloadBackup(backup.url)
      if ('showSaveFilePicker' in window) {
        try {
          const handle = await (window as any).showSaveFilePicker({
            suggestedName: backup.name,
            types: [{ description: 'Backup file', accept: { 'application/octet-stream': ['.tachibk', '.proto.gz'] } }],
          })
          const writable = await handle.createWritable()
          await writable.write(blob); await writable.close()
          toast({ kind: 'success', message: 'Backup saved', detail: backup.name }); return
        } catch (pickerErr: any) { if (pickerErr?.name === 'AbortError') return }
      }
      const objectUrl = URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = objectUrl; a.download = backup.name
      document.body.appendChild(a); a.click(); document.body.removeChild(a)
      setTimeout(() => URL.revokeObjectURL(objectUrl), 5000)
      toast({ kind: 'download', message: 'Backup downloaded', detail: backup.name })
    } catch (e: any) { backupError = e?.message ?? 'Failed to download backup' }
  }

  let restoreLoading      = $state(false)
  let restoreError        = $state<string | null>(null)
  let restoreStatus       = $state<RestoreStatus | null>(null)
  let restorePollInterval = $state<ReturnType<typeof setInterval> | null>(null)
  let validateLoading     = $state(false)
  let validateError       = $state<string | null>(null)
  let validateResult      = $state<ValidateBackupResult | null>(null)
  let restoreFile         = $state<File | null>(null)

  function stopRestorePoll() {
    if (restorePollInterval) { clearInterval(restorePollInterval); restorePollInterval = null }
  }

  async function pollRestoreStatus(id: string) {
    try {
      const status = await getAdapter().pollRestoreStatus(id)
      restoreStatus = status
      if (status?.state === 'SUCCESS' || status?.state === 'FAILURE') stopRestorePoll()
    } catch {}
  }

  async function submitRestore() {
    if (!restoreFile) return
    restoreLoading = true; restoreError = null; restoreStatus = null
    stopRestorePoll()
    try {
      const result = await requestManager.meta.restoreBackup(restoreFile)
      restoreStatus = result.status
      if (result.status?.state !== 'SUCCESS' && result.status?.state !== 'FAILURE')
        restorePollInterval = setInterval(() => pollRestoreStatus(result.id), 1500)
    } catch (e: any) { restoreError = e?.message ?? 'Failed to start restore' }
    finally { restoreLoading = false }
  }

  async function submitValidate() {
    if (!restoreFile) return
    validateLoading = true; validateError = null; validateResult = null
    try {
      validateResult = await requestManager.meta.validateBackup(restoreFile)
    } catch (e: any) { validateError = e?.message ?? 'Failed to validate backup' }
    finally { validateLoading = false }
  }

  let appDataExporting = $state(false)
  let appDataImporting = $state(false)
  let appDataError     = $state<string | null>(null)
  let appDataMsg       = $state<string | null>(null)
  let appDataBackupDir = $state<string | null>(null)

  $effect(() => {
    if (!supportsFilesystem) return
    platformService.getAutoBackupDir().then(d => { appDataBackupDir = d }).catch(() => {})
  })

  async function handleExportAppData() {
    appDataExporting = true; appDataError = null; appDataMsg = null
    try {
      await exportAppData()
      appDataMsg = 'Backup saved.'
      setTimeout(() => appDataMsg = null, 3000)
    } catch (e: any) {
      if (String(e).includes('Cancelled')) return
      appDataError = e?.message ?? String(e)
    } finally { appDataExporting = false }
  }

  async function handleImportAppData() {
    appDataImporting = true; appDataError = null; appDataMsg = null
    try {
      await importAppData()
    } catch (e: any) {
      if (String(e).includes('Cancelled')) { appDataImporting = false; return }
      appDataError = e?.message ?? String(e)
      appDataImporting = false
    }
  }

  $effect(() => { untrack(() => { loadBackupList(); fetchStorage() }) })
  $effect(() => { return () => stopRestorePoll() })
</script>

<div class="s-panel">

  {#if migrateFrom && !isExternalServer}
    <div class="s-migrate-banner">
      <div class="s-migrate-body">
        <span class="s-migrate-title">Manga found at previous path — move to new location?</span>
        <span class="s-migrate-paths">{migrateFrom} → {migrateTo}</span>
        {#if migrateProgress && migrateProgress.total > 0}
          <div class="s-migrate-bar"><div class="s-migrate-fill" style="width:{Math.round((migrateProgress.done/migrateProgress.total)*100)}%"></div></div>
          <span class="s-migrate-paths">{migrateProgress.current} · {migrateProgress.done} / {migrateProgress.total}</span>
        {/if}
        {#if migrateError}<span class="s-desc" style="color:var(--color-error)">{migrateError}</span>{/if}
      </div>
      <div class="s-migrate-actions">
        <button class="s-btn s-btn-accent" onclick={startMigration} disabled={migrating}>
          {migrating ? (migrateProgress ? `Moving… ${migrateProgress.done}/${migrateProgress.total}` : 'Starting…') : 'Move files'}
        </button>
        <button class="s-btn" onclick={dismissMigration} disabled={migrating}>Skip</button>
      </div>
    </div>
  {/if}

  <div class="s-section">
    <p class="s-section-title">
      Disk Usage
      <button class="s-btn" onclick={fetchStorage} disabled={storageLoading}>{storageLoading ? '…' : '↻'}</button>
    </p>
    <div class="s-section-body">
      {#if storageLoading}
        <p class="s-empty">Reading filesystem…</p>
      {:else if storageError}
        <p class="s-empty" style="color:var(--color-error)">{storageError}</p>
      {:else if !supportsFilesystem}
        <p class="s-empty">Disk usage is unavailable in web mode.</p>
      {:else if isExternalServer}
        <p class="s-empty">Disk usage is unavailable for external servers — filesystem access requires a local connection.</p>
      {:else if multiStorageInfos.length > 0}
        {#each multiStorageInfos as info}
          {@const limitGb    = settingsState.settings.storageLimitGb ?? null}
          {@const limitBytes = limitGb !== null ? limitGb * 1024 ** 3 : null}
          {@const available  = info.manga_bytes + info.free_bytes}
          {@const cap        = limitBytes !== null ? Math.min(limitBytes, available) : available}
          {@const pct        = cap > 0 ? Math.min(100, (info.manga_bytes / cap) * 100) : 0}
          <div class="s-storage-wrap">
            <div class="s-storage-header">
              <span class="s-storage-label">{info.label}</span>
              <span class="s-storage-used">{fmtBytes(info.manga_bytes)} of {fmtBytes(cap)}</span>
            </div>
            <div class="s-storage-bar">
              <div class="s-storage-fill" class:critical={pct > 90} class:warn={pct > 75 && pct <= 90} style="width:{pct}%"></div>
            </div>
            <div class="s-storage-footer">
              <span>{info.path}</span>
              <span>{fmtBytes(info.free_bytes)} free</span>
            </div>
          </div>
        {/each}
      {:else}
        <p class="s-empty">No download path configured.</p>
      {/if}
    </div>
  </div>

  <div class="s-section">
    <p class="s-section-title">Downloads Path</p>
    <div class="s-section-body">
      {#if isExternalServer}
        <div class="s-row">
          <span class="s-desc">Connected to an external server. The path below is read from the server — changes here will update the server's config directly.</span>
        </div>
      {/if}
      <div class="s-row" style="gap:var(--sp-2)">
        <input class="s-input full" class:error={!!pathsFieldError.dl}
          bind:value={downloadsPathInput}
          placeholder={isExternalServer ? 'Server default' : (defaultDownloadsPath || 'Default location')}
          spellcheck="false"
          onkeydown={(e) => e.key === 'Enter' && savePaths()}
          oninput={() => { pathsFieldError = { ...pathsFieldError, dl: undefined } }} />
        {#if !isExternalServer && supportsFilesystem}
          <button class="s-btn" onclick={browseDownloadsFolder}>Browse</button>
        {/if}
      </div>
      <div class="s-row">
        <div class="s-row-info">
          {#if pathsFieldError.dl}
            <span class="s-desc" style="color:var(--color-error)">{pathsFieldError.dl}</span>
          {/if}
          {#if pathsError}
            <span class="s-desc" style="color:var(--color-error)">{pathsError}</span>
          {/if}
        </div>
        <div class="s-btn-row">
          {#if pathsFieldError.dl && !isExternalServer && supportsFilesystem}
            <button class="s-btn" onclick={async () => {
              try { await createDirectory(downloadsPathInput.trim()); pathsFieldError = { ...pathsFieldError, dl: undefined } }
              catch (e: any) { pathsFieldError = { ...pathsFieldError, dl: e?.message ?? 'Failed' } }
            }}>Create</button>
          {/if}
          {#if downloadsPathInput.trim() !== confirmedDownloadsPath}
            <button class="s-btn s-btn-accent" onclick={savePaths} disabled={pathsSaving}>
              {pathsSaved ? 'Saved ✓' : pathsSaving ? 'Saving…' : 'Save'}
            </button>
          {/if}
        </div>
      </div>
    </div>
  </div>

  <div class="s-section">
    <p class="s-section-title">Storage Limit</p>
    <div class="s-section-body">
      <div class="s-row">
        <div class="s-row-info">
          <span class="s-label">Warn when limit is reached</span>
          <span class="s-desc">{settingsState.settings.storageLimitGb === null ? 'No limit set' : `Warn above ${settingsState.settings.storageLimitGb} GB`}</span>
        </div>
        {#if settingsState.settings.storageLimitGb === null}
          <button class="s-btn" onclick={() => updateSettings({ storageLimitGb: 10 })}>Set limit</button>
        {:else}
          <div class="s-stepper">
            <button class="s-step-btn" onclick={() => updateSettings({ storageLimitGb: Math.max(1, (settingsState.settings.storageLimitGb ?? 10) - 1) })} disabled={(settingsState.settings.storageLimitGb ?? 10) <= 1}>−</button>
            <input type="number" min="1" step="1" class="s-slider-val" style="width:52px"
              value={settingsState.settings.storageLimitGb}
              oninput={(e) => { const n = parseFloat(e.currentTarget.value); if (!isNaN(n) && n > 0) updateSettings({ storageLimitGb: n }) }} />
            <span class="s-slider-unit">GB</span>
            <button class="s-step-btn" onclick={() => updateSettings({ storageLimitGb: (settingsState.settings.storageLimitGb ?? 10) + 1 })}>+</button>
            <button class="s-btn-icon" title="Remove limit" onclick={() => updateSettings({ storageLimitGb: null })}>↺</button>
          </div>
        {/if}
      </div>
    </div>
  </div>

  <div class="s-section">
    <button class="s-collapsible-trigger" onclick={() => advStorageOpen = !advStorageOpen}>
      <span class="s-label">Advanced</span>
      <svg class="s-collapsible-caret" class:open={advStorageOpen} width="10" height="6" viewBox="0 0 10 6"><path d="M0 0l5 6 5-6" fill="currentColor"/></svg>
    </button>
    {#if advStorageOpen}
      <div class="s-collapsible-body">
        <div class="s-row">
          <div class="s-row-info">
            <span class="s-label">Local source path</span>
            <span class="s-desc">Read manga already on disk without an extension. Leave blank if unused.</span>
          </div>
          <div style="display:flex;flex-direction:column;align-items:flex-end;gap:4px">
            <div class="s-btn-row">
              <input class="s-input mono" class:error={!!pathsFieldError.loc}
                bind:value={localSourcePathInput} placeholder="Optional" spellcheck="false"
                onkeydown={(e) => e.key === 'Enter' && savePaths()}
                oninput={() => { pathsFieldError = { ...pathsFieldError, loc: undefined } }} />
              {#if !isExternalServer && supportsFilesystem}
                <button class="s-btn" onclick={browseLocalSourceFolder}>Browse</button>
              {/if}
              {#if pathsFieldError.loc && !isExternalServer && supportsFilesystem}
                <button class="s-btn" onclick={async () => {
                  try { await createDirectory(localSourcePathInput.trim()); pathsFieldError = { ...pathsFieldError, loc: undefined } }
                  catch (e: any) { pathsFieldError = { ...pathsFieldError, loc: e?.message ?? 'Failed' } }
                }}>Create</button>
              {/if}
            </div>
            {#if pathsFieldError.loc}<span class="s-desc" style="color:var(--color-error)">{pathsFieldError.loc}</span>{/if}
          </div>
        </div>

        {#each extraScanDirs as dir}
          <div class="s-row">
            <div class="s-row-info">
              <span class="s-label mono" style="font-family:monospace;font-size:var(--text-xs)">{dir}</span>
              <span class="s-desc">Extra scan directory</span>
            </div>
            <button class="s-btn s-btn-danger" onclick={() => removeExtraScanDir(dir)}>Remove</button>
          </div>
        {/each}

        <div class="s-row">
          <div class="s-row-info">
            <span class="s-label">Additional scan path</span>
            <span class="s-desc">Include an extra directory in disk usage readings</span>
          </div>
          <div class="s-btn-row">
            <input class="s-input mono" bind:value={newScanDir} placeholder="/path/to/dir" spellcheck="false"
              onkeydown={(e) => e.key === 'Enter' && addExtraScanDir()} />
            {#if !isExternalServer && supportsFilesystem}
              <button class="s-btn" onclick={browseExtraScanDir}>Browse</button>
            {/if}
          </div>
        </div>
      </div>
    {/if}
  </div>

  <div class="s-section">
    <button class="s-collapsible-trigger" onclick={() => backupSectionOpen = !backupSectionOpen}>
      <span class="s-label">Backup</span>
      <svg class="s-collapsible-caret" class:open={backupSectionOpen} width="10" height="6" viewBox="0 0 10 6"><path d="M0 0l5 6 5-6" fill="currentColor"/></svg>
    </button>
    {#if backupSectionOpen}
      <div class="s-collapsible-body">

        <p class="s-subsection-title">Library backup</p>

        <div class="s-row">
          <div class="s-row-info">
            <span class="s-label">Create backup</span>
            <span class="s-desc">Snapshot your library, categories, and tracker links</span>
          </div>
          <button class="s-btn s-btn-accent" onclick={createBackup} disabled={backupLoading}>
            {backupLoading ? 'Creating…' : 'Create backup'}
          </button>
        </div>

        {#if backupError}
          <div class="s-banner s-banner-error">{backupError}</div>
        {/if}

        {#if backupList.length === 0}
          <p class="s-empty">No backups yet — create one above.</p>
        {:else}
          {#each backupList as backup}
            <div class="s-folder-row">
              <ClockCounterClockwise size={14} weight="light" style="color:var(--text-faint);flex-shrink:0" />
              <span class="s-folder-name" style="font-family:monospace;font-size:var(--text-xs)">{backup.name}</span>
              <button class="s-btn-icon" onclick={() => downloadBackup(backup)} title="Download">↓</button>
              <button class="s-btn-icon danger" onclick={() => deleteBackup(backup.url)} disabled={backup.deleting} title="Delete">
                <Trash size={12} weight="light" />
              </button>
            </div>
          {/each}
        {/if}

        <div class="s-row">
          <div class="s-row-info">
            <span class="s-label">Restore from file</span>
            <span class="s-desc">{restoreFile ? restoreFile.name : 'Select a .tachibk file'}</span>
          </div>
          <label class="s-btn" style="cursor:pointer">
            Browse
            <input type="file" accept=".tachibk,.proto.gz" style="display:none"
              onchange={(e) => {
                const f = (e.currentTarget as HTMLInputElement).files?.[0] ?? null
                restoreFile = f; restoreStatus = null; restoreError = null; validateResult = null; validateError = null
              }} />
          </label>
        </div>

        {#if restoreFile}
          <div class="s-row">
            <div class="s-row-info"></div>
            <div class="s-btn-row">
              <button class="s-btn" onclick={submitValidate} disabled={validateLoading || restoreLoading}>
                {validateLoading ? 'Checking…' : 'Validate'}
              </button>
              <button class="s-btn s-btn-accent" onclick={submitRestore} disabled={restoreLoading || validateLoading}>
                {restoreLoading ? 'Restoring…' : 'Restore'}
              </button>
            </div>
          </div>
        {/if}

        {#if validateError}
          <div class="s-banner s-banner-error">{validateError}</div>
        {/if}

        {#if validateResult}
          {#if validateResult.missingSources.length === 0 && validateResult.missingTrackers.length === 0}
            <div class="s-row"><span class="s-desc" style="color:var(--color-success,#4caf50)">✓ All sources and trackers present</span></div>
          {:else}
            {#if validateResult.missingSources.length > 0}
              <div class="s-row">
                <div class="s-row-info">
                  <span class="s-label" style="color:var(--color-error)">Missing sources</span>
                  <span class="s-desc">{validateResult.missingSources.map(s => s.name).join(', ')}</span>
                </div>
              </div>
            {/if}
            {#if validateResult.missingTrackers.length > 0}
              <div class="s-row">
                <div class="s-row-info">
                  <span class="s-label" style="color:var(--color-error)">Missing trackers</span>
                  <span class="s-desc">{validateResult.missingTrackers.map(t => t.name).join(', ')}</span>
                </div>
              </div>
            {/if}
          {/if}
        {/if}

        {#if restoreError}
          <div class="s-banner s-banner-error">{restoreError}</div>
        {/if}

        {#if restoreStatus}
          <div class="s-row">
            <div class="s-row-info">
              <span class="s-label">
                {restoreStatus.state === 'SUCCESS' ? '✓ Restore complete' :
                 restoreStatus.state === 'FAILURE' ? '✗ Restore failed'  : 'Restoring…'}
              </span>
              {#if restoreStatus.totalManga > 0}
                <span class="s-desc">{restoreStatus.mangaProgress} / {restoreStatus.totalManga} manga</span>
              {/if}
            </div>
            {#if restoreStatus.state !== 'SUCCESS' && restoreStatus.state !== 'FAILURE' && restoreStatus.totalManga > 0}
              <div class="s-storage-bar" style="width:160px;flex-shrink:0">
                <div class="s-storage-fill" style="width:{Math.round((restoreStatus.mangaProgress / restoreStatus.totalManga) * 100)}%"></div>
              </div>
            {/if}
          </div>
        {/if}

        <p class="s-subsection-title">App data backup</p>

        <div class="s-row">
          <div class="s-row-info">
            <span class="s-label">Export settings</span>
            <span class="s-desc">Save all Moku app settings to a .zip via a native save dialog.</span>
          </div>
          <button class="s-btn s-btn-accent" onclick={handleExportAppData} disabled={appDataExporting || !supportsFilesystem}>
            {appDataExporting ? 'Saving…' : 'Export'}
          </button>
        </div>

        <div class="s-row">
          <div class="s-row-info">
            <span class="s-label">Import settings</span>
            <span class="s-desc">Restore from a previously exported .zip file. Reloads the app immediately.</span>
          </div>
          <button class="s-btn" onclick={handleImportAppData} disabled={appDataImporting || !supportsFilesystem}>
            {appDataImporting ? 'Importing…' : 'Import'}
          </button>
        </div>

        {#if appDataError}
          <div class="s-banner s-banner-error">{appDataError}</div>
        {/if}

        {#if appDataMsg}
          <div class="s-row">
            <span class="s-desc" style="color:var(--color-success,#4caf50)">{appDataMsg}</span>
          </div>
        {/if}

        {#if appDataBackupDir}
          <div class="s-row">
            <div class="s-row-info">
              <span class="s-label">Auto-backup location</span>
              <span class="s-desc">Pre-update snapshots are kept here (last 5).</span>
            </div>
            <button class="s-btn" onclick={() => platformService.openPath(appDataBackupDir!)}>Open folder</button>
          </div>
        {/if}

      </div>
    {/if}
  </div>

  <div class="s-section">
    <button class="s-collapsible-trigger" onclick={() => resetSectionOpen = !resetSectionOpen}>
      <span class="s-label">Reset</span>
      <svg class="s-collapsible-caret" class:open={resetSectionOpen} width="10" height="6" viewBox="0 0 10 6"><path d="M0 0l5 6 5-6" fill="currentColor"/></svg>
    </button>
    {#if resetSectionOpen}
      <div class="s-collapsible-body">
        {#each resetItems as item}
          <div class="s-row">
            <div class="s-row-info">
              <span class="s-label">{item.label}</span>
              <span class="s-desc">{item.desc}</span>
              {#if item.error}<span class="s-desc" style="color:var(--color-error)">{item.error}</span>{/if}
            </div>
            <div class="s-btn-row">
              {#if item.state === 'done'}
                <span class="s-pill on">Done</span>
              {:else if item.state === 'busy'}
                <button class="s-btn" disabled>Working…</button>
              {:else if confirming === item.key}
                <span class="s-desc" style="color:var(--text-muted)">Sure?</span>
                <button class="s-btn s-btn-danger" onclick={() => runReset(item.key)}>Confirm</button>
                <button class="s-btn" onclick={() => confirming = null}>Cancel</button>
              {:else}
                <button
                  class="s-btn"
                  class:s-btn-danger={item.confirm}
                  onclick={() => item.confirm ? (confirming = item.key) : runReset(item.key)}
                >Reset</button>
              {/if}
            </div>
          </div>
        {/each}
      </div>
    {/if}
  </div>

</div>