<template>
  <div class="flex h-full">
    <!-- Sidebar -->
    <aside class="w-44 bg-bg-surface border-r border-border-subtle shrink-0 p-3">
      <h1 class="text-[0.8125rem] font-medium text-text-tertiary uppercase tracking-[0.08em] px-2 mb-3">Settings</h1>
      <nav role="tablist" aria-label="Settings sections" aria-orientation="vertical" @keydown="onTabKeydown" class="flex flex-col gap-0.5">
        <button
          v-for="tab in tabs"
          :key="tab.id"
          @click="activeTab = tab.id"
          role="tab"
          :aria-selected="activeTab === tab.id"
          :aria-controls="'tabpanel-' + tab.id"
          :id="'tab-' + tab.id"
          :tabindex="activeTab === tab.id ? 0 : -1"
          class="relative flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-[0.8125rem] transition-colors duration-150"
          :class="activeTab === tab.id
            ? 'bg-bg-elevated text-text-primary'
            : 'text-text-secondary hover:bg-bg-elevated hover:text-text-primary'"
        >
          <!-- Active indicator -->
          <div
            v-if="activeTab === tab.id"
            class="absolute left-0 top-1.5 bottom-1.5 w-[2px] rounded-full bg-accent"
            aria-hidden="true"
          />
          <component :is="tab.icon" class="w-4 h-4 shrink-0" :stroke-width="1.5" aria-hidden="true" />
          {{ tab.label }}
        </button>
      </nav>
    </aside>

    <!-- Content pane -->
    <div class="flex-1 p-6 overflow-y-auto">
      <div class="max-w-2xl">
      <Transition name="fade" mode="out-in" :duration="{ enter: 200, leave: 150 }">
      <div :key="activeTab">
      <!-- General -->
      <div v-if="activeTab === 'general'" id="tabpanel-general" role="tabpanel" aria-labelledby="tab-general" tabindex="0">
        <h2 class="text-[1.25rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-6">General</h2>

        <div class="space-y-4">
          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">UI Scale</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Adjust the interface zoom level</p>
            </div>
            <div class="flex items-center gap-2">
              <button
                v-tooltip="'Decrease zoom'"
                @click="decreaseZoom"
                aria-label="Decrease zoom"
                class="w-8 h-8 flex items-center justify-center border border-border-visible rounded-lg text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
                :disabled="zoomLevel <= 80"
              >
                <Minus class="w-3.5 h-3.5" aria-hidden="true" />
              </button>
              <span class="w-12 text-center text-sm font-mono text-text-primary" role="status" aria-live="polite">{{ zoomLevel }}%</span>
              <button
                v-tooltip="'Increase zoom'"
                @click="increaseZoom"
                aria-label="Increase zoom"
                class="w-8 h-8 flex items-center justify-center border border-border-visible rounded-lg text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
                :disabled="zoomLevel >= 150"
              >
                <Plus class="w-3.5 h-3.5" aria-hidden="true" />
              </button>
            </div>
          </div>

          <div class="flex items-center justify-between">
            <div>
              <p id="label-locale" class="text-[0.8125rem] text-text-primary">Locale</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Date and number formatting</p>
            </div>
            <div class="w-56">
              <AppSelect
                v-model="locale"
                aria-labelledby="label-locale"
                :options="LOCALES"
                label-key="name"
                value-key="code"
                placeholder="System Default"
                :placeholder-value="'system'"
                :searchable="true"
                @update:model-value="saveLocaleSettings"
              />
            </div>
          </div>

          <div class="flex items-center justify-between">
            <div>
              <p id="label-currency" class="text-[0.8125rem] text-text-primary">Currency</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Currency symbol and formatting</p>
            </div>
            <div class="w-56">
              <AppSelect
                v-model="currency"
                aria-labelledby="label-currency"
                :options="currencyOptions"
                label-key="name"
                value-key="code"
                placeholder="US Dollar"
                :placeholder-value="'USD'"
                :searchable="true"
                @update:model-value="saveLocaleSettings"
              />
            </div>
          </div>

          <div class="border-t border-border-subtle" />

          <div class="flex items-center justify-between">
            <div>
              <p id="label-theme" class="text-[0.8125rem] text-text-primary">Theme</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Light or dark appearance</p>
            </div>
            <div class="w-48">
              <AppSelect
                v-model="themeMode"
                aria-labelledby="label-theme"
                :options="themeModes"
                label-key="label"
                value-key="value"
                placeholder="Dark"
                :placeholder-value="'dark'"
                @update:model-value="saveThemeSettings"
              />
            </div>
          </div>

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Accent Color</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Primary interface color</p>
            </div>
            <div class="flex gap-2">
              <button
                v-for="ac in accentColors"
                :key="ac.value"
                @click="accentColor = ac.value; saveThemeSettings()"
                class="w-6 h-6 rounded-full border-2 transition-all hover:scale-110 cursor-pointer"
                :class="accentColor === ac.value ? 'border-text-primary scale-110' : 'border-transparent'"
                :style="{ backgroundColor: ac.color }"
                :aria-label="ac.label + ' accent color'"
                :aria-pressed="accentColor === ac.value"
              />
            </div>
          </div>

          <div class="border-t border-border-subtle" />

          <div class="flex items-center justify-between">
            <div>
              <p id="label-reduce-motion" class="text-[0.8125rem] text-text-primary">Reduce motion</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Control animation behavior</p>
            </div>
            <div class="w-48">
              <AppSelect
                v-model="reduceMotion"
                aria-labelledby="label-reduce-motion"
                :options="reduceMotionOptions"
                label-key="label"
                value-key="value"
                placeholder="System (default)"
                :placeholder-value="'system'"
                @update:model-value="saveReduceMotionSetting"
              />
            </div>
          </div>

          <div class="border-t border-border-subtle" />

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Dyslexia-friendly mode</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Use OpenDyslexic font throughout the interface</p>
            </div>
            <button
              @click="toggleDyslexiaMode"
              role="switch"
              :aria-checked="dyslexiaMode"
              aria-label="Dyslexia-friendly mode"
              :class="[
                'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
                dyslexiaMode ? 'bg-status-running' : 'bg-bg-elevated'
              ]"
            >
              <span
                :class="[
                  'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
                  dyslexiaMode ? 'translate-x-[18px]' : 'translate-x-[3px]'
                ]"
              />
            </button>
          </div>

          <div class="flex items-center justify-between">
            <div>
              <p id="label-ui-font" class="text-[0.8125rem] text-text-primary">UI Font</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Choose a font for the interface</p>
            </div>
            <div class="w-56">
              <AppSelect
                v-model="uiFont"
                aria-labelledby="label-ui-font"
                :options="UI_FONTS"
                label-key="label"
                value-key="value"
                placeholder="Inter (Default)"
                :placeholder-value="'Inter'"
                :disabled="dyslexiaMode"
                @update:model-value="saveUIFontSetting"
              >
                <template #option="{ item, label }">
                  <span :style="{ fontFamily: `'${item.value}', system-ui, sans-serif` }">{{ label }}</span>
                </template>
                <template #selected="{ label, isPlaceholder }">
                  <span
                    :class="isPlaceholder ? 'text-text-tertiary' : 'text-text-primary'"
                    class="truncate"
                    :style="{ fontFamily: `'${uiFont}', system-ui, sans-serif` }"
                  >{{ label }}</span>
                </template>
              </AppSelect>
            </div>
          </div>

          <div class="border-t border-border-subtle" />

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Sound effects</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Play audio cues for timer events</p>
            </div>
            <button
              @click="toggleSoundEnabled"
              role="switch"
              :aria-checked="soundEnabled"
              aria-label="Sound effects"
              :class="[
                'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
                soundEnabled ? 'bg-status-running' : 'bg-bg-elevated'
              ]"
            >
              <span
                :class="[
                  'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
                  soundEnabled ? 'translate-x-[18px]' : 'translate-x-[3px]'
                ]"
              />
            </button>
          </div>

          <div v-if="soundEnabled" class="space-y-4">
            <div class="flex items-center justify-between">
              <div>
                <p id="label-sound-mode" class="text-[0.8125rem] text-text-primary">Sound mode</p>
                <p class="text-[0.6875rem] text-text-tertiary mt-0.5">How sounds are generated</p>
              </div>
              <div class="w-48">
                <AppSelect
                  v-model="soundMode"
                  aria-labelledby="label-sound-mode"
                  :options="soundModes"
                  label-key="label"
                  value-key="value"
                  placeholder="Synthesized"
                  :placeholder-value="'synthesized'"
                  @update:model-value="saveSoundSettings"
                />
              </div>
            </div>

            <div class="flex items-center justify-between">
              <div>
                <p class="text-[0.8125rem] text-text-primary">Volume</p>
                <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Audio cue loudness</p>
              </div>
              <div class="flex items-center gap-3">
                <input
                  type="range"
                  min="0"
                  max="100"
                  step="1"
                  v-model.number="soundVolume"
                  @input="saveSoundSettings"
                  aria-label="Sound volume"
                  class="w-32 h-1 accent-accent bg-bg-elevated rounded-full appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-webkit-slider-thumb]:cursor-pointer"
                />
                <span class="text-[0.75rem] text-text-secondary font-mono w-10 text-right">{{ soundVolume }}%</span>
              </div>
            </div>

            <div>
              <p class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium mb-3">Event sounds</p>
              <div class="grid grid-cols-1 gap-2">
                <div
                  v-for="evt in soundEventsList"
                  :key="evt.key"
                  class="flex items-center justify-between py-1"
                >
                  <label :for="'sound-evt-' + evt.key" class="flex items-center gap-2.5 cursor-pointer">
                    <input
                      type="checkbox"
                      :id="'sound-evt-' + evt.key"
                      :checked="soundEventsMap[evt.key]"
                      @change="toggleSoundEvent(evt.key)"
                      class="w-3.5 h-3.5 rounded border-border-visible bg-bg-inset accent-accent cursor-pointer"
                    />
                    <span class="text-[0.8125rem] text-text-primary">{{ evt.label }}</span>
                  </label>
                  <button
                    @click="testSound(evt.key)"
                    class="px-2 py-0.5 text-[0.6875rem] border border-border-subtle text-text-tertiary rounded hover:bg-bg-elevated hover:text-text-secondary transition-colors"
                    :aria-label="'Test ' + evt.label + ' sound'"
                  >
                    Test
                  </button>
                </div>
              </div>
            </div>
          </div>

          <div class="border-t border-border-subtle" />

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Getting Started checklist</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Show the onboarding guide on the Dashboard</p>
            </div>
            <div class="flex items-center gap-3">
              <button
                @click="onboardingStore.reset()"
                class="px-2.5 py-1 text-[0.6875rem] border border-border-subtle text-text-tertiary rounded-md hover:bg-bg-elevated hover:text-text-secondary transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
                aria-label="Reset getting started checklist progress"
              >
                Reset
              </button>
              <button
                @click="onboardingStore.isDismissed ? onboardingStore.restore() : onboardingStore.dismiss()"
                role="switch"
                :aria-checked="!onboardingStore.isDismissed"
                aria-label="Getting Started checklist"
                class="focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
                :class="[
                  'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
                  !onboardingStore.isDismissed ? 'bg-status-running' : 'bg-bg-elevated'
                ]"
              >
                <span
                  :class="[
                    'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
                    !onboardingStore.isDismissed ? 'translate-x-[18px]' : 'translate-x-[3px]'
                  ]"
                />
              </button>
            </div>
          </div>

          <div class="border-t border-border-subtle" />

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Close to tray</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Hide in system tray instead of quitting when closing the window</p>
            </div>
            <button
              @click="toggleCloseToTray"
              role="switch"
              :aria-checked="closeToTray"
              aria-label="Close to tray"
              class="focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
              :class="[
                'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
                closeToTray ? 'bg-status-running' : 'bg-bg-elevated'
              ]"
            >
              <span
                :class="[
                  'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
                  closeToTray ? 'translate-x-[18px]' : 'translate-x-[3px]'
                ]"
              />
            </button>
          </div>

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Minimize to tray</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Hide in system tray instead of the taskbar when minimizing</p>
            </div>
            <button
              @click="toggleMinimizeToTray"
              role="switch"
              :aria-checked="minimizeToTray"
              aria-label="Minimize to tray"
              class="focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
              :class="[
                'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
                minimizeToTray ? 'bg-status-running' : 'bg-bg-elevated'
              ]"
            >
              <span
                :class="[
                  'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
                  minimizeToTray ? 'translate-x-[18px]' : 'translate-x-[3px]'
                ]"
              />
            </button>
          </div>

          <div class="border-t border-border-subtle" />

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Persistent notifications</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Keep toast notifications visible until manually dismissed</p>
            </div>
            <button
              @click="togglePersistentNotifications"
              role="switch"
              :aria-checked="persistentNotifications"
              aria-label="Persistent notifications"
              class="focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
              :class="[
                'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
                persistentNotifications ? 'bg-status-running' : 'bg-bg-elevated'
              ]"
            >
              <span
                :class="[
                  'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
                  persistentNotifications ? 'translate-x-[18px]' : 'translate-x-[3px]'
                ]"
              />
            </button>
          </div>
        </div>
      </div>

      <!-- Timer -->
      <div v-if="activeTab === 'timer'" id="tabpanel-timer" role="tabpanel" aria-labelledby="tab-timer" tabindex="0">
        <h2 class="text-[1.25rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-6">Timer</h2>

        <div class="space-y-4">
          <!-- Idle Detection toggle -->
          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Idle Detection</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Detect when you're idle and prompt to continue or stop</p>
            </div>
            <button
              @click="toggleIdleDetection"
              role="switch"
              :aria-checked="idleDetection"
              aria-label="Idle detection"
              :class="[
                'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
                idleDetection ? 'bg-status-running' : 'bg-bg-elevated'
              ]"
            >
              <span
                :class="[
                  'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
                  idleDetection ? 'translate-x-[18px]' : 'translate-x-[3px]'
                ]"
              />
            </button>
          </div>

          <!-- Idle sub-settings - progressive disclosure -->
          <div v-if="idleDetection" class="space-y-4 pl-4 border-l-2 border-border-subtle ml-1">
            <div class="flex items-center justify-between">
              <div>
                <p class="text-[0.8125rem] text-text-primary">Idle Timeout</p>
                <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Minutes before idle pause triggers</p>
              </div>
              <AppNumberInput
                v-model="idleTimeout"
                :min="1"
                :max="60"
                :step="1"
                :precision="0"
                suffix="min"
                @update:model-value="saveSettings"
              />
            </div>
            <div class="flex items-center justify-between">
              <div>
                <p class="text-[0.8125rem] text-text-primary">Reminder Interval</p>
                <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Minutes between reminders while running</p>
              </div>
              <AppNumberInput
                v-model="reminderInterval"
                :min="0"
                :max="120"
                :step="5"
                :precision="0"
                suffix="min"
                @update:model-value="saveSettings"
              />
            </div>
          </div>

          <!-- Divider -->
          <div class="border-t border-border-subtle" />

          <!-- App Tracking Mode -->
          <div class="flex items-center justify-between">
            <div>
              <p id="label-app-tracking" class="text-[0.8125rem] text-text-primary">App Tracking Mode</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">What happens when a tracked app leaves focus</p>
            </div>
            <div class="w-48">
              <AppSelect
                v-model="appTrackingMode"
                aria-labelledby="label-app-tracking"
                :options="appTrackingModes"
                label-key="label"
                value-key="value"
                placeholder="Auto-pause"
                :placeholder-value="'auto'"
                @update:model-value="saveSettings"
              />
            </div>
          </div>

          <!-- Linux app tracking notice -->
          <p v-if="platform === 'linux'" class="text-[0.6875rem] text-text-tertiary -mt-1">
            On Linux, window visibility cannot be detected. The timer will only pause when the tracked app's process exits entirely.
          </p>

          <!-- Check Interval -->
          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Check Interval</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">How often to check idle & app visibility</p>
            </div>
            <AppNumberInput
              v-model="appCheckInterval"
              :min="1"
              :max="60"
              :step="1"
              :precision="0"
              suffix="sec"
              @update:model-value="saveSettings"
            />
          </div>

          <!-- Divider -->
          <div class="border-t border-border-subtle" />

          <!-- Timeline Recording -->
          <div :class="['flex items-center justify-between', platform === 'linux' && 'opacity-50 pointer-events-none']">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Record app timeline</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Capture which apps and windows are active while the timer runs. Data stays local.</p>
            </div>
            <button
              @click="toggleTimelineRecording"
              role="switch"
              :aria-checked="timelineRecording"
              :disabled="platform === 'linux'"
              aria-label="Record app timeline"
              :class="[
                'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
                timelineRecording ? 'bg-status-running' : 'bg-bg-elevated'
              ]"
            >
              <span
                :class="[
                  'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
                  timelineRecording ? 'translate-x-[18px]' : 'translate-x-[3px]'
                ]"
              />
            </button>
          </div>
          <!-- Linux timeline notice -->
          <p v-if="platform === 'linux'" class="text-[0.6875rem] text-text-tertiary -mt-1">
            Timeline recording is not available on Linux - Wayland's security model prevents detecting the foreground window.
          </p>

          <!-- Divider -->
          <div class="border-t border-border-subtle" />

          <!-- Timer Font -->
          <div class="flex items-center justify-between">
            <div>
              <p id="label-timer-font" class="text-[0.8125rem] text-text-primary">Timer Font</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Monospace font for timer display</p>
            </div>
            <div class="w-56">
              <AppSelect
                v-model="timerFont"
                aria-labelledby="label-timer-font"
                :options="timerFontOptions"
                label-key="label"
                value-key="value"
                placeholder="JetBrains Mono"
                :placeholder-value="'JetBrains Mono'"
                @update:model-value="saveTimerFontSetting"
              >
                <template #option="{ item, label }">
                  <span :style="{ fontFamily: `'${item.value}', monospace` }">{{ label }}</span>
                </template>
                <template #selected="{ label, isPlaceholder }">
                  <span
                    :class="isPlaceholder ? 'text-text-tertiary' : 'text-text-primary'"
                    class="truncate"
                    :style="{ fontFamily: `'${timerFont}', monospace` }"
                  >{{ label }}</span>
                </template>
              </AppSelect>
            </div>
          </div>

          <!-- Divider -->
          <div class="border-t border-border-subtle" />

          <!-- Keyboard Shortcuts -->
          <h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Keyboard Shortcuts</h3>

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Toggle Timer</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Start/stop the active timer</p>
            </div>
            <AppShortcutRecorder
              :model-value="shortcutToggleTimer"
              @update:model-value="(v: string) => { shortcutToggleTimer = v; saveShortcutSettings() }"
              label="Toggle timer shortcut"
            />
          </div>

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Show App</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Bring the app to front</p>
            </div>
            <AppShortcutRecorder
              :model-value="shortcutShowApp"
              @update:model-value="(v: string) => { shortcutShowApp = v; saveShortcutSettings() }"
              label="Show app shortcut"
            />
          </div>

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Quick Entry</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Open quick entry dialog from anywhere</p>
            </div>
            <AppShortcutRecorder
              :model-value="shortcutQuickEntry"
              @update:model-value="(v: string) => { shortcutQuickEntry = v; saveShortcutSettings() }"
              label="Quick entry shortcut"
            />
          </div>

          <!-- Divider -->
          <div class="border-t border-border-subtle" />

          <!-- Goals -->
          <h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Goals</h3>

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Daily Goal</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Target hours per day</p>
            </div>
            <AppNumberInput
              v-model="dailyGoalHours"
              :min="0"
              :max="24"
              :step="0.5"
              :precision="1"
              suffix="hrs"
              @update:model-value="saveGoalSettings"
            />
          </div>

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Weekly Goal</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Target hours per week</p>
            </div>
            <AppNumberInput
              v-model="weeklyGoalHours"
              :min="0"
              :max="168"
              :step="1"
              :precision="0"
              suffix="hrs"
              @update:model-value="saveGoalSettings"
            />
          </div>

          <!-- Divider -->
          <div class="border-t border-border-subtle" />

          <!-- Reminders -->
          <h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Reminders</h3>

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">End-of-day reminder</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Get a notification to log your time at the end of the day</p>
            </div>
            <button
              @click="toggleEodReminder"
              role="switch"
              :aria-checked="eodReminderEnabled"
              aria-label="End-of-day reminder"
              class="focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
              :class="[
                'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
                eodReminderEnabled ? 'bg-status-running' : 'bg-bg-elevated'
              ]"
            >
              <span
                :class="[
                  'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
                  eodReminderEnabled ? 'translate-x-[18px]' : 'translate-x-[3px]'
                ]"
              />
            </button>
          </div>

          <div v-if="eodReminderEnabled" class="flex items-center justify-between pl-4 border-l-2 border-border-subtle ml-1">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Reminder time</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">When to show the reminder</p>
            </div>
            <input
              v-model="eodReminderTime"
              type="time"
              class="px-3 py-1.5 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
              @change="saveReminderSettings"
            />
          </div>

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Weekly summary</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Show a summary of last week's hours on Monday morning</p>
            </div>
            <button
              @click="toggleWeeklySummary"
              role="switch"
              :aria-checked="weeklySummaryEnabled"
              aria-label="Weekly summary"
              class="focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
              :class="[
                'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
                weeklySummaryEnabled ? 'bg-status-running' : 'bg-bg-elevated'
              ]"
            >
              <span
                :class="[
                  'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
                  weeklySummaryEnabled ? 'translate-x-[18px]' : 'translate-x-[3px]'
                ]"
              />
            </button>
          </div>

          <!-- Divider -->
          <div class="border-t border-border-subtle" />

          <!-- Recurring Entries -->
          <h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Recurring Entries</h3>

          <div v-if="recurringEntries.length > 0" class="space-y-2">
            <div
              v-for="rec in recurringEntries"
              :key="rec.id"
              class="flex items-center justify-between py-2 px-3 bg-bg-inset rounded-lg"
            >
              <div class="flex-1 min-w-0">
                <p class="text-[0.8125rem] text-text-primary truncate">
                  {{ getProjectName(rec.project_id) }}
                  <span v-if="rec.description" class="text-text-tertiary"> - {{ rec.description }}</span>
                </p>
                <p class="text-[0.6875rem] text-text-tertiary">
                  {{ formatRecurrenceRule(rec.recurrence_rule) }} at {{ rec.time_of_day }} - {{ formatDuration(rec.duration) }} - {{ formatMode(rec.mode) }}
                </p>
              </div>
              <div class="flex items-center gap-2 ml-3">
                <button
                  @click="recurringStore.toggleEnabled(rec)"
                  role="switch"
                  :aria-checked="rec.enabled === 1"
                  :aria-label="'Toggle ' + (rec.description || 'recurring entry')"
                  :class="[
                    'relative inline-flex h-4 w-7 items-center rounded-full transition-colors duration-150',
                    rec.enabled === 1 ? 'bg-status-running' : 'bg-bg-elevated'
                  ]"
                >
                  <span
                    :class="[
                      'inline-block h-3 w-3 transform rounded-full bg-text-primary transition-transform duration-150',
                      rec.enabled === 1 ? 'translate-x-[13px]' : 'translate-x-[2px]'
                    ]"
                  />
                </button>
                <button
                  @click="editRecurringEntry(rec)"
                  class="text-text-tertiary hover:text-text-primary transition-colors"
                  :aria-label="'Edit ' + (rec.description || 'recurring entry')"
                >
                  <Pencil class="w-3.5 h-3.5" />
                </button>
                <button
                  @click="deleteRecurringEntry(rec.id!)"
                  class="text-text-tertiary hover:text-status-error transition-colors"
                  :aria-label="'Delete ' + (rec.description || 'recurring entry')"
                >
                  <Trash2 class="w-3.5 h-3.5" />
                </button>
              </div>
            </div>
          </div>

          <div v-if="!showRecurringForm">
            <button
              @click="showRecurringForm = true; resetRecurringForm()"
              class="flex items-center gap-1.5 text-[0.8125rem] text-accent-text hover:text-accent transition-colors"
            >
              <Plus class="w-3.5 h-3.5" />
              Add recurring entry
            </button>
          </div>

          <!-- Add/Edit Recurring Entry Form (inline) -->
          <div v-if="showRecurringForm" class="space-y-4 p-4 bg-bg-inset rounded-lg border border-border-subtle">
            <div class="grid grid-cols-2 gap-4">
              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Project</label>
                <AppSelect
                  v-model="recProjectId"
                  :options="recProjects"
                  label-key="name"
                  value-key="id"
                  placeholder="Select project"
                />
              </div>
              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Task</label>
                <AppSelect
                  v-model="recTaskId"
                  :options="recTasks"
                  label-key="name"
                  value-key="id"
                  placeholder="No task"
                  :disabled="!recProjectId"
                />
              </div>
            </div>

            <div>
              <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Description</label>
              <input
                v-model="recDescription"
                type="text"
                class="w-full px-3 py-2 bg-bg-base border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
                placeholder="What is this for?"
              />
            </div>

            <div class="grid grid-cols-2 gap-4">
              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Duration</label>
                <AppNumberInput
                  v-model="recDurationMinutes"
                  :min="1"
                  :max="480"
                  :step="5"
                  :precision="0"
                  suffix="min"
                />
              </div>
              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Time of Day</label>
                <AppTimePicker
                  v-model:hour="recTimeHour"
                  v-model:minute="recTimeMinute"
                />
              </div>
            </div>

            <div class="grid grid-cols-2 gap-4">
              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Schedule</label>
                <AppSelect
                  v-model="recPattern"
                  :options="recurrencePatterns"
                  label-key="label"
                  value-key="value"
                />
              </div>
              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Mode</label>
                <AppSelect
                  v-model="recMode"
                  :options="recurringModes"
                  label-key="label"
                  value-key="value"
                />
              </div>
            </div>

            <!-- Weekly day picker -->
            <div v-if="recPattern === 'weekly'" class="flex gap-2">
              <button
                v-for="day in weekDays"
                :key="day.value"
                @click="toggleWeekDay(day.value)"
                :class="[
                  'px-2 py-1 text-[0.75rem] rounded border transition-colors',
                  recWeeklyDays.includes(day.value)
                    ? 'bg-accent text-bg-base border-accent'
                    : 'border-border-subtle text-text-secondary hover:bg-bg-elevated'
                ]"
              >
                {{ day.label }}
              </button>
            </div>

            <!-- Monthly day picker -->
            <div v-if="recPattern === 'monthly'">
              <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Day of month</label>
              <AppNumberInput
                v-model="recMonthlyDay"
                :min="1"
                :max="31"
                :step="1"
                :precision="0"
              />
            </div>

            <div class="flex justify-end gap-2">
              <button
                @click="showRecurringForm = false; editingRecurring = null"
                class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors"
              >
                Cancel
              </button>
              <button
                @click="saveRecurringEntry"
                :disabled="!recProjectId"
                class="px-3 py-1.5 text-[0.75rem] bg-accent text-bg-base rounded-lg hover:bg-accent-hover transition-colors disabled:opacity-40"
              >
                {{ editingRecurring ? 'Update' : 'Add' }}
              </button>
            </div>
          </div>
          <!-- Divider -->
          <div class="border-t border-border-subtle" />

          <!-- Entry Templates -->
          <h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Entry Templates</h3>

          <div v-if="entryTemplatesStore.templates.length > 0" class="space-y-2">
            <div
              v-for="tpl in entryTemplatesStore.templates"
              :key="tpl.id"
              class="flex items-center justify-between py-2 px-3 bg-bg-inset rounded-lg"
            >
              <div class="flex-1 min-w-0">
                <p class="text-[0.8125rem] text-text-primary truncate">{{ tpl.name }}</p>
                <p class="text-[0.6875rem] text-text-tertiary">
                  {{ getTemplateProjectName(tpl.project_id) }}
                  <span v-if="tpl.duration"> - {{ formatTemplateDuration(tpl.duration) }}</span>
                </p>
              </div>
              <button
                @click="deleteEntryTemplate(tpl.id!)"
                class="text-text-tertiary hover:text-status-error transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
                :aria-label="'Delete template ' + tpl.name"
              >
                <Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
              </button>
            </div>
          </div>
          <p v-else class="text-[0.75rem] text-text-tertiary">No saved templates. Use "Save as Template" in the Entries view.</p>
        </div>
      </div>

      <!-- Billing -->
      <div v-if="activeTab === 'billing'" id="tabpanel-billing" role="tabpanel" aria-labelledby="tab-billing" tabindex="0">
        <h2 class="text-[1.25rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-6">Billing</h2>

        <div class="space-y-4">
          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Default Hourly Rate</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Applied to new projects</p>
            </div>
            <AppNumberInput
              v-model="hourlyRate"
              :min="0"
              :step="1"
              :precision="2"
              :prefix="getCurrencySymbol()"
              @update:model-value="saveSettings"
            />
          </div>

          <div class="border-t border-border-subtle" />

          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Time Rounding</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Round time entries for billing</p>
            </div>
            <button
              @click="toggleRounding"
              role="switch"
              :aria-checked="roundingEnabled"
              aria-label="Time rounding"
              :class="[
                'relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150',
                roundingEnabled ? 'bg-status-running' : 'bg-bg-elevated'
              ]"
            >
              <span
                :class="[
                  'inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
                  roundingEnabled ? 'translate-x-[18px]' : 'translate-x-[3px]'
                ]"
              />
            </button>
          </div>

          <div v-if="roundingEnabled" class="space-y-4 pl-4 border-l-2 border-border-subtle ml-1">
            <div class="flex items-center justify-between">
              <div>
                <p class="text-[0.8125rem] text-text-primary">Increment</p>
                <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Round to nearest increment</p>
              </div>
              <div class="w-48">
                <AppSelect
                  v-model="roundingIncrement"
                  :options="roundingIncrements"
                  label-key="label"
                  value-key="value"
                  @update:model-value="saveRoundingSettings"
                />
              </div>
            </div>
            <div class="flex items-center justify-between">
              <div>
                <p class="text-[0.8125rem] text-text-primary">Method</p>
                <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Rounding direction</p>
              </div>
              <div class="w-48">
                <AppSelect
                  v-model="roundingMethod"
                  :options="roundingMethods"
                  label-key="label"
                  value-key="value"
                  @update:model-value="saveRoundingSettings"
                />
              </div>
            </div>
          </div>

          <div class="border-t border-border-subtle" />

          <!-- Business Identity -->
          <h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Business Identity</h3>
          <p class="text-[0.6875rem] text-text-tertiary">This information appears on your invoices.</p>

          <div class="space-y-4 max-w-md">
          <div>
            <label for="biz-name" class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Business Name</label>
            <input
              v-model="businessName"
              type="text"
              id="biz-name"
              class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
              placeholder="Your Company Name"
              @change="saveBusinessSettings"
            />
          </div>
          <div>
            <label for="biz-address" class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Address</label>
            <textarea
              v-model="businessAddress"
              id="biz-address"
              rows="3"
              class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible resize-none"
              placeholder="123 Business St&#10;City, State ZIP"
              @change="saveBusinessSettings"
            ></textarea>
          </div>
          <div class="grid grid-cols-2 gap-4">
            <div>
              <label for="biz-email" class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Email</label>
              <input
                v-model="businessEmail"
                type="email"
                id="biz-email"
                class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
                placeholder="billing@company.com"
                @change="saveBusinessSettings"
              />
            </div>
            <div>
              <label for="biz-phone" class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Phone</label>
              <input
                v-model="businessPhone"
                type="tel"
                id="biz-phone"
                class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
                placeholder="(555) 123-4567"
                @change="saveBusinessSettings"
              />
            </div>
          </div>
          <div>
            <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Logo</label>
            <div class="flex items-center gap-4">
              <div
                v-if="businessLogo"
                class="w-20 h-12 border border-border-subtle rounded flex items-center justify-center overflow-hidden bg-white"
              >
                <img :src="businessLogo" alt="Business logo preview" class="max-w-full max-h-full object-contain" />
              </div>
              <div class="flex gap-2">
                <button
                  @click="uploadLogo"
                  class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors"
                >
                  {{ businessLogo ? 'Change' : 'Upload' }}
                </button>
                <button
                  v-if="businessLogo"
                  @click="removeLogo"
                  class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-status-error-text rounded-lg hover:bg-status-error/10 transition-colors"
                >
                  Remove
                </button>
              </div>
            </div>
            <p class="text-[0.625rem] text-text-tertiary mt-1">PNG or JPG, max 200x80px. Appears on invoice header.</p>
          </div>
          </div>
        </div>
      </div>

      <!-- Calendar -->
      <div v-if="activeTab === 'calendar'" id="tabpanel-calendar" role="tabpanel" aria-labelledby="tab-calendar" tabindex="0">
        <h2 class="text-[1.25rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-6">Calendar</h2>

        <p class="text-[0.6875rem] text-text-tertiary mb-4">Import ICS calendar files or subscribe to URLs to see external events on your calendar view.</p>

        <!-- Source list -->
        <div v-if="calendarSources.length > 0" class="space-y-2 mb-5">
          <div
            v-for="source in calendarSources"
            :key="source.id"
            class="flex items-center justify-between py-2.5 px-3 bg-bg-inset rounded-lg"
          >
            <div class="flex items-center gap-3 flex-1 min-w-0">
              <component
                :is="source.source_type === 'url' ? Globe : FileText"
                class="w-4 h-4 shrink-0 text-text-tertiary"
                :stroke-width="1.5"
                aria-hidden="true"
              />
              <div class="min-w-0">
                <p class="text-[0.8125rem] text-text-primary truncate">{{ source.name }}</p>
                <p class="text-[0.6875rem] text-text-tertiary truncate">
                  {{ source.source_type === 'url' ? source.url : 'Local file' }}
                  <span v-if="source.last_synced"> - synced {{ formatSyncedDate(source.last_synced) }}</span>
                </p>
              </div>
            </div>
            <div class="flex items-center gap-2 ml-3">
              <button
                v-if="source.source_type === 'url'"
                @click="syncCalendarSource(source)"
                :disabled="syncingSourceId === source.id"
                class="p-1.5 text-text-tertiary hover:text-text-primary transition-colors disabled:opacity-40"
                :aria-label="'Sync ' + source.name"
              >
                <RefreshCw class="w-3.5 h-3.5" :class="syncingSourceId === source.id ? 'animate-spin' : ''" />
              </button>
              <button
                @click="toggleCalendarSourceEnabled(source)"
                role="switch"
                :aria-checked="!!source.enabled"
                :aria-label="'Toggle ' + source.name"
                :class="[
                  'relative inline-flex h-4 w-7 items-center rounded-full transition-colors duration-150',
                  source.enabled ? 'bg-status-running' : 'bg-bg-elevated'
                ]"
              >
                <span
                  :class="[
                    'inline-block h-3 w-3 transform rounded-full bg-text-primary transition-transform duration-150',
                    source.enabled ? 'translate-x-[13px]' : 'translate-x-[2px]'
                  ]"
                />
              </button>
              <button
                @click="deleteCalendarSource(source.id!)"
                class="text-text-tertiary hover:text-status-error transition-colors"
                :aria-label="'Delete ' + source.name"
              >
                <Trash2 class="w-3.5 h-3.5" />
              </button>
            </div>
          </div>
        </div>

        <div v-else class="py-6 text-center text-[0.75rem] text-text-tertiary mb-5">
          No calendar sources added yet.
        </div>

        <!-- Add Source Form -->
        <div v-if="!showCalendarForm">
          <button
            @click="showCalendarForm = true; resetCalendarForm()"
            class="flex items-center gap-1.5 text-[0.8125rem] text-accent-text hover:text-accent transition-colors"
          >
            <Plus class="w-3.5 h-3.5" />
            Add source
          </button>
        </div>

        <div v-if="showCalendarForm" class="space-y-4 p-4 bg-bg-inset rounded-lg border border-border-subtle">
          <div>
            <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Name</label>
            <input
              v-model="calSourceName"
              type="text"
              class="w-full px-3 py-2 bg-bg-base border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
              placeholder="My Calendar"
            />
          </div>

          <div>
            <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Type</label>
            <div class="flex gap-2">
              <button
                @click="calSourceType = 'file'"
                :class="[
                  'px-3 py-1.5 text-[0.75rem] rounded-lg border transition-colors',
                  calSourceType === 'file'
                    ? 'bg-accent text-bg-base border-accent'
                    : 'border-border-subtle text-text-secondary hover:bg-bg-elevated'
                ]"
              >
                File
              </button>
              <button
                @click="calSourceType = 'url'"
                :class="[
                  'px-3 py-1.5 text-[0.75rem] rounded-lg border transition-colors',
                  calSourceType === 'url'
                    ? 'bg-accent text-bg-base border-accent'
                    : 'border-border-subtle text-text-secondary hover:bg-bg-elevated'
                ]"
              >
                URL
              </button>
            </div>
          </div>

          <div v-if="calSourceType === 'url'">
            <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">URL</label>
            <input
              v-model="calSourceUrl"
              type="url"
              class="w-full px-3 py-2 bg-bg-base border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
              placeholder="https://calendar.example.com/feed.ics"
            />
          </div>

          <div v-if="calSourceType === 'url'">
            <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Sync Interval</label>
            <div class="w-48">
              <AppSelect
                v-model="calSyncInterval"
                :options="calSyncIntervalOptions"
                label-key="label"
                value-key="value"
              />
            </div>
          </div>

          <div v-if="calSourceType === 'file'">
            <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">ICS File</label>
            <div class="flex items-center gap-3">
              <button
                @click="pickCalendarFile"
                class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors"
              >
                Choose File
              </button>
              <span v-if="calSelectedFileName" class="text-[0.75rem] text-text-secondary truncate">{{ calSelectedFileName }}</span>
            </div>
          </div>

          <div class="flex justify-end gap-2">
            <button
              @click="showCalendarForm = false"
              class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors"
            >
              Cancel
            </button>
            <button
              @click="saveCalendarSource"
              :disabled="!calSourceName || (calSourceType === 'url' && !calSourceUrl) || (calSourceType === 'file' && !calFileContent)"
              class="px-3 py-1.5 text-[0.75rem] bg-accent text-bg-base rounded-lg hover:bg-accent-hover transition-colors disabled:opacity-40"
            >
              Add
            </button>
          </div>
        </div>
      </div>

      <!-- Data -->
      <div v-if="activeTab === 'data'" id="tabpanel-data" role="tabpanel" aria-labelledby="tab-data" tabindex="0">
        <h2 class="text-[1.25rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-6">Data</h2>

        <div class="space-y-4">
          <!-- Export -->
          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Export All Data</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Download as JSON backup</p>
            </div>
            <button
              @click="exportData"
              class="px-4 py-1.5 border border-border-visible text-text-primary text-[0.8125rem] rounded-lg hover:bg-bg-elevated transition-colors duration-150"
            >
              Export
            </button>
          </div>

          <!-- Last exported -->
          <div v-if="lastExported" class="flex items-center justify-between" role="status">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Last exported</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">{{ lastExportedFormatted }}</p>
            </div>
          </div>

          <div class="border-t border-border-subtle" />

          <!-- Auto-backup on close -->
          <div class="flex items-center justify-between">
            <div>
              <p class="text-[0.8125rem] text-text-primary">Auto-backup on close</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Save a backup when the app closes</p>
            </div>
            <button @click="toggleAutoBackup" role="switch" :aria-checked="autoBackupEnabled" aria-label="Auto-backup on close"
              class="relative inline-flex h-5 w-9 items-center rounded-full transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
              :class="autoBackupEnabled ? 'bg-status-running' : 'bg-bg-elevated'">
              <span :class="['inline-block h-3.5 w-3.5 transform rounded-full bg-text-primary transition-transform duration-150',
                autoBackupEnabled ? 'translate-x-[18px]' : 'translate-x-[3px]']" />
            </button>
          </div>

          <!-- Backup path -->
          <div v-if="autoBackupEnabled" class="flex items-center justify-between pl-4 border-l-2 border-border-subtle ml-1">
            <div class="flex-1 min-w-0">
              <p class="text-[0.8125rem] text-text-primary">Backup directory</p>
              <p class="text-[0.6875rem] text-text-tertiary mt-0.5 truncate">{{ backupPath || 'Not set' }}</p>
            </div>
            <button @click="chooseBackupPath"
              class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
              aria-label="Choose backup directory">
              {{ backupPath ? 'Change' : 'Choose' }}
            </button>
          </div>

          <!-- Backup frequency and retention -->
          <div v-if="autoBackupEnabled" class="pl-4 border-l-2 border-border-subtle ml-1 space-y-3">
            <div class="flex items-center justify-between">
              <div>
                <p class="text-[0.8125rem] text-text-primary">Frequency</p>
                <p class="text-[0.6875rem] text-text-tertiary mt-0.5">How often to run backups</p>
              </div>
              <div class="w-36">
                <AppSelect
                  v-model="backupFrequency"
                  :options="[{id:'daily',name:'Daily'},{id:'weekly',name:'Weekly'}]"
                  label-key="name"
                  value-key="id"
                  @update:model-value="saveBackupFrequency"
                />
              </div>
            </div>
            <div class="flex items-center justify-between">
              <div>
                <p class="text-[0.8125rem] text-text-primary">Retention</p>
                <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Keep last N backups</p>
              </div>
              <div class="w-28">
                <AppNumberInput v-model="backupRetention" :min="1" :max="30" @update:model-value="saveBackupRetention" />
              </div>
            </div>
          </div>

          <div class="border-t border-border-subtle" />

          <!-- Import Data -->
          <div>
            <div class="flex items-center justify-between mb-4">
              <h3 class="text-[0.8125rem] font-medium text-text-primary">Import Data</h3>
              <button @click="showJsonImportWizard = true" class="px-3 py-1.5 text-[0.8125rem] border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors">
                Restore from Backup
              </button>
            </div>

            <div class="flex items-end gap-3 mb-4">
              <div class="w-48">
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Format</label>
                <AppSelect
                  v-model="importFormat"
                  :options="importFormats"
                  label-key="label"
                  value-key="value"
                />
              </div>
              <button
                @click="handleImportFile"
                class="flex items-center gap-2 px-4 py-2 border border-border-visible text-text-primary text-xs rounded-lg hover:bg-bg-elevated transition-colors duration-150"
              >
                <Upload class="w-3.5 h-3.5" aria-hidden="true" />
                Choose File
              </button>
            </div>

            <div v-if="importFileName" class="mb-4">
              <p class="text-[0.75rem] text-text-secondary mb-2">File: {{ importFileName }}</p>

              <!-- CSV Preview -->
              <div v-if="importPreview.length > 0" class="overflow-x-auto mb-3">
                <table class="text-[0.6875rem]">
                  <thead v-if="importPreview.length > 0">
                    <tr class="text-text-tertiary font-medium">
                      <th v-for="(cell, j) in importPreview[0]" :key="j" class="pr-4 py-0.5 whitespace-nowrap text-left font-medium">{{ cell }}</th>
                    </tr>
                  </thead>
                  <tbody>
                    <tr v-for="(row, i) in importPreview.slice(1)" :key="i" class="text-text-secondary">
                      <td v-for="(cell, j) in row" :key="j" class="pr-4 py-0.5 whitespace-nowrap">{{ cell }}</td>
                    </tr>
                  </tbody>
                </table>
              </div>

              <!-- Date format detection -->
              <div v-if="importFormat !== 'json' && importFormat !== 'generic'" class="flex items-center gap-3 mb-3">
                <template v-if="importDateDetected !== 'ambiguous'">
                  <span class="text-[0.6875rem] text-text-tertiary">
                    Date format detected: {{ importDateFormat === 'DMY' ? 'DD/MM/YYYY' : 'MM/DD/YYYY' }}
                  </span>
                  <button
                    @click="importDateDetected = 'ambiguous'"
                    class="text-[0.6875rem] text-accent hover:text-accent-hover transition-colors"
                  >
                    Change
                  </button>
                </template>
                <template v-else>
                  <label class="text-[0.6875rem] text-text-tertiary">Date format:</label>
                  <div class="w-36">
                    <AppSelect
                      v-model="importDateFormat"
                      :options="dateFormatOptions"
                      label-key="label"
                      value-key="value"
                    />
                  </div>
                </template>
              </div>

              <button
                @click="executeImport"
                :disabled="isImporting"
                class="px-4 py-2 bg-accent text-bg-base text-xs font-medium rounded-lg hover:bg-accent-hover transition-colors duration-150 disabled:opacity-40"
              >
                {{ isImporting ? 'Importing...' : 'Import' }}
              </button>
            </div>

            <p v-if="importStatus" class="text-[0.75rem] mt-2" :class="importStatus.startsWith('Error') ? 'text-status-error-text' : 'text-status-running'">
              {{ importStatus }}
            </p>
          </div>

          <!-- Danger Zone -->
          <div class="rounded-xl border border-status-error/20 p-5">
            <h3 class="text-xs text-status-error-text uppercase tracking-[0.08em] font-medium mb-4">Danger Zone</h3>
            <div class="space-y-4">
              <div class="flex items-center justify-between">
                <div>
                  <p class="text-[0.8125rem] text-text-primary">Clear All Data</p>
                  <p class="text-[0.6875rem] text-text-tertiary mt-0.5">Permanently delete all entries, projects, clients, and invoices</p>
                </div>
                <button
                  @click="showClearDataDialog = true"
                  class="px-4 py-1.5 border border-status-error text-status-error-text text-[0.8125rem] font-medium rounded-lg hover:bg-status-error/10 transition-colors duration-150"
                >
                  Clear Data
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>
      </div>
      </Transition>
      </div>
    </div>

    <!-- Clear Data Confirmation Dialog -->
    <Transition name="modal">
    <div
      v-if="showClearDataDialog"
      class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center p-4 z-50"
      @click.self="showClearDataDialog = false"
    >
      <div ref="clearDialogRef" role="alertdialog" aria-modal="true" aria-labelledby="clear-data-title" aria-describedby="clear-data-desc" class="bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-sm p-6">
        <h2 id="clear-data-title" class="text-[1.125rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-2">Clear All Data</h2>
        <p id="clear-data-desc" class="text-[0.75rem] text-text-secondary mb-4">
          Are you sure? This action cannot be undone.
        </p>
        <p class="text-[0.6875rem] text-status-error-text mb-6">
          All time entries, projects, clients, and invoices will be permanently deleted.
        </p>
        <div class="flex justify-end gap-3">
          <button
            @click="showClearDataDialog = false"
            class="px-4 py-2 border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors duration-150"
          >
            Cancel
          </button>
          <button
            @click="clearAllData"
            class="px-4 py-2 bg-status-error text-white font-medium rounded-lg hover:bg-status-error/80 transition-colors duration-150"
          >
            Delete Everything
          </button>
        </div>
      </div>
    </div>
    </Transition>


    <JsonImportWizard
      :show="showJsonImportWizard"
      @close="showJsonImportWizard = false"
      @imported="showJsonImportWizard = false"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, onMounted, markRaw } from 'vue'
