<script setup lang="ts">
/*
 * SettingsView — the home screen of SnapGo before the user triggers a
 * capture. The active configuration section is selected by the App-shell
 * sidebar and passed in via the `tab` prop, so this view only owns the
 * card content for the currently selected destination type:
 *
 *   • "general" — the global hotkey / capture parameters.
 *   • "s3"      — S3-compatible object-storage credentials.
 *   • "ssh"     — SSH/SCP destination for the "save remote" button.
 *   • "llm"     — multimodal screenshot summary provider settings.
 *   • "ocr"     — cloud OCR provider settings for text extraction.
 *
 * State flow: load() pulls config from Go on mount, save() pushes back.
 */
import { computed, onMounted, ref, watch } from 'vue'
import {
  GetConfig,
  SaveConfig,
  TestConnection,
  TestSSHConnection,
  CaptureNow,
} from '../../wailsjs/go/main/App'

// Tab discriminator shared with the App shell. Kept as a string union so
// the parent can pass the value without importing a type from this view.
type TabId = 'general' | 's3' | 'ssh' | 'llm' | 'ocr'
type ThemeMode = 'auto' | 'light' | 'dark'

const props = defineProps<{
  tab: TabId
  theme: ThemeMode
}>()

const emit = defineEmits<{
  (event: 'theme-change', theme: ThemeMode): void
}>()

interface S3Config {
  endpoint: string
  region: string
  bucket: string
  accessKeyId: string
  secretAccessKey: string
  pathPrefix: string
  publicUrlBase: string
  usePathStyle: boolean
}

interface SSHConfig {
  host: string
  port: number
  user: string
  authMethod: string
  password: string
  pathPrefix: string
  strictHostKey: boolean
  knownHostsPath: string
  connectTimeoutSecs: number
}

interface LLMProviderConfig {
  label: string
  baseUrl: string
  apiKey: string
  model: string
  maxTokens: number
  temperature: number
  timeoutSecs: number
}

interface LLMConfig {
  activeProvider: string
  prompt: string
  providers: Record<string, LLMProviderConfig>
}

interface OCRProviderConfig {
  label: string
  endpoint: string
  accessKeyId: string
  accessKeySecret: string
  region: string
  service: string
  action: string
  version: string
  timeoutSecs: number
}

interface OCRConfig {
  activeProvider: string
  providers: Record<string, OCRProviderConfig>
}

interface AppConfig {
  hotkey: string
  theme: ThemeMode
  s3: S3Config
  ssh: SSHConfig
  llm: LLMConfig
  ocr: OCRConfig
}

const llmProviderOptions = [
  { id: 'qwen', label: '阿里千问多模态' },
  { id: 'doubao', label: '火山方舟豆包多模态' },
  { id: 'openai', label: 'ChatGPT / OpenAI-compatible' },
]

const ocrProviderOptions = [
  { id: 'aliyun', label: '阿里云文字识别' },
  { id: 'volcengine', label: '火山引擎文字识别' },
]

const themeOptions: Array<{ id: ThemeMode; label: string }> = [
  { id: 'light', label: '浅色' },
  { id: 'dark', label: '深色' },
  { id: 'auto', label: '自动' },
]

const DEFAULT_SUMMARY_PROMPT = `你是一个截图内容总结助手。请阅读截图，并用中文输出一段适合直接粘贴到聊天、工单、Issue 或 PR 中的说明。

要求：
- 先说明截图中最重要的信息和用户可能想表达的意图。
- 如果截图包含报错、异常状态、表格、代码、界面控件或关键数字，请准确提取。
- 如果截图内容不足以判断，不要编造；直接说明不确定点。
- 输出尽量简洁，默认 3 到 6 条要点。
- 不要输出 Markdown 标题，不要寒暄。`