import { invoke } from '@tauri-apps/api/core'
import { Settings as SettingsIcon, Clock, Receipt, Database, Calendar as CalendarIcon, Plus, Minus, Upload, Pencil, Trash2, RefreshCw, Globe, FileText } from 'lucide-vue-next'
import { useSettingsStore } from '../stores/settings'
import { useToastStore } from '../stores/toast'
import { useOnboardingStore } from '../stores/onboarding'
import { useRecurringStore } from '../stores/recurring'
import type { RecurringEntry } from '../stores/recurring'
import { useEntryTemplatesStore } from '../stores/entryTemplates'
import AppNumberInput from '../components/AppNumberInput.vue'
import AppSelect from '../components/AppSelect.vue'
import AppShortcutRecorder from '../components/AppShortcutRecorder.vue'
import AppTimePicker from '../components/AppTimePicker.vue'
import { LOCALES, getCurrencies, getCurrencySymbol } from '../utils/locale'
import { parseCSV, mapTogglCSV, mapClockifyCSV, mapHarvestCSV, mapGenericCSV, detectDateFormat, findCol, type ImportEntry, type DateFormat, type DateDetectResult } from '../utils/import'
import { TIMER_FONTS, loadGoogleFont, loadAndApplyTimerFont } from '../utils/fonts'
import { UI_FONTS, loadUIFont } from '../utils/uiFonts'
import { useFocusTrap } from '../utils/focusTrap'
import JsonImportWizard from '../components/JsonImportWizard.vue'
import { audioEngine, SOUND_EVENTS, DEFAULT_EVENTS } from '../utils/audio'
import type { SoundEvent } from '../utils/audio'
import { resetPositionCache } from '../utils/dropdown'