// `defaultConfig` keeps the initial render in sync with the Go-side
// DefaultAppConfig so the UI never flashes empty fields before load()
// completes. Centralising the defaults also avoids subtle drift between
// frontend and backend.
function defaultConfig(): AppConfig {
  return {
    hotkey: 'cmd+shift+a',
    theme: 'auto',
    s3: {
      endpoint: '',
      region: 'us-east-1',
      bucket: '',
      accessKeyId: '',
      secretAccessKey: '',
      pathPrefix: 'snapgo/',
      publicUrlBase: '',
      usePathStyle: true,
    },
    ssh: {
      host: '',
      port: 22,
      user: '',
      authMethod: 'password',
      password: '',
      pathPrefix: 'snapgo/',
      strictHostKey: false,
      knownHostsPath: '',
      connectTimeoutSecs: 10,
    },
    llm: {
      activeProvider: 'openai',
      prompt: DEFAULT_SUMMARY_PROMPT,
      providers: {
        qwen: {
          label: '阿里千问多模态',
          baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
          apiKey: '',
          model: 'qwen-vl-plus',
          maxTokens: 600,
          temperature: 0.2,
          timeoutSecs: 60,
        },
        doubao: {
          label: '火山方舟豆包多模态',
          baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
          apiKey: '',
          model: '',
          maxTokens: 600,
          temperature: 0.2,
          timeoutSecs: 60,
        },
        openai: {
          label: 'ChatGPT / OpenAI-compatible',
          baseUrl: 'https://api.openai.com/v1',
          apiKey: '',
          model: 'gpt-5.5',
          maxTokens: 600,
          temperature: 0.2,
          timeoutSecs: 60,
        },
      },
    },
    ocr: {
      activeProvider: 'aliyun',
      providers: {
        aliyun: {
          label: '阿里云文字识别',
          endpoint: 'https://ocr-api.cn-hangzhou.aliyuncs.com',
          accessKeyId: '',
          accessKeySecret: '',
          region: '',
          service: '',
          action: 'RecognizeGeneral',
          version: '2021-07-07',
          timeoutSecs: 30,
        },
        volcengine: {
          label: '火山引擎文字识别',
          endpoint: 'https://visual.volcengineapi.com',
          accessKeyId: '',
          accessKeySecret: '',
          region: 'cn-north-1',
          service: 'cv',
          action: 'OCRNormal',
          version: '2020-08-26',
          timeoutSecs: 30,
        },
      },
    },
  }
}

const config = ref<AppConfig>(defaultConfig())

const saving = ref(false)
const testing = ref(false)
const testingSSH = ref(false)
const message = ref<{ kind: 'ok' | 'err'; text: string } | null>(null)

// The "remote path" in the UI is shown as "~/<pathPrefix>" so the user
// understands that everything is rooted at the remote home directory.
// We strip leading slashes / tilde here defensively in case someone pastes
// a full path.
const sshPathDisplay = computed({
  get: () => config.value.ssh.pathPrefix,
  set: (raw: string) => {
    let cleaned = raw.trim()
    cleaned = cleaned.replace(/^~/, '')
    cleaned = cleaned.replace(/^\/+/, '')
    config.value.ssh.pathPrefix = cleaned
  },
})

const activeLLMProvider = computed(() => {
  const id = config.value.llm.activeProvider || 'openai'
  if (!config.value.llm.providers[id]) {
    config.value.llm.activeProvider = 'openai'
    return config.value.llm.providers.openai
  }
  return config.value.llm.providers[id]
})

const activeOCRProvider = computed(() => {
  const id = config.value.ocr.activeProvider || 'aliyun'
  if (!config.value.ocr.providers[id]) {
    config.value.ocr.activeProvider = 'aliyun'
    return config.value.ocr.providers.aliyun
  }
  return config.value.ocr.providers[id]
})

function normalizeTheme(theme: unknown): ThemeMode {
  return theme === 'light' || theme === 'dark' || theme === 'auto'
    ? theme
    : 'auto'
}

function flash(kind: 'ok' | 'err', text: string) {
  message.value = { kind, text }
  window.setTimeout(() => {
    if (message.value?.text === text) message.value = null
  }, 4000)
}

async function load() {
  const cfg = await GetConfig()
  if (cfg) {
    // Merge with defaults so a config file written before SSH support
    // existed does not leave ssh fields undefined.
    const merged = defaultConfig()
    Object.assign(merged, cfg)
    merged.s3 = { ...merged.s3, ...(cfg as any).s3 }
    merged.ssh = { ...merged.ssh, ...(cfg as any).ssh }
    merged.llm = { ...merged.llm, ...(cfg as any).llm }
    merged.llm.providers = {
      ...defaultConfig().llm.providers,
      ...((cfg as any).llm?.providers ?? {}),
    }
    merged.ocr = { ...merged.ocr, ...(cfg as any).ocr }
    merged.ocr.providers = {
      ...defaultConfig().ocr.providers,
      ...((cfg as any).ocr?.providers ?? {}),
    }
    merged.theme = normalizeTheme((cfg as any).theme)
    config.value = merged
    emit('theme-change', config.value.theme)
  }
}

async function save() {
  saving.value = true
  try {
    await SaveConfig(config.value as any)
    flash('ok', 'Settings saved')
  } catch (err: any) {
    flash('err', `Save failed: ${err?.message ?? err}`)
  } finally {
    saving.value = false
  }
}

async function test() {
  testing.value = true
  try {
    await TestConnection(config.value.s3 as any)
    flash('ok', 'S3 connection OK — bucket is writable')
  } catch (err: any) {
    flash('err', `Test failed: ${err?.message ?? err}`)
  } finally {
    testing.value = false
  }
}

async function testSSH() {
  testingSSH.value = true
  try {
    await TestSSHConnection(config.value.ssh as any)
    flash('ok', 'SSH connection OK')
  } catch (err: any) {
    flash('err', `SSH test failed: ${err?.message ?? err}`)
  } finally {
    testingSSH.value = false
  }
}

async function manualCapture() {
  await CaptureNow()
}

watch(
  () => config.value.theme,
  (theme) => emit('theme-change', normalizeTheme(theme))
)

onMounted(load)
</script>