const settingsStore = useSettingsStore()
const toastStore = useToastStore()
const recurringStore = useRecurringStore()
const entryTemplatesStore = useEntryTemplatesStore()
const onboardingStore = useOnboardingStore()
const recurringEntries = computed(() => recurringStore.entries)

// Recurring entry form state
const showRecurringForm = ref(false)
const editingRecurring = ref<RecurringEntry | null>(null)
const recProjectId = ref<number | null>(null)
const recTaskId = ref<number | null>(null)
const recDescription = ref('')
const recDurationMinutes = ref(60)
const recPattern = ref('daily')
const recWeeklyDays = ref<string[]>([])
const recMonthlyDay = ref(1)
const recTimeOfDay = ref('09:00')
const recTimeHour = ref(9)
const recTimeMinute = ref(0)
const recMode = ref('prompt')
const recProjects = ref<Array<{ id: number; name: string; color: string }>>([])
const recTasks = ref<Array<{ id: number; name: string }>>([])

const weekDays = [
  { value: 'mon', label: 'Mon' },
  { value: 'tue', label: 'Tue' },
  { value: 'wed', label: 'Wed' },
  { value: 'thu', label: 'Thu' },
  { value: 'fri', label: 'Fri' },
  { value: 'sat', label: 'Sat' },
  { value: 'sun', label: 'Sun' },
]