<template>
  <div class="settings" :class="`theme-${props.theme}`">
    <header class="hero">
      <div>
        <h1>SnapGo</h1>
        <p class="subtitle">
          Press <kbd>{{ config.hotkey }}</kbd> anywhere to capture, upload, and
          copy the URL to your clipboard.
        </p>
      </div>
      <button class="btn primary" @click="manualCapture">Capture now</button>
    </header>

    <!-- General tab — shortcut and appearance configuration. -->
    <section v-if="props.tab === 'general'" class="card">
      <h2>General</h2>
      <div class="grid">
        <label class="field">
          <span>Global hotkey</span>
          <input
            v-model="config.hotkey"
            placeholder="cmd+shift+a"
            spellcheck="false"
          />
        </label>
        <label class="field">
          <span>主题选择</span>
          <select v-model="config.theme">
            <option
              v-for="themeOption in themeOptions"
              :key="themeOption.id"
              :value="themeOption.id"
            >
              {{ themeOption.label }}
            </option>
          </select>
        </label>
      </div>
      <p class="hint">
        Tokens (case-insensitive, "+"-separated): cmd / ctrl / option / shift +
        a–z or 0–9. Example: <code>cmd+shift+a</code>.
      </p>
      <div class="actions">
        <button class="btn primary" :disabled="saving" @click="save">
          {{ saving ? 'Saving…' : 'Save' }}
        </button>
      </div>
    </section>

    <!-- S3-Conf tab — unchanged content, moved out of General. -->
    <section v-if="props.tab === 's3'" class="card">
      <h2>S3-compatible storage</h2>
      <div class="grid">
        <label class="field">
          <span>Endpoint *</span>
          <input
            v-model="config.s3.endpoint"
            placeholder="https://s3.us-east-005.backblazeb2.com"
          />
        </label>
        <label class="field">
          <span>Region</span>
          <input v-model="config.s3.region" placeholder="us-east-1 / auto" />
        </label>
        <label class="field">
          <span>Bucket *</span>
          <input v-model="config.s3.bucket" placeholder="my-screenshots" />
        </label>
        <label class="field">
          <span>Access Key ID *</span>
          <input v-model="config.s3.accessKeyId" />
        </label>
        <label class="field">
          <span>Secret Access Key *</span>
          <input v-model="config.s3.secretAccessKey" type="password" />
        </label>
        <label class="field">
          <span>Path prefix</span>
          <input v-model="config.s3.pathPrefix" placeholder="snapgo/" />
        </label>
        <label class="field full">
          <span>Public URL base (optional, e.g. CDN)</span>
          <input
            v-model="config.s3.publicUrlBase"
            placeholder="https://cdn.example.com/screenshots"
          />
        </label>
        <label class="field checkbox">
          <input type="checkbox" v-model="config.s3.usePathStyle" />
          <span
            >Use path-style addressing (recommended for MinIO, R2, custom
            domains)</span
          >
        </label>
      </div>

      <div class="actions">
        <button class="btn" :disabled="testing" @click="test">
          {{ testing ? 'Testing…' : 'Test connection' }}
        </button>
        <button class="btn primary" :disabled="saving" @click="save">
          {{ saving ? 'Saving…' : 'Save' }}
        </button>
      </div>
    </section>

    <!-- SSH-Conf tab — destination for the save-remote action. -->
    <section v-if="props.tab === 'ssh'" class="card">
      <h2>SSH / SCP destination</h2>
      <div class="grid">
        <label class="field">
          <span>Host (IP or domain) *</span>
          <input v-model="config.ssh.host" placeholder="192.168.1.10" />
        </label>
        <label class="field">
          <span>Port</span>
          <input
            v-model.number="config.ssh.port"
            type="number"
            min="1"
            max="65535"
            placeholder="22"
          />
        </label>
        <div class="field-row auth-row">
          <label class="field">
            <span>Auth method</span>
            <select v-model="config.ssh.authMethod">
              <option value="password">Password</option>
              <option value="key">SSH-Key</option>
              <option value="kerberos">Kerberos</option>
            </select>
          </label>
          <label class="field">
            <span>User *</span>
            <input v-model="config.ssh.user" placeholder="ubuntu" />
          </label>
          <label
            v-if="config.ssh.authMethod === 'password'"
            class="field"
          >
            <span>Password</span>
            <input v-model="config.ssh.password" type="password" />
          </label>
        </div>
        <label class="field full">
          <span>Remote path (under home)</span>
          <div class="prefixed-input">
            <span class="input-prefix">~/</span>
            <input
              v-model="sshPathDisplay"
              placeholder="snapgo/"
              spellcheck="false"
            />
          </div>
        </label>
        <label class="field">
          <span>Connect timeout (sec)</span>
          <input
            v-model.number="config.ssh.connectTimeoutSecs"
            type="number"
            min="1"
            max="120"
            placeholder="10"
          />
        </label>
        <label class="field">
          <span>Known hosts path</span>
          <input
            v-model="config.ssh.knownHostsPath"
            placeholder="~/.ssh/known_hosts"
            :disabled="!config.ssh.strictHostKey"
          />
        </label>
        <label class="field checkbox">
          <input type="checkbox" v-model="config.ssh.strictHostKey" />
          <span>
            Strict host key checking (recommended for production hosts)
          </span>
        </label>
      </div>
      <p v-if="config.ssh.authMethod === 'kerberos'" class="hint">
        Kerberos mode delegates to the system <code>ssh</code>/<code>scp</code>.
        Run <code>kinit your.name@BYTEDANCE.COM</code> in a terminal first; tickets
        expire (typically every 10–24h), so re-run <code>kinit</code> if uploads
        start failing. Verify with <code>klist</code>.
      </p>
      <p v-else-if="config.ssh.authMethod === 'key'" class="hint">
        SSH-Key mode uses your <code>ssh-agent</code> and <code>~/.ssh/id_*</code>
        keys. Make sure password-less login already works (e.g. via
        <code>ssh-copy-id</code>).
      </p>
      <p
        v-else-if="config.ssh.authMethod === 'password' && !config.ssh.password"
        class="hint warn"
      >
        Password is empty — enter the remote login password, or switch the auth
        method to SSH-Key.
      </p>

      <div class="actions">
        <button class="btn" :disabled="testingSSH" @click="testSSH">
          {{ testingSSH ? 'Testing…' : 'Test connection' }}
        </button>
        <button class="btn primary" :disabled="saving" @click="save">
          {{ saving ? 'Saving…' : 'Save' }}
        </button>
      </div>
    </section>

    <!-- LLM tab — multimodal screenshot summary provider settings. -->
    <section v-if="props.tab === 'llm'" class="card">
      <h2>LLM screenshot summary</h2>
      <div class="grid">
        <label class="field full">
          <span>Provider</span>
          <select v-model="config.llm.activeProvider">
            <option
              v-for="provider in llmProviderOptions"
              :key="provider.id"
              :value="provider.id"
            >
              {{ provider.label }}
            </option>
          </select>
        </label>
        <label class="field full">
          <span>Base URL *</span>
          <input
            v-model="activeLLMProvider.baseUrl"
            placeholder="https://api.openai.com/v1"
            spellcheck="false"
          />
        </label>
        <label class="field">
          <span>API Key *</span>
          <input v-model="activeLLMProvider.apiKey" type="password" />
        </label>
        <label class="field">
          <span>Model *</span>
          <input
            v-model="activeLLMProvider.model"
            placeholder="gpt-5.5 / qwen-vl-plus / Ark endpoint ID"
            spellcheck="false"
          />
        </label>
        <label class="field">
          <span>Max tokens</span>
          <input
            v-model.number="activeLLMProvider.maxTokens"
            type="number"
            min="64"
            max="4096"
            placeholder="600"
          />
        </label>
        <label class="field">
          <span>Temperature</span>
          <input
            v-model.number="activeLLMProvider.temperature"
            type="number"
            min="0"
            max="2"
            step="0.1"
            placeholder="0.2"
          />
        </label>
        <label class="field">
          <span>Timeout (sec)</span>
          <input
            v-model.number="activeLLMProvider.timeoutSecs"
            type="number"
            min="10"
            max="300"
            placeholder="60"
          />
        </label>
        <label class="field full">
          <span>Prompt</span>
          <textarea
            v-model="config.llm.prompt"
            class="prompt-textarea"
            spellcheck="false"
          />
        </label>
      </div>
      <p class="hint">
        The "copy summary" screenshot action first uploads the image through the
        S3 configuration, then sends the public image URL to this multimodal
        provider. Keep S3 configured before using it.
      </p>
      <p v-if="config.llm.activeProvider === 'doubao'" class="hint">
        Volcengine Ark usually uses the inference endpoint ID as the model
        value; paste the endpoint/model identifier from your Ark console.
      </p>
      <p v-else-if="config.llm.activeProvider === 'qwen'" class="hint">
        DashScope OpenAI-compatible mode is used here. If your workspace uses a
        regional endpoint, replace Base URL with that compatible-mode URL.
      </p>

      <div class="actions">
        <button class="btn primary" :disabled="saving" @click="save">
          {{ saving ? 'Saving…' : 'Save' }}
        </button>
      </div>
    </section>

    <!-- OCR tab — cloud OCR provider settings for screenshot text extraction. -->
    <section v-if="props.tab === 'ocr'" class="card">
      <h2>文字提取</h2>
      <div class="grid">
        <label class="field full">
          <span>Provider</span>
          <select v-model="config.ocr.activeProvider">
            <option
              v-for="provider in ocrProviderOptions"
              :key="provider.id"
              :value="provider.id"
            >
              {{ provider.label }}
            </option>
          </select>
        </label>
        <label class="field full">
          <span>Endpoint *</span>
          <input
            v-model="activeOCRProvider.endpoint"
            placeholder="https://ocr-api.cn-hangzhou.aliyuncs.com"
            spellcheck="false"
          />
        </label>
        <label class="field">
          <span>AccessKey ID *</span>
          <input v-model="activeOCRProvider.accessKeyId" spellcheck="false" />
        </label>
        <label class="field">
          <span>AccessKey Secret *</span>
          <input v-model="activeOCRProvider.accessKeySecret" type="password" />
        </label>
        <label
          v-if="config.ocr.activeProvider === 'volcengine'"
          class="field"
        >
          <span>Region *</span>
          <input
            v-model="activeOCRProvider.region"
            placeholder="cn-north-1"
            spellcheck="false"
          />
        </label>
        <label
          v-if="config.ocr.activeProvider === 'volcengine'"
          class="field"
        >
          <span>Service *</span>
          <input
            v-model="activeOCRProvider.service"
            placeholder="cv"
            spellcheck="false"
          />
        </label>
        <label class="field">
          <span>Action *</span>
          <input
            v-model="activeOCRProvider.action"
            placeholder="RecognizeGeneral / OCRNormal"
            spellcheck="false"
          />
        </label>
        <label class="field">
          <span>Version *</span>
          <input
            v-model="activeOCRProvider.version"
            placeholder="2021-07-07 / 2020-08-26"
            spellcheck="false"
          />
        </label>
        <label class="field">
          <span>Timeout (sec)</span>
          <input
            v-model.number="activeOCRProvider.timeoutSecs"
            type="number"
            min="5"
            max="120"
            placeholder="30"
          />
        </label>
      </div>
      <p v-if="config.ocr.activeProvider === 'aliyun'" class="hint">
        阿里云默认使用 OCR API 的 <code>RecognizeGeneral</code>，请求体为截图
        PNG 原始数据，并使用 ACS3-HMAC-SHA256 签名。
      </p>
      <p v-else class="hint">
        火山引擎默认使用视觉智能的 <code>OCRNormal</code>，截图以
        <code>image_base64</code> 发送，并使用 Region/Service 参与签名。
      </p>

      <div class="actions">
        <button class="btn primary" :disabled="saving" @click="save">
          {{ saving ? 'Saving…' : 'Save' }}
        </button>
      </div>
    </section>

    <transition name="fade">
      <div
        v-if="message"
        class="banner"
        :class="message.kind === 'ok' ? 'ok' : 'err'"
      >
        {{ message.text }}
      </div>
    </transition>
  </div>
</template>

<style scoped>
.settings {
  max-width: 760px;
  margin: 0 auto;
  padding: 28px 32px 60px;
  color: #1f2937;
}
.settings.theme-light {
  color-scheme: light;
}
.settings.theme-dark {
  color-scheme: dark;
}
.settings.theme-auto {
  color-scheme: light dark;
}
.hero {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}
.hero h1 {
  margin: 0 0 6px;
  font-size: 24px;
  letter-spacing: -0.01em;
}
.subtitle {
  margin: 0;
  color: #6b7280;
  font-size: 13px;
}
kbd {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: #f3f4f6;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  padding: 1px 6px;
  font-size: 12px;
}

/* Tab strip removed — sidebar in App.vue now drives section selection. */

.card {
  background: #ffffff;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 18px 20px;
  margin-bottom: 18px;
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}
.card h2 {
  margin: 0 0 12px;
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #6b7280;
}
.grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px 16px;
}
.field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
}
.field.full {
  grid-column: 1 / -1;
}
/* field-row groups several fields onto a single full-width row. auth-row
   splits Auth method / User / Password roughly 1:2:2. */
.field-row {
  grid-column: 1 / -1;
  display: grid;
  gap: 12px 16px;
}
.auth-row {
  grid-template-columns: 1fr 2fr 2fr;
}
.field span {
  color: #374151;
  font-weight: 500;
}
.field input[type='text'],
.field input[type='number'],
.field input:not([type='checkbox']),
.field select,
.field textarea {
  border: 1px solid #d1d5db;
  border-radius: 6px;
  padding: 7px 10px;
  font-size: 13px;
  line-height: 1.4;
  background: #fff;
  color: inherit;
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
  width: 100%;
  box-sizing: border-box;
}
.field input[type='text'],
.field input[type='number'],
.field input:not([type='checkbox']),
.field select {
  height: 36px;
}
.field textarea {
  min-height: 168px;
  resize: vertical;
  font-family: inherit;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.18);
}
.field.checkbox {
  flex-direction: row;
  align-items: center;
  gap: 8px;
  grid-column: 1 / -1;
}
.field input:disabled {
  background: #f3f4f6;
  color: #9ca3af;
  cursor: not-allowed;
}
.prefixed-input {
  display: flex;
  align-items: stretch;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  background: #fff;
  overflow: hidden;
}
.prefixed-input:focus-within {
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.18);
}
.input-prefix {
  padding: 7px 10px;
  background: #f3f4f6;
  color: #4b5563;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  border-right: 1px solid #d1d5db;
}
.prefixed-input input {
  border: 0 !important;
  border-radius: 0 !important;
  flex: 1;
  padding: 7px 10px;
}
.prefixed-input input:focus {
  box-shadow: none !important;
}
.hint {
  margin: 8px 0 0;
  font-size: 12px;
  color: #6b7280;
}
.hint.warn {
  color: #b45309;
}
.hint code {
  background: #f3f4f6;
  padding: 1px 6px;
  border-radius: 4px;
}
.actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}
.btn {
  border: 1px solid #d1d5db;
  background: #fff;
  border-radius: 6px;
  padding: 7px 14px;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.btn:hover:not(:disabled) {
  border-color: #9ca3af;
  background: #f9fafb;
}
.btn.primary {
  background: #3b82f6;
  border-color: #3b82f6;
  color: #fff;
}
.btn.primary:hover:not(:disabled) {
  background: #2563eb;
  border-color: #2563eb;
}
.btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
.banner {
  position: fixed;
  bottom: 18px;
  left: 50%;
  transform: translateX(-50%);
  padding: 8px 14px;
  border-radius: 6px;
  font-size: 13px;
  box-shadow: 0 6px 24px rgba(15, 23, 42, 0.18);
}
.banner.ok {
  background: #10b981;
  color: #fff;
}
.banner.err {
  background: #ef4444;
  color: #fff;
}
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.2s;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
.settings.theme-dark {
  color: #e5e7eb;
}
.settings.theme-dark .card {
  background: #1f2937;
  border-color: #374151;
}
.settings.theme-dark .field span {
  color: #d1d5db;
}
.settings.theme-dark .field input[type='text'],
.settings.theme-dark .field input[type='number'],
.settings.theme-dark .field input:not([type='checkbox']),
.settings.theme-dark .field select,
.settings.theme-dark .field textarea {
  background: #111827;
  border-color: #374151;
  color: #fff;
}
.settings.theme-dark .field input::placeholder,
.settings.theme-dark .field textarea::placeholder {
  color: #9ca3af;
}
.settings.theme-dark .field input:disabled {
  background: #1f2937;
  color: #6b7280;
}
.settings.theme-dark .prefixed-input {
  background: #111827;
  border-color: #374151;
}
.settings.theme-dark .input-prefix {
  background: #1f2937;
  border-right-color: #374151;
  color: #d1d5db;
}
.settings.theme-dark .btn:not(.primary) {
  background: #1f2937;
  border-color: #374151;
  color: #e5e7eb;
}
.settings.theme-dark .btn:not(.primary):hover:not(:disabled) {
  background: #374151;
}
.settings.theme-dark .subtitle {
  color: #9ca3af;
}

@media (prefers-color-scheme: dark) {
  .settings.theme-auto {
    color: #e5e7eb;
  }
  .settings.theme-auto .card {
    background: #1f2937;
    border-color: #374151;
  }
  .settings.theme-auto .field span {
    color: #d1d5db;
  }
  .settings.theme-auto .field input[type='text'],
  .settings.theme-auto .field input[type='number'],
  .settings.theme-auto .field input:not([type='checkbox']),
  .settings.theme-auto .field select,
  .settings.theme-auto .field textarea {
    background: #111827;
    border-color: #374151;
    color: #fff;
  }
  .settings.theme-auto .field input::placeholder,
  .settings.theme-auto .field textarea::placeholder {
    color: #9ca3af;
  }
  .settings.theme-auto .field input:disabled {
    background: #1f2937;
    color: #6b7280;
  }
  .settings.theme-auto .prefixed-input {
    background: #111827;
    border-color: #374151;
  }
  .settings.theme-auto .input-prefix {
    background: #1f2937;
    border-right-color: #374151;
    color: #d1d5db;
  }
  .settings.theme-auto .btn:not(.primary) {
    background: #1f2937;
    border-color: #374151;
    color: #e5e7eb;
  }
  .settings.theme-auto .btn:not(.primary):hover:not(:disabled) {
    background: #374151;
  }
  .settings.theme-auto .subtitle {
    color: #9ca3af;
  }
}
</style>