const recurrencePatterns = [
  { value: 'daily', label: 'Daily' },
  { value: 'weekdays', label: 'Weekdays (Mon-Fri)' },
  { value: 'weekly', label: 'Weekly' },
  { value: 'monthly', label: 'Monthly' },
]

const recurringModes = [
  { value: 'auto_create', label: 'Auto-create entry' },
  { value: 'prompt', label: 'Show notification' },
  { value: 'prefill_timer', label: 'Pre-fill timer' },
]

// Tabs
const tabs = [
  { id: 'general', label: 'General', icon: markRaw(SettingsIcon) },
  { id: 'timer', label: 'Timer', icon: markRaw(Clock) },
  { id: 'billing', label: 'Billing', icon: markRaw(Receipt) },
  { id: 'calendar', label: 'Calendar', icon: markRaw(CalendarIcon) },
  { id: 'data', label: 'Data', icon: markRaw(Database) },
]

const activeTab = ref('general')

function onTabKeydown(e: KeyboardEvent) {
  const tabIds = tabs.map(t => t.id)
  const current = tabIds.indexOf(activeTab.value)
  let next = current
  if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
    e.preventDefault()
    next = (current + 1) % tabIds.length
  } else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
    e.preventDefault()
    next = (current - 1 + tabIds.length) % tabIds.length
  } else if (e.key === 'Home') {
    e.preventDefault()
    next = 0
  } else if (e.key === 'End') {
    e.preventDefault()
    next = tabIds.length - 1
  } else {
    return
  }
  activeTab.value = tabIds[next]
  document.getElementById(`tab-${tabIds[next]}`)?.focus()
}

// Settings state
const hourlyRate = ref(0)
const idleDetection = ref(true)
const idleTimeout = ref(5)
const reminderInterval = ref(15)
const appTrackingMode = ref('auto')
const appCheckInterval = ref(5)
const zoomLevel = ref(100)
const locale = ref('system')
const currency = ref('USD')
const currencyOptions = getCurrencies()
const themeMode = ref('dark')
const accentColor = ref('amber')
const shortcutToggleTimer = ref('CmdOrCtrl+Shift+T')
const shortcutShowApp = ref('CmdOrCtrl+Shift+Z')
const shortcutQuickEntry = ref('CmdOrCtrl+Shift+N')
const timelineRecording = ref(false)
const platform = ref('')
const timerFont = ref('JetBrains Mono')
const timerFontOptions = TIMER_FONTS
const reduceMotion = ref('system')
const dyslexiaMode = ref(false)
const uiFont = ref('Inter')

// Tray behavior settings
const closeToTray = ref(false)
const minimizeToTray = ref(false)

// Notification settings
const persistentNotifications = ref(false)

// Sound settings
const soundEnabled = ref(false)
const soundMode = ref('synthesized')
const soundVolume = ref(70)
const soundEventsMap = ref<Record<SoundEvent, boolean>>({ ...DEFAULT_EVENTS })
const soundEventsList = SOUND_EVENTS

const soundModes = [
  { value: 'synthesized', label: 'Synthesized' },
  { value: 'system', label: 'System' },
  { value: 'custom', label: 'Custom' },
]

// Goal settings
const dailyGoalHours = ref(8)
const weeklyGoalHours = ref(40)

// Reminder settings
const eodReminderEnabled = ref(false)
const eodReminderTime = ref('17:00')
const weeklySummaryEnabled = ref(false)

async function toggleEodReminder() {
  eodReminderEnabled.value = !eodReminderEnabled.value
  await saveReminderSettings()
}

async function toggleWeeklySummary() {
  weeklySummaryEnabled.value = !weeklySummaryEnabled.value
  await saveReminderSettings()
}

async function saveReminderSettings() {
  await settingsStore.updateSetting('eod_reminder_enabled', eodReminderEnabled.value ? 'true' : 'false')
  await settingsStore.updateSetting('eod_reminder_time', eodReminderTime.value)
  await settingsStore.updateSetting('weekly_summary_enabled', weeklySummaryEnabled.value ? 'true' : 'false')
}

// Rounding settings
const roundingEnabled = ref(false)
const roundingIncrement = ref(15)
const roundingMethod = ref('nearest')

// Business identity settings
const businessName = ref('')
const businessAddress = ref('')
const businessEmail = ref('')
const businessPhone = ref('')
const businessLogo = ref('')

const roundingIncrements = [
  { value: 1, label: '1 minute' },
  { value: 5, label: '5 minutes' },
  { value: 6, label: '6 minutes' },
  { value: 10, label: '10 minutes' },
  { value: 15, label: '15 minutes' },
  { value: 30, label: '30 minutes' },
  { value: 60, label: '1 hour' },
]

const roundingMethods = [
  { value: 'nearest', label: 'Nearest' },
  { value: 'up', label: 'Round up' },
  { value: 'down', label: 'Round down' },
]

const themeModes = [
  { value: 'dark', label: 'Dark' },
  { value: 'light', label: 'Light' },
  { value: 'system', label: 'System' },
]

const accentColors = [
  { value: 'amber', label: 'Amber', color: '#D97706' },
  { value: 'blue', label: 'Blue', color: '#3B82F6' },
  { value: 'purple', label: 'Purple', color: '#8B5CF6' },
  { value: 'green', label: 'Green', color: '#10B981' },
  { value: 'red', label: 'Red', color: '#EF4444' },
  { value: 'pink', label: 'Pink', color: '#EC4899' },
  { value: 'cyan', label: 'Cyan', color: '#06B6D4' },
]

const reduceMotionOptions = [
  { label: 'System (default)', value: 'system' },
  { label: 'On', value: 'on' },
  { label: 'Off', value: 'off' },
]

const appTrackingModes = [
  { value: 'auto', label: 'Auto-pause/resume' },
  { value: 'notify', label: 'Notify + auto-resume' },
  { value: 'prompt', label: 'Prompt user' },
]

// Calendar source types
interface CalendarSource {
  id?: number
  name: string
  source_type: string
  url?: string
  last_synced?: string
  sync_interval?: number
  enabled: number
}

// Calendar source state
const calendarSources = ref<CalendarSource[]>([])
const showCalendarForm = ref(false)
const calSourceName = ref('')
const calSourceType = ref<'file' | 'url'>('file')
const calSourceUrl = ref('')
const calSyncInterval = ref(60)
const calFileContent = ref('')
const calSelectedFileName = ref('')
const syncingSourceId = ref<number | null>(null)

const calSyncIntervalOptions = [
  { value: 15, label: 'Every 15 minutes' },
  { value: 30, label: 'Every 30 minutes' },
  { value: 60, label: 'Every hour' },
  { value: 120, label: 'Every 2 hours' },
]

function resetCalendarForm() {
  calSourceName.value = ''
  calSourceType.value = 'file'
  calSourceUrl.value = ''
  calSyncInterval.value = 60
  calFileContent.value = ''
  calSelectedFileName.value = ''
}

function formatSyncedDate(dateStr: string): string {
  try {
    const d = new Date(dateStr)
    const now = new Date()
    const diffMs = now.getTime() - d.getTime()
    const diffMin = Math.floor(diffMs / 60000)
    if (diffMin < 1) return 'just now'
    if (diffMin < 60) return `${diffMin}m ago`
    const diffHours = Math.floor(diffMin / 60)
    if (diffHours < 24) return `${diffHours}h ago`
    const diffDays = Math.floor(diffHours / 24)
    return `${diffDays}d ago`
  } catch {
    return dateStr
  }
}

async function fetchCalendarSources() {
  try {
    calendarSources.value = await invoke<CalendarSource[]>('get_calendar_sources')
  } catch (e) {
    console.error('Failed to fetch calendar sources:', e)
  }
}

async function pickCalendarFile() {
  try {
    const { open: openFileDialog } = await import('@tauri-apps/plugin-dialog')
    const selected = await openFileDialog({
      filters: [{ name: 'ICS Calendar Files', extensions: ['ics'] }]
    })
    if (!selected) return

    const { readTextFile } = await import('@tauri-apps/plugin-fs')
    const content: string = await readTextFile(selected as string)
    calFileContent.value = content
    calSelectedFileName.value = (selected as string).split(/[/\\]/).pop() || 'file.ics'
  } catch (e) {
    console.error('Failed to read ICS file:', e)
    toastStore.error('Failed to read calendar file')
  }
}

async function saveCalendarSource() {
  if (!calSourceName.value) return

  try {
    const source: any = {
      name: calSourceName.value,
      sourceType: calSourceType.value,
      enabled: 1,
    }

    if (calSourceType.value === 'url') {
      source.url = calSourceUrl.value
      source.syncInterval = calSyncInterval.value
    }

    const sourceId = await invoke<number>('create_calendar_source', { source })

    // For file type, import the ICS content
    if (calSourceType.value === 'file' && calFileContent.value) {
      const count = await invoke<number>('import_ics_file', {
        sourceId,
        content: calFileContent.value
      })
      toastStore.success(`Imported ${count} events from ${calSelectedFileName.value}`)
    }

    showCalendarForm.value = false
    resetCalendarForm()
    await fetchCalendarSources()
  } catch (e) {
    console.error('Failed to create calendar source:', e)
    toastStore.error('Failed to add calendar source')
  }
}

async function syncCalendarSource(source: CalendarSource) {
  if (!source.id || !source.url) return
  syncingSourceId.value = source.id

  try {
    const resp = await fetch(source.url)
    if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
    const content = await resp.text()
    const count = await invoke<number>('import_ics_file', {
      sourceId: source.id,
      content
    })
    toastStore.success(`Synced ${count} events from ${source.name}`)
    await fetchCalendarSources()
  } catch (e) {
    console.error('Calendar sync failed for', source.name, e)
    toastStore.error(`Failed to sync ${source.name}`)
  } finally {
    syncingSourceId.value = null
  }
}

async function toggleCalendarSourceEnabled(source: CalendarSource) {
  if (!source.id) return
  try {
    const updated = { ...source, enabled: source.enabled ? 0 : 1, sourceType: source.source_type }
    await invoke('update_calendar_source', { source: updated })
    await fetchCalendarSources()
  } catch (e) {
    console.error('Failed to toggle calendar source:', e)
  }
}

async function deleteCalendarSource(id: number) {
  try {
    await invoke('delete_calendar_source', { id })
    await fetchCalendarSources()
    toastStore.success('Calendar source removed')
  } catch (e) {
    console.error('Failed to delete calendar source:', e)
    toastStore.error('Failed to remove calendar source')
  }
}

// Dialog state
const showClearDataDialog = ref(false)
const clearDialogRef = ref<HTMLElement | null>(null)
const { activate: activateClearTrap, deactivate: deactivateClearTrap } = useFocusTrap()

watch(showClearDataDialog, (val) => {
  if (val) {
    setTimeout(() => {
      if (clearDialogRef.value) activateClearTrap(clearDialogRef.value, { onDeactivate: () => { showClearDataDialog.value = false } })
    }, 50)
  } else {
    deactivateClearTrap()
  }
})

// JSON import wizard
const showJsonImportWizard = ref(false)

// Auto-backup state
const autoBackupEnabled = ref(false)
const backupPath = ref('')
const lastExported = ref('')
const backupFrequency = ref('daily')
const backupRetention = ref(7)

async function saveBackupFrequency(val: string) {
  await settingsStore.updateSetting('auto_backup_frequency', val)
}

async function saveBackupRetention(val: number) {
  await settingsStore.updateSetting('auto_backup_retention', String(val))
}

const lastExportedFormatted = computed(() => {
  if (!lastExported.value) return ''
  return new Date(lastExported.value).toLocaleString()
})

async function toggleAutoBackup() {
  autoBackupEnabled.value = !autoBackupEnabled.value
  await settingsStore.updateSetting('auto_backup', autoBackupEnabled.value ? 'true' : 'false')
}

async function chooseBackupPath() {
  try {
    const { open } = await import('@tauri-apps/plugin-dialog')
    const selected = await open({ directory: true, title: 'Choose backup directory' })
    if (selected && typeof selected === 'string') {
      backupPath.value = selected
      await settingsStore.updateSetting('backup_path', selected)
    }
  } catch {
    // User cancelled
  }
}

// Import state
const importFormat = ref('toggl')
const importFileContent = ref('')
const importFileName = ref('')
const importPreview = ref<string[][]>([])
const importStatus = ref('')
const isImporting = ref(false)
const importDateDetected = ref<DateDetectResult>('ambiguous')
const importDateFormat = ref<DateFormat>('MDY')

const dateFormatOptions = [
  { label: 'MM/DD/YYYY', value: 'MDY' },
  { label: 'DD/MM/YYYY', value: 'DMY' },
]

const importFormats = [
  { label: 'Toggl CSV', value: 'toggl' },
  { label: 'Clockify CSV', value: 'clockify' },
  { label: 'Harvest CSV', value: 'harvest' },
  { label: 'Generic CSV', value: 'generic' },
  { label: 'ZeroClock JSON', value: 'json' },
]

// Zoom steps
const zoomSteps = [80, 90, 100, 110, 120, 130, 150]

function increaseZoom() {
  const currentIndex = zoomSteps.indexOf(zoomLevel.value)
  if (currentIndex < zoomSteps.length - 1) {
    zoomLevel.value = zoomSteps[currentIndex + 1]
  } else if (currentIndex === -1) {
    const next = zoomSteps.find(s => s > zoomLevel.value)
    if (next) zoomLevel.value = next
  }
  applyZoom()
}

function decreaseZoom() {
  const currentIndex = zoomSteps.indexOf(zoomLevel.value)
  if (currentIndex > 0) {
    zoomLevel.value = zoomSteps[currentIndex - 1]
  } else if (currentIndex === -1) {
    const prev = [...zoomSteps].reverse().find(s => s < zoomLevel.value)
    if (prev) zoomLevel.value = prev
  }
  applyZoom()
}

function applyZoom() {
  const app = document.getElementById('app')
  if (app) {
    (app.style as any).zoom = `${zoomLevel.value}%`
  }
  resetPositionCache()
  settingsStore.updateSetting('ui_zoom', zoomLevel.value.toString())
}

// Toggle idle detection with auto-save
function toggleIdleDetection() {
  idleDetection.value = !idleDetection.value
  saveSettings()
}

// Toggle timeline recording
async function toggleTimelineRecording() {
  timelineRecording.value = !timelineRecording.value
  await settingsStore.updateSetting('timeline_recording', timelineRecording.value ? 'on' : 'off')
}

// Save settings (called on any change)
async function saveSettings() {
  try {
    await settingsStore.updateSetting('hourly_rate', hourlyRate.value.toString())
    await settingsStore.updateSetting('idle_detection', idleDetection.value.toString())
    await settingsStore.updateSetting('idle_timeout', idleTimeout.value.toString())
    await settingsStore.updateSetting('reminder_interval', reminderInterval.value.toString())
    await settingsStore.updateSetting('app_tracking_mode', appTrackingMode.value)
    await settingsStore.updateSetting('app_check_interval', appCheckInterval.value.toString())
  } catch (error) {
    console.error('Failed to save settings:', error)
    toastStore.error('Failed to save settings')
  }
}

// Save locale/currency settings
async function saveLocaleSettings() {
  await settingsStore.updateSetting('locale', locale.value)
  await settingsStore.updateSetting('currency', currency.value)
}

// Save theme settings
async function saveThemeSettings() {
  await settingsStore.updateSetting('theme_mode', themeMode.value)
  await settingsStore.updateSetting('accent_color', accentColor.value)
}

// Save reduce motion setting
async function saveReduceMotionSetting() {
  await settingsStore.updateSetting('reduce_motion', reduceMotion.value)
}

// Toggle dyslexia mode
async function toggleDyslexiaMode() {
  dyslexiaMode.value = !dyslexiaMode.value
  await settingsStore.updateSetting('dyslexia_mode', dyslexiaMode.value ? 'true' : 'false')
}

// Save UI font setting
async function saveUIFontSetting() {
  await settingsStore.updateSetting('ui_font', uiFont.value)
}

// Save shortcut settings
async function saveShortcutSettings() {
  await settingsStore.updateSetting('shortcut_toggle_timer', shortcutToggleTimer.value)
  await settingsStore.updateSetting('shortcut_show_app', shortcutShowApp.value)
  await settingsStore.updateSetting('shortcut_quick_entry', shortcutQuickEntry.value)
}

// Save timer font setting
async function saveTimerFontSetting() {
  await settingsStore.updateSetting('timer_font', timerFont.value)
  loadAndApplyTimerFont(timerFont.value)
}

// Toggle sound enabled
async function toggleSoundEnabled() {
  soundEnabled.value = !soundEnabled.value
  await settingsStore.updateSetting('sound_enabled', soundEnabled.value ? 'true' : 'false')
}

// Toggle persistent notifications
async function togglePersistentNotifications() {
  persistentNotifications.value = !persistentNotifications.value
  toastStore.setPersistentNotifications(persistentNotifications.value)
  await settingsStore.updateSetting('persistent_notifications', persistentNotifications.value ? 'true' : 'false')
}

// Toggle close to tray
async function toggleCloseToTray() {
  closeToTray.value = !closeToTray.value
  await settingsStore.updateSetting('close_to_tray', closeToTray.value ? 'true' : 'false')
}

// Toggle minimize to tray
async function toggleMinimizeToTray() {
  minimizeToTray.value = !minimizeToTray.value
  await settingsStore.updateSetting('minimize_to_tray', minimizeToTray.value ? 'true' : 'false')
}

// Save sound settings
async function saveSoundSettings() {
  await settingsStore.updateSetting('sound_mode', soundMode.value)
  await settingsStore.updateSetting('sound_volume', soundVolume.value.toString())
}

// Toggle individual sound event
async function toggleSoundEvent(key: SoundEvent) {
  soundEventsMap.value[key] = !soundEventsMap.value[key]
  await settingsStore.updateSetting('sound_events', JSON.stringify(soundEventsMap.value))
}

// Test a sound event in the settings UI
function testSound(key: SoundEvent) {
  audioEngine.playTest(key)
}

// Save goal settings
async function saveGoalSettings() {
  await settingsStore.updateSetting('daily_goal_hours', dailyGoalHours.value.toString())
  await settingsStore.updateSetting('weekly_goal_hours', weeklyGoalHours.value.toString())
}

// Sync time picker hour/minute with recTimeOfDay string
watch([recTimeHour, recTimeMinute], ([h, m]) => {
  recTimeOfDay.value = String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0')
})

// Watch recurring project to fetch tasks
watch(recProjectId, async (pid) => {
  if (pid) {
    recTasks.value = await invoke('get_tasks', { projectId: pid })
  } else {
    recTasks.value = []
  }
  recTaskId.value = null
})

// Recurring entry helpers
function getProjectName(id: number): string {
  return recProjects.value.find(p => p.id === id)?.name || 'Unknown'
}

function formatRecurrenceRule(rule: string): string {
  if (rule === 'daily') return 'Daily'
  if (rule === 'weekdays') return 'Weekdays'
  if (rule.startsWith('weekly:')) {
    const days = rule.replace('weekly:', '').split(',')
    return 'Weekly: ' + days.map(d => d.charAt(0).toUpperCase() + d.slice(1)).join(', ')
  }
  if (rule.startsWith('monthly:')) {
    return 'Monthly on day ' + rule.replace('monthly:', '')
  }
  return rule
}

function formatMode(mode: string): string {
  if (mode === 'auto_create') return 'Auto-create'
  if (mode === 'prompt') return 'Notify'
  if (mode === 'prefill_timer') return 'Pre-fill'
  return mode
}

function formatDuration(seconds: number): string {
  const h = Math.floor(seconds / 3600)
  const m = Math.floor((seconds % 3600) / 60)
  if (h > 0 && m > 0) return `${h}h ${m}m`
  if (h > 0) return `${h}h`
  return `${m}m`
}

// Entry template helpers
function getTemplateProjectName(projectId: number): string {
  return recProjects.value.find(p => p.id === projectId)?.name || 'Unknown'
}

function formatTemplateDuration(seconds: number): string {
  const h = Math.floor(seconds / 3600)
  const m = Math.floor((seconds % 3600) / 60)
  if (h > 0 && m > 0) return `${h}h ${m}m`
  if (h > 0) return `${h}h`
  if (m > 0) return `${m}m`
  return '0m'
}

async function deleteEntryTemplate(id: number) {
  await entryTemplatesStore.deleteTemplate(id)
}

function buildRecurrenceRule(): string {
  if (recPattern.value === 'weekly') {
    return 'weekly:' + recWeeklyDays.value.join(',')
  }
  if (recPattern.value === 'monthly') {
    return 'monthly:' + recMonthlyDay.value
  }
  return recPattern.value // 'daily' or 'weekdays'
}

function toggleWeekDay(day: string) {
  const idx = recWeeklyDays.value.indexOf(day)
  if (idx >= 0) {
    recWeeklyDays.value.splice(idx, 1)
  } else {
    recWeeklyDays.value.push(day)
  }
}

function resetRecurringForm() {
  recProjectId.value = null
  recTaskId.value = null
  recDescription.value = ''
  recDurationMinutes.value = 60
  recPattern.value = 'daily'
  recWeeklyDays.value = []
  recMonthlyDay.value = 1
  recTimeOfDay.value = '09:00'
  recTimeHour.value = 9
  recTimeMinute.value = 0
  recMode.value = 'prompt'
}

function editRecurringEntry(rec: RecurringEntry) {
  editingRecurring.value = rec
  recProjectId.value = rec.project_id
  recTaskId.value = rec.task_id || null
  recDescription.value = rec.description || ''
  recDurationMinutes.value = Math.round(rec.duration / 60)
  recTimeOfDay.value = rec.time_of_day
  const [h, m] = rec.time_of_day.split(':').map(Number)
  recTimeHour.value = h || 0
  recTimeMinute.value = m || 0
  recMode.value = rec.mode

  // Parse recurrence rule
  if (rec.recurrence_rule === 'daily' || rec.recurrence_rule === 'weekdays') {
    recPattern.value = rec.recurrence_rule
  } else if (rec.recurrence_rule.startsWith('weekly:')) {
    recPattern.value = 'weekly'
    recWeeklyDays.value = rec.recurrence_rule.replace('weekly:', '').split(',')
  } else if (rec.recurrence_rule.startsWith('monthly:')) {
    recPattern.value = 'monthly'
    recMonthlyDay.value = parseInt(rec.recurrence_rule.replace('monthly:', ''))
  }

  showRecurringForm.value = true
}

async function saveRecurringEntry() {
  if (!recProjectId.value) return
  const entry: RecurringEntry = {
    id: editingRecurring.value?.id,
    project_id: recProjectId.value,
    task_id: recTaskId.value || undefined,
    description: recDescription.value || undefined,
    duration: recDurationMinutes.value * 60,
    recurrence_rule: buildRecurrenceRule(),
    time_of_day: recTimeOfDay.value,
    mode: recMode.value as 'auto_create' | 'prompt' | 'prefill_timer',
    enabled: 1,
  }

  if (editingRecurring.value?.id) {
    await recurringStore.updateEntry(entry)
  } else {
    await recurringStore.createEntry(entry)
  }

  showRecurringForm.value = false
  editingRecurring.value = null
  resetRecurringForm()
}

async function deleteRecurringEntry(id: number) {
  await recurringStore.deleteEntry(id)
}

// Toggle rounding
function toggleRounding() {
  roundingEnabled.value = !roundingEnabled.value
  saveRoundingSettings()
}

// Save rounding settings
async function saveRoundingSettings() {
  await settingsStore.updateSetting('rounding_enabled', roundingEnabled.value.toString())
  await settingsStore.updateSetting('rounding_increment', roundingIncrement.value.toString())
  await settingsStore.updateSetting('rounding_method', roundingMethod.value)
}

// Save business identity settings
async function saveBusinessSettings() {
  await settingsStore.updateSetting('business_name', businessName.value)
  await settingsStore.updateSetting('business_address', businessAddress.value)
  await settingsStore.updateSetting('business_email', businessEmail.value)
  await settingsStore.updateSetting('business_phone', businessPhone.value)
}

async function uploadLogo() {
  try {
    const { open } = await import('@tauri-apps/plugin-dialog')
    const selected = await open({
      filters: [{ name: 'Images', extensions: ['png', 'jpg', 'jpeg'] }]
    })
    if (!selected) return

    const { readFile } = await import('@tauri-apps/plugin-fs')
    const bytes = await readFile(selected as string)
    const ext = (selected as string).toLowerCase().endsWith('.png') ? 'png' : 'jpeg'
    const base64 = btoa(String.fromCharCode(...new Uint8Array(bytes)))
    const dataUrl = `data:image/${ext};base64,${base64}`
    businessLogo.value = dataUrl
    await settingsStore.updateSetting('business_logo', dataUrl)
  } catch (e) {
    console.error('Failed to upload logo:', e)
    toastStore.error('Failed to upload logo')
  }
}

async function removeLogo() {
  businessLogo.value = ''
  await settingsStore.updateSetting('business_logo', '')
}

// Import file handling
async function handleImportFile() {
  try {
    const { open } = await import('@tauri-apps/plugin-dialog')
    const selected = await open({
      filters: [
        { name: 'Data Files', extensions: ['csv', 'json'] }
      ]
    })
    if (!selected) return

    const filePath = selected as string
    // @ts-ignore - plugin-fs types are available at runtime
    const { readTextFile } = await import('@tauri-apps/plugin-fs')
    const content: string = await readTextFile(filePath)
    importFileContent.value = content
    importFileName.value = filePath.split(/[/\\]/).pop() || 'file'

    if (importFormat.value === 'json') {
      importPreview.value = []
      importDateDetected.value = 'ambiguous'
      importDateFormat.value = 'MDY'
    } else {
      const allRows = parseCSV(content)
      importPreview.value = allRows.slice(0, 6)

      // Detect date format from CSV data
      const header = allRows[0]?.map(h => h.toLowerCase().trim()) || []
      let dateColIdx = -1
      if (importFormat.value === 'harvest') {
        dateColIdx = findCol(header, 'date')
      } else {
        dateColIdx = findCol(header, 'start date')
      }
      const detected = detectDateFormat(allRows, dateColIdx)
      importDateDetected.value = detected
      importDateFormat.value = detected === 'ambiguous' ? 'MDY' : detected
    }
  } catch (e) {
    console.error('Failed to read file:', e)
  }
}

async function executeImport() {
  if (!importFileContent.value) return
  isImporting.value = true
  importStatus.value = 'Importing...'

  try {
    if (importFormat.value === 'json') {
      const data = JSON.parse(importFileContent.value)
      await invoke('import_json_data', { data })
      importStatus.value = 'Import complete!'
    } else {
      const rows = parseCSV(importFileContent.value)
      let entries: ImportEntry[]

      const df = importDateFormat.value
      if (importFormat.value === 'toggl') {
        entries = mapTogglCSV(rows, df)
      } else if (importFormat.value === 'clockify') {
        entries = mapClockifyCSV(rows, df)
      } else if (importFormat.value === 'harvest') {
        entries = mapHarvestCSV(rows, df)
      } else {
        entries = mapGenericCSV(rows, { project: 0, description: 1, start_time: 2, duration: 3, client: -1, task: -1 })
      }

      await invoke('import_entries', { entries })
      importStatus.value = `Imported ${entries.length} entries!`
    }

    importFileContent.value = ''
    importFileName.value = ''
    importPreview.value = []
    importDateDetected.value = 'ambiguous'
    importDateFormat.value = 'MDY'
  } catch (e) {
    importStatus.value = `Error: ${e}`
  } finally {
    isImporting.value = false
  }
}

// Export all data
async function exportData() {
  try {
    const { save } = await import('@tauri-apps/plugin-dialog')
    const { writeTextFile } = await import('@tauri-apps/plugin-fs')

    const filePath = await save({
      defaultPath: `zeroclock-export-${new Date().toISOString().split('T')[0]}.json`,
      filters: [{ name: 'JSON', extensions: ['json'] }],
    })
    if (!filePath) return

    const data = await invoke('export_data')
    const json = JSON.stringify(data, null, 2)
    await writeTextFile(filePath, json)

    const now = new Date().toISOString()
    await settingsStore.updateSetting('last_exported', now)
    lastExported.value = now
    toastStore.success('Data exported successfully')
  } catch (error) {
    console.error('Failed to export data:', error)
    toastStore.error('Failed to export data')
  }
}

// Clear all data
async function clearAllData() {
  try {
    await invoke('clear_all_data')
    showClearDataDialog.value = false
    toastStore.success('All data has been cleared')
  } catch (error) {
    console.error('Failed to clear data:', error)
    toastStore.error('Failed to clear data')
  }
}

// Load settings on mount
onMounted(async () => {
  try { platform.value = await invoke('get_platform') } catch { /* non-critical */ }
  await settingsStore.fetchSettings()

  hourlyRate.value = parseFloat(settingsStore.settings.hourly_rate) || 0
  idleDetection.value = settingsStore.settings.idle_detection !== 'false'
  idleTimeout.value = parseInt(settingsStore.settings.idle_timeout) || 5
  reminderInterval.value = parseInt(settingsStore.settings.reminder_interval) || 15
  appTrackingMode.value = settingsStore.settings.app_tracking_mode || 'auto'
  appCheckInterval.value = parseInt(settingsStore.settings.app_check_interval) || 5
  zoomLevel.value = parseInt(settingsStore.settings.ui_zoom) || 100
  locale.value = settingsStore.settings.locale || 'system'
  currency.value = settingsStore.settings.currency || 'USD'
  themeMode.value = settingsStore.settings.theme_mode || 'dark'
  accentColor.value = settingsStore.settings.accent_color || 'amber'
  shortcutToggleTimer.value = settingsStore.settings.shortcut_toggle_timer || 'CmdOrCtrl+Shift+T'
  shortcutShowApp.value = settingsStore.settings.shortcut_show_app || 'CmdOrCtrl+Shift+Z'
  shortcutQuickEntry.value = settingsStore.settings.shortcut_quick_entry || 'CmdOrCtrl+Shift+N'
  dailyGoalHours.value = parseFloat(settingsStore.settings.daily_goal_hours) || 8
  weeklyGoalHours.value = parseFloat(settingsStore.settings.weekly_goal_hours) || 40
  eodReminderEnabled.value = settingsStore.settings.eod_reminder_enabled === 'true'
  eodReminderTime.value = settingsStore.settings.eod_reminder_time || '17:00'
  weeklySummaryEnabled.value = settingsStore.settings.weekly_summary_enabled === 'true'
  roundingEnabled.value = settingsStore.settings.rounding_enabled === 'true'
  roundingIncrement.value = parseInt(settingsStore.settings.rounding_increment) || 15
  roundingMethod.value = settingsStore.settings.rounding_method || 'nearest'
  businessName.value = settingsStore.settings.business_name || ''
  businessAddress.value = settingsStore.settings.business_address || ''
  businessEmail.value = settingsStore.settings.business_email || ''
  businessPhone.value = settingsStore.settings.business_phone || ''
  businessLogo.value = settingsStore.settings.business_logo || ''
  timelineRecording.value = platform.value !== 'linux' && settingsStore.settings.timeline_recording === 'on'
  timerFont.value = settingsStore.settings.timer_font || 'JetBrains Mono'
  reduceMotion.value = settingsStore.settings.reduce_motion || 'system'
  dyslexiaMode.value = settingsStore.settings.dyslexia_mode === 'true'
  uiFont.value = settingsStore.settings.ui_font || 'Inter'

  // Tray behavior settings
  closeToTray.value = settingsStore.settings.close_to_tray === 'true'
  minimizeToTray.value = settingsStore.settings.minimize_to_tray === 'true'

  // Notification settings
  persistentNotifications.value = settingsStore.settings.persistent_notifications === 'true'

  // Auto-backup settings
  autoBackupEnabled.value = settingsStore.settings.auto_backup === 'true'
  backupPath.value = settingsStore.settings.backup_path || ''
  lastExported.value = settingsStore.settings.last_exported || ''
  backupFrequency.value = settingsStore.settings.auto_backup_frequency || 'daily'
  backupRetention.value = parseInt(settingsStore.settings.auto_backup_retention || '7')

  // Sound settings
  soundEnabled.value = settingsStore.settings.sound_enabled === 'true'
  soundMode.value = settingsStore.settings.sound_mode || 'synthesized'
  soundVolume.value = parseInt(settingsStore.settings.sound_volume) || 70
  try {
    const parsed = JSON.parse(settingsStore.settings.sound_events || '{}')
    soundEventsMap.value = { ...DEFAULT_EVENTS, ...parsed }
  } catch { /* use defaults */ }

  // Preload all timer fonts for preview in dropdown
  TIMER_FONTS.forEach(f => loadGoogleFont(f.value))

  // Preload all UI fonts for preview in dropdown
  UI_FONTS.forEach(f => loadUIFont(f.value))

  // Load recurring entries and projects list
  await recurringStore.fetchEntries()
  recProjects.value = await invoke<Array<{ id: number; name: string; color: string }>>('get_projects')

  // Load entry templates
  await entryTemplatesStore.fetchTemplates()

  // Load calendar sources
  await fetchCalendarSources()

  // Load onboarding state
  await onboardingStore.load()
})
</script>
