<template>
  <!-- ═══ AI 模型配置(provider 列表)═══ -->
  <section class="panel">
    <div class="panel-header">
      <h2>{{ $t('settings.panelAiProvider') }}</h2>
      <button class="btn btn-ghost btn-sm" @click="openProviderForm()">{{ $t('settings.addProvider') }}</button>
    </div>
    <div class="provider-list" v-if="aiProviders.length > 0">
      <div class="provider-card" :class="{ 'provider-card--default': p.is_default, 'provider-card--disabled': p.enabled === false }" v-for="p in aiProviders" :key="p.id">
        <div class="provider-top">
          <div class="provider-info">
            <span class="provider-name">{{ p.name }}</span>
            <span v-if="p.is_default" class="default-badge">{{ $t('settings.badgeDefault') }}</span>
            <span v-if="p.enabled === false" class="pool-badge">{{ $t('settings.badgePoolDisabled') }}</span>
          </div>
          <div class="provider-actions">
            <button class="btn-link" @click="setDefaultProvider(p.id)" v-if="!p.is_default">{{ $t('settings.actionSetDefault') }}</button>
            <button class="btn-link" @click="openProviderForm(p)">{{ $t('common.edit') }}</button>
            <button class="btn-link btn-link-danger" @click="deleteProvider(p.id)">{{ $t('common.delete') }}</button>
          </div>
        </div>
        <div class="provider-detail">
          <div class="detail-row">
            <span class="detail-label">{{ $t('settings.detailBaseUrl') }}</span>
            <span class="detail-value">{{ p.base_url }}</span>
          </div>
          <div class="detail-row">
            <span class="detail-label">{{ $t('settings.detailModel') }}</span>
            <span class="detail-value">{{ p.default_model }}</span>
          </div>
          <div class="detail-row">
            <span class="detail-label">{{ $t('settings.detailApiKey') }}</span>
            <span class="detail-value mask">{{ maskKey(p.api_key) }}</span>
          </div>
          <!-- F-260614-04c 负载均衡池:enabled toggle + weight 即时调 IPC,落库即重建 caps -->
          <div class="detail-row detail-row--pool">
            <span class="detail-label" :title="$t('settings.descPoolEnabled')">{{ $t('settings.detailPoolEnabled') }}</span>
            <div class="detail-value detail-value--control">
              <label class="toggle toggle--sm" :title="$t('settings.descPoolEnabled')">
                <input type="checkbox" :checked="p.enabled !== false" @change="onPoolToggle(p, ($event.target as HTMLInputElement).checked)" />
                <span class="toggle-slider"></span>
              </label>
            </div>
          </div>
          <div class="detail-row detail-row--pool" v-if="p.enabled !== false">
            <span class="detail-label" :title="$t('settings.descPoolWeight')">{{ $t('settings.detailPoolWeight') }}</span>
            <div class="detail-value detail-value--control">
              <input type="number" class="setting-input setting-input--sm" min="0" max="100"
                     :value="p.weight ?? 50"
                     @change="onPoolWeightChange(p, ($event.target as HTMLInputElement).valueAsNumber)" />
              <span class="pool-weight-hint">0-100</span>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="empty-hint" v-else>{{ $t('settings.emptyProvider') }}</div>
  </section>

  <!-- ═══ AI 提供商表单 ═══ -->
  <section class="panel" v-if="providerForm.visible">
    <div class="panel-header">
      <h2>{{ providerForm.editId ? $t('settings.editProviderTitle') : $t('settings.addProviderTitle') }}</h2>
      <!-- L19: 取消走 closeProviderForm,脏表单二次确认后再关(防误弃改动) -->
      <button class="btn btn-ghost btn-sm" @click="closeProviderForm">{{ $t('common.cancel') }}</button>
    </div>
    <div class="form-grid">
      <div class="form-field">
        <label class="form-label">{{ $t('settings.labelName') }}</label>
        <input v-model="providerForm.name" class="setting-input" :placeholder="$t('settings.phProviderName')" />
      </div>
      <!-- 项 D:厂商预设下拉 — 选中自动填充 providerType/baseUrl/defaultModel(用户可改覆盖)。
           新建态独立"快捷选择";编辑态回填后切预设仍可覆盖(等价快捷填充),不阻塞手改。
           "自定义" 不预填任何字段。 -->
      <div class="form-field">
        <label class="form-label">{{ $t('settings.labelPreset') }}</label>
        <select v-model="providerForm.presetId" class="setting-select" @change="onPresetChange">
          <option value="__custom">{{ $t('settings.presetCustom') }}</option>
          <option v-for="p in PROVIDER_PRESETS" :key="p.id" :value="p.id">{{ p.name }}</option>
        </select>
      </div>
      <div class="form-field">
        <label class="form-label">{{ $t('settings.labelProviderType') }}</label>
        <select v-model="providerForm.providerType" class="setting-select">
          <option value="openai_compat">{{ $t('settings.providerTypeOpenaiCompat') }}</option>
          <option value="anthropic">{{ $t('settings.providerTypeAnthropic') }}</option>
        </select>
      </div>
      <div class="form-field">
        <label class="form-label">{{ $t('settings.labelBaseUrl') }}</label>
        <input v-model="providerForm.baseUrl" class="setting-input" :placeholder="$t('settings.phBaseUrl')" />
      </div>
      <div class="form-field">
        <label class="form-label">{{ $t('settings.labelApiKey') }}</label>
        <input v-model="providerForm.apiKey" class="setting-input" type="password" :placeholder="$t('settings.phApiKey')" />
      </div>
      <div class="form-field">
        <label class="form-label">{{ $t('settings.labelDefaultModel') }}</label>
        <input v-model="providerForm.defaultModel" class="setting-input" :placeholder="$t('settings.phDefaultModel')" />
        <!-- 方向 A(轻量):defaultModel 仅作"建议起点"——预设 model id 易过时(DeepSeek chat 停用 /
             Gemini 2.5→3.5 等),提示用户点「测试连接」拉真实模型列表覆盖,降低踩过时默认值的困惑。 -->
        <div class="form-field-hint">{{ $t('settings.defaultModelHint') }}</div>
      </div>
      <!-- P0-2: 保存失败持久错误横幅(常驻表单内,非自隐 toast)。
           含分类建议:keyring/迁移失败 → 查系统钥匙串权限;其它 → 通用重试。
           用户看到错误同时表单保持编辑态,改完点保存即可重试。 -->
      <div v-if="providerForm.saveError" class="save-error-banner">
        <pre class="save-error-msg">{{ providerForm.saveError }}</pre>
      </div>
      <div class="form-actions">
        <button class="btn btn-primary" @click="saveProvider" :disabled="providerForm.saving">
          {{ providerForm.saving ? $t('settings.saving') : $t('common.save') }}
        </button>
        <button class="btn btn-ghost" @click="fetchModels" :disabled="providerForm.fetching || !canTestConnection">
          {{ providerForm.fetching ? $t('settings.fetchingModels') : $t('settings.testConnection') }}
        </button>
      </div>

      <!-- ═══ 拉取到的模型列表(F-01 阶段6)═══ -->
      <div class="form-field form-field--full" v-if="providerForm.models.length > 0">
        <label class="form-label">{{ $t('settings.modelListTitle', { count: providerForm.models.length }) }}</label>
        <div class="model-list">
          <div class="model-row" v-for="m in providerForm.models" :key="m.model_id">
            <div class="model-row-main">
              <label class="toggle toggle--sm">
                <input type="checkbox" v-model="m.enabled" />
                <span class="toggle-slider"></span>
              </label>
              <span class="model-id">{{ m.model_id }}</span>
              <div class="model-tags">
                <span v-for="md in m.modalities" :key="'md-' + md" class="tag tag-modality">{{ $t('settings.tagModality.' + md) }}</span>
                <span v-for="cap in m.capabilities" :key="'cap-' + cap" class="tag tag-capability">{{ $t('settings.tagCapability.' + cap) }}</span>
                <span v-if="m.probe_source" class="tag tag-source">{{ $t('settings.tagSource.' + m.probe_source) }}</span>
              </div>
            </div>
            <div class="model-row-side">
              <label class="model-weight-label">{{ $t('settings.labelWeight') }}</label>
              <input type="number" class="setting-input setting-input--sm" min="0" max="100" v-model.number="m.weight" />
            </div>
          </div>
        </div>
        <div class="model-list-hint">{{ $t('settings.modelListHint') }}</div>
        <!-- M32: 模型列表 enabled/weight 改了未保存时显提示(关闭/切走将丢弃) -->
        <div v-if="isFormDirty" class="model-list-unsaved-hint">{{ $t('settings.modelListUnsavedHint') }}</div>
      </div>
    </div>
  </section>
</template>

<script setup lang="ts">
import { reactive, ref, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { aiApi } from '@/api'
import type { AiProviderConfig, ModelConfig } from '@/api/types'
import { PROVIDER_PRESETS, CUSTOM_PRESET_ID, findPreset } from './providerPresets'

// ============================================================
// AI 提供商域 — 真实后端 CRUD + F-04c 负载均衡池(enabled / weight)
// ============================================================
const { t } = useI18n()

// 懒加载 useAiStore 避免静态 import 形成循环依赖:
// ProviderPanel → stores/ai → useAiEvents → aiShared → useAppSettingsStore
// 在 Settings chunk 首次加载时,stores/ai 模块顶层代码(__bindMessages)可能触发 TDZ。
// 改为运行时动态 import,模块加载顺序不再耦合。
async function syncAiStoreProviders() {
  const { useAiStore } = await import('@/stores/ai')
  const aiStore = useAiStore()
  await aiStore.loadProviders()
}

const emit = defineEmits<{
  // P0-2: 加 'success'(保存成功绿色 toast)
  (e: 'toast', msg: string, type: 'error' | 'warning' | 'info' | 'success'): void
  (e: 'providers-changed'): void
}>()

const props = defineProps<{
  confirmDialog: (msg: string) => Promise<boolean>
}>()

const aiProviders = ref<AiProviderConfig[]>([])

function errMsg(e: unknown): string { return e instanceof Error ? e.message : String(e) }

const providerForm = reactive({
  visible: false,
  editId: '' as string,
  name: '',
  providerType: 'openai_compat',
  baseUrl: '',
  apiKey: '',
  defaultModel: '',
  // 项 D:厂商预设下拉当前值(__custom=自定义;命中预设 id 时仅作展示与回填源头,
  // 实际字段值由 onPresetChange 写入;用户手改任一字段后 presetId 不自动复位,
  // 避免编辑态抖动,提交时也不读 presetId —— 以表单实际字段值为准)。
  presetId: CUSTOM_PRESET_ID,
  saving: false,
  // F-01 阶段6:模型拉取 + 列表
  fetching: false,
  models: [] as ModelConfig[],
  // P0-2: 保存失败的持久错误横幅(非自隐 toast)。
  // toast 3s 自隐易被忽略,保存失败时表单内常驻错误条 + 可操作建议,
  // 直到下次保存尝试/重开表单才清除。null=无错误。
  saveError: null as string | null,
  // L19/M32: 表单打开/保存成功时的「已落库快照」JSON 串,用于 isFormDirty 比对。
  // openProviderForm 写入初始快照,saveProvider 成功后刷新;关闭表单清空。
  savedSnapshot: '' as string,
})

/**
 * L19/M32: 表单脏判定——当前表单状态与已落库快照不一致即脏。
 * 触发场景:① 取消/关闭有改动的表单 → 二次确认放弃(L19);
 *          ② 模型列表 enabled/weight 改了未保存 → 表单内提示(M32)。
 * 比对用 JSON.stringify(整体快照语义,简单可靠;表单字段少,性能无虞)。
 * 不含 saving/fetching/saveError/visible 等 UI 态字段(只比业务字段)。
 */
const isFormDirty = computed(() => {
  if (!providerForm.visible) return false
  return formBusinessSnapshot() !== providerForm.savedSnapshot
})

function formBusinessSnapshot(): string {
  return JSON.stringify({
    name: providerForm.name,
    providerType: providerForm.providerType,
    baseUrl: providerForm.baseUrl,
    apiKey: providerForm.apiKey,
    defaultModel: providerForm.defaultModel,
    models: providerForm.models.map(m => ({
      model_id: m.model_id,
      enabled: m.enabled,
      weight: m.weight,
    })),
  })
}

/**
 * L19: 关闭表单入口——脏表单二次确认,清表单后关闭。
 * 用于模板「取消」按钮 + saveProvider 成功后(无脏直接关)。
 */
async function closeProviderForm() {
  if (isFormDirty.value) {
    const ok = await props.confirmDialog(t('settings.confirmDiscardUnsaved'))
    if (!ok) return  // 用户取消关闭,继续编辑
  }
  providerForm.visible = false
  providerForm.savedSnapshot = ''
}

async function loadProviders() {
  try {
    aiProviders.value = await aiApi.listProviders()
    // 同步刷新全局 store(AiChat 等 AI 面板消费方共享同一 state,避免改完仍提示「未配置」)
    // 懒加载避免循环依赖(见文件顶部 syncAiStoreProviders 注释)
    await syncAiStoreProviders()
    emit('providers-changed')
  } catch (e) {
    console.error(t('settings.toastLoadProviderFail'), e)
    emit('toast', t('settings.toastLoadProviderFail'), 'error')
  }
}

function openProviderForm(p?: AiProviderConfig) {
  if (p) {
    providerForm.editId = p.id
    providerForm.name = p.name
    providerForm.baseUrl = p.base_url
    providerForm.apiKey = '' // 编辑不回填 api_key(list 返回 mask,留空=不改,FR-S1)
    providerForm.defaultModel = p.default_model
    providerForm.providerType = p.provider_type || 'openai_compat'
    // 项 D:编辑态不反推预设(避免用户已手改 baseUrl/model 却被下拉误标成某预设误导);
    // 统一标"自定义",下拉仅作新建快捷填充入口。
    providerForm.presetId = CUSTOM_PRESET_ID
    // F-01 阶段6:回填已保存的 model_configs(深拷贝避免双向绑定污染列表源)
    providerForm.models = (p.model_configs ?? []).map(m => ({ ...m, modalities: [...m.modalities], capabilities: [...m.capabilities] }))
  } else {
    providerForm.editId = ''
    providerForm.name = ''
    providerForm.providerType = 'openai_compat'
    providerForm.baseUrl = ''
    providerForm.apiKey = ''
    providerForm.defaultModel = ''
    providerForm.presetId = CUSTOM_PRESET_ID
    providerForm.models = []
  }
  providerForm.fetching = false
  providerForm.saveError = null  // P0-2: 重开表单清旧错误横幅
  providerForm.visible = true
  // L19/M32: 记初始快照(在 visible=true、字段回填完成后),isFormDirty 据此比对。
  providerForm.savedSnapshot = formBusinessSnapshot()
}

/**
 * 项 D:厂商预设下拉变更 — 命中预设则覆盖填充 providerType/baseUrl/defaultModel。
 *
 * 设计取舍:
 * - 覆盖而非合并:选预设=用户明确意图用某厂商端点,全量填入最省心;用户随后仍可手改任一字段。
 * - 不动 name/apiKey:name 用户应自取别名(避免重名"DeepSeek"×N);apiKey 是密钥预设无可填。
 * - 选"自定义"不动作(仅清字段是粗暴的;用户可能误点,留原值更安全)。
 * - 不复位 presetId:用户手改 baseUrl 等后下拉仍标原预设——这无副作用(提交不读 presetId),
 *   强行复位反而要监听每个字段 input,过度工程。
 */
function onPresetChange() {
  const preset = findPreset(providerForm.presetId)
  if (!preset) return  // __custom 或未知 id:不动作
  providerForm.providerType = preset.providerType
  providerForm.baseUrl = preset.baseUrl
  providerForm.defaultModel = preset.defaultModel
}

/**
 * 测试连接按钮可用条件(P0-1):
 * - baseUrl 必填(无论新建/编辑,表单值都要有)
 * - 新建态(editId 空):api_key 必填(无已保存配置可沿用)
 * - 编辑态(editId 有):api_key 可空(留空=沿用已保存密钥)
 */
const canTestConnection = computed(() => {
  if (!providerForm.baseUrl) return false
  if (!providerForm.editId && !providerForm.apiKey) return false
  return true
})

/**
 * 测试连接并拉取模型列表(P0-1)。
 * 用**表单当前值**测试(非 DB 值):新建态直接传表单值;编辑态传表单值 + editId,
 * apiKey 留空时后端从已保存配置沿用密钥。一次性测试不写库。
 *
 * 注:本路径成功后仅更新 model_configs 视图,不落库;用户需点「保存」才会持久化
 * (避免「测试中」产生半成品配置)。原 ai_fetch_models(从 DB 读 + 写回)保留,
 * 供未来「已保存配置重测」场景,当前 UI 不再走它。
 */
async function fetchModels() {
  if (!canTestConnection.value) {
    emit('toast', t('settings.toastSaveIncomplete'), 'warning')
    return
  }
  providerForm.fetching = true
  try {
    const models = await aiApi.testConnection({
      providerType: providerForm.providerType,
      baseUrl: providerForm.baseUrl,
      apiKey: providerForm.apiKey,
      providerId: providerForm.editId || null,
    })
    providerForm.models = models.map(m => ({ ...m, modalities: [...m.modalities], capabilities: [...m.capabilities] }))
    emit('toast', t('settings.toastFetchOk', { count: models.length }), 'info')
  } catch (e) {
    const msg = errMsg(e)
    // 按 model_fetch 错误映射友好提示:超时 / 鉴权失败 / 端点不存在 / 厂商服务异常 / 不支持
    let key = 'settings.toastFetchFail'
    if (msg.includes('超时')) key = 'settings.fetchFailedTimeout'
    else if (msg.includes('鉴权失败')) key = 'settings.fetchFailedAuth'
    else if (msg.includes('端点不存在') || msg.includes('404')) key = 'settings.fetchFailedNotFound'
    emit('toast', t(key, { msg }), 'error')
  } finally {
    providerForm.fetching = false
  }
}

/**
 * P0-2: 把后端返回的错误文本分类成「用户可操作的建议」。
 *
 * 后端 commands/provider.rs 已对密钥相关失败返回友好中文 Err(含原因+建议),
 * 这里只做轻量分类挂上"建议下一步",避免裸 Err 文本吓到用户:
 * - keyring/迁移类:后端 Err 含「钥匙串」/「迁移」→ 指引查系统钥匙串权限
 * - 其余(DB/字段/未知):透传后端文案,笼统提示重试
 *
 * 注:不依赖具体 Err 子串判定成败(后端 Err 语义=失败),分类仅用于建议文案。
 */
function classifySaveError(raw: string): { title: string; detail: string } {
  if (raw.includes('钥匙串') || raw.includes('迁移')) {
    return {
      title: t('settings.saveErrKeyringTitle'),
      detail: t('settings.saveErrKeyringDetail', { msg: raw }),
    }
  }
  return {
    title: t('settings.saveErrGenericTitle'),
    detail: t('settings.saveErrGenericDetail', { msg: raw }),
  }
}

async function saveProvider() {
  // apiKey:新建必填;编辑可留空(不改,FR-S1 后端保留原 DB 值)
  const needKey = !providerForm.editId
  if (!providerForm.name || !providerForm.baseUrl || (needKey && !providerForm.apiKey) || !providerForm.defaultModel) {
    emit('toast', t('settings.toastSaveIncomplete'), 'warning')
    return
  }
  providerForm.saving = true
  try {
    await aiApi.saveProvider({
      id: providerForm.editId || undefined,
      name: providerForm.name,
      providerType: providerForm.providerType,
      baseUrl: providerForm.baseUrl,
      apiKey: providerForm.apiKey,
      defaultModel: providerForm.defaultModel,
      modelConfigs: providerForm.models,
    })
    // P0-2: 成功明确反馈——清错误横幅 + 关表单 + 绿色「已保存」toast
    providerForm.saveError = null
    providerForm.visible = false
    providerForm.savedSnapshot = ''  // L19: 关表单清快照
    await loadProviders()
    emit('toast', t('settings.toastSaved'), 'success')
  } catch (e) {
    // P0-2: 失败时①表单保持编辑态(不关)②表单内常驻错误横幅(分类建议,非自隐)
    //       ③仍发 error toast(用户视线不在表单时仍可见),双通道确保反馈不被忽略。
    const raw = errMsg(e)
    console.error(t('settings.toastSaveFail', { msg: raw }), e)
    const { title, detail } = classifySaveError(raw)
    providerForm.saveError = title + '\n' + detail
    emit('toast', t('settings.toastSaveFail', { msg: raw }), 'error')
  } finally {
    providerForm.saving = false
  }
}

async function deleteProvider(id: string) {
  const p = aiProviders.value.find(x => x.id === id)
  if (!await props.confirmDialog(t('settings.confirmDeleteProvider', { name: p?.name || id }))) return
  try {
    await aiApi.deleteProvider(id)
    await loadProviders()
    emit('toast', t('settings.toastDeleted'), 'info')
  } catch (e) {
    console.error(t('settings.toastDeleteFail', { msg: errMsg(e) }), e)
    emit('toast', t('settings.toastDeleteFail', { msg: errMsg(e) }), 'error')
  }
}

async function setDefaultProvider(id: string) {
  try {
    await aiApi.setProvider(id)
    await loadProviders()
    emit('toast', t('settings.toastSetDefaultOk'), 'info')
  } catch (e) {
    console.error(t('settings.toastSetDefaultFail', { msg: errMsg(e) }), e)
    emit('toast', t('settings.toastSetDefaultFail', { msg: errMsg(e) }), 'error')
  }
}

// ============================================================
// F-260614-04c: 负载均衡池可编辑层(enabled / weight)
// 卡片即时调 IPC(对齐 syncConcurrencyConfig 模式);落库后后端 reload_provider_caps
// 重建 per_provider caps,下条消息即按新配置 acquire。
// 不走 saveProvider(全量 INSERT OR REPLACE 会触发 R-PD-1 密钥迁移分支,与 F-04c 轻量 UPDATE 路径冲突)。
// ============================================================
let _poolTimer: ReturnType<typeof setTimeout> | null = null

/**
 * toggle 即时生效(开关语义=立即入/出池,无 debounce 必要;但失败需回滚视图)。
 * 乐观更新本地数组,失败 revert + toast。
 */
async function onPoolToggle(p: AiProviderConfig, enabled: boolean) {
  const prev = p.enabled
  p.enabled = enabled
  try {
    await aiApi.updateProviderPool(p.id, enabled, p.weight ?? 50)
    emit('toast', t('settings.toastPoolUpdateOk'), 'info')
  } catch (e) {
    p.enabled = prev  // revert
    console.error(t('settings.toastPoolUpdateFail', { msg: errMsg(e) }), e)
    emit('toast', t('settings.toastPoolUpdateFail', { msg: errMsg(e) }), 'error')
  }
}

/**
 * weight 变更 debounce 300ms(防快速连续输入触发频繁 IPC),clamp [0,100] 对齐后端。
 * 双 clamp:前端 clamp 视觉输入 + 后端 command 再 clamp(防御性)。
 */
function onPoolWeightChange(p: AiProviderConfig, raw: number) {
  // NaN 兜底(清空输入):回退 50,防 IPC 传 NaN 后端 serde u32 失败
  const clamped = Math.min(100, Math.max(0, Number.isFinite(raw) ? Math.trunc(raw) : 50))
  const prev = p.weight
  p.weight = clamped
  if (_poolTimer) clearTimeout(_poolTimer)
  _poolTimer = setTimeout(async () => {
    try {
      await aiApi.updateProviderPool(p.id, p.enabled !== false, clamped)
      emit('toast', t('settings.toastPoolUpdateOk'), 'info')
    } catch (e) {
      p.weight = prev  // revert
      console.error(t('settings.toastPoolUpdateFail', { msg: errMsg(e) }), e)
      emit('toast', t('settings.toastPoolUpdateFail', { msg: errMsg(e) }), 'error')
    }
  }, 300)
}

function maskKey(key: string): string {
  if (!key || key.length < 8) return '••••••••'
  return key.slice(0, 4) + '••••••••' + key.slice(-4)
}

/** 暴露给 shell:loadProviders 供外部主动刷新 / Knowledge 读 aiProviders / 卸载清 timer。
 *  L19: isFormDirty + closeProviderForm 暴露,供外部(如导航切走前)查询/触发拦截。
 *  当前 Settings.vue 未接入 nav 拦截(不在本次改文件集),接口先行供后续 shell 接入。 */
defineExpose({
  aiProviders,
  loadProviders,
  isFormDirty,
  closeProviderForm,
  clearPoolTimer() { if (_poolTimer) clearTimeout(_poolTimer) },
})

// 阶段3 UX 重构:本面板改为 ai-model 类懒挂载,不再由 Settings.vue onMounted 调用,
// 改为自身挂载时拉取 provider 列表 + 同步 aiStore。
onMounted(loadProviders)
</script>

<style scoped>
/* 阶段1/3 UX 重构:panel/panel-header/empty-hint/btn 系列/setting-select/
   toggle/toggle-slider 已迁移到 settings.css 全局。
   本 scoped 仅保留 ProviderPanel 特有样式:provider-card/default-badge/
   pool-badge/detail 系列/form 系列/setting-input/btn-link/toggle--sm 等。
   注:注释内勿用斜杠分隔类名列表(会误闭合 CSS 注释)。

/* ===== 链接按钮(本面板特有,未进全局) ===== */
.btn-link { background: none; border: none; color: var(--df-accent); font-size: 12px; cursor: pointer; padding: 2px 6px; }
.btn-link:hover { text-decoration: underline; }
.btn-link-danger { color: var(--df-danger); }

/* ===== Provider 卡片 ===== */
.provider-list { display: flex; flex-direction: column; gap: 12px; }
.provider-card {
  background: var(--df-bg);
  border: 0.5px solid var(--df-border);
  border-radius: var(--df-radius);
  padding: 14px 16px;
}
.provider-card--default {
  border-color: var(--df-accent);
  box-shadow: inset 3px 0 0 var(--df-accent);
}
.provider-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}
.provider-info { display: flex; align-items: center; gap: 8px; }
.provider-name { font-size: 14px; font-weight: 500; color: var(--df-text); }
.default-badge {
  font-size: 10px;
  padding: 1px 6px;
  border-radius: var(--df-radius-xs);
  background: rgba(100,255,218,0.15);
  color: var(--df-success);
  font-weight: 500;
}
.provider-actions { display: flex; gap: 4px; }

.provider-detail { display: flex; flex-direction: column; gap: 6px; }
.detail-row { display: flex; align-items: flex-start; gap: 12px; }
.detail-label {
  font-size: 12px;
  color: var(--df-text-dim);
  min-width: 60px;
}
.detail-value {
  font-size: 12px;
  color: var(--df-text-secondary);
  font-family: var(--df-font-mono);
}
.detail-value.mask { color: var(--df-text-dim); letter-spacing: 1px; }

/* Provider 卡片 disabled 态(F-260614-04c:enabled=false 视觉弱化) */
.provider-card--disabled { opacity: 0.6; }
.pool-badge {
  font-size: 10px;
  padding: 1px 6px;
  border-radius: var(--df-radius-xs);
  background: rgba(255,107,107,0.15);
  color: var(--df-danger);
  font-weight: 500;
}

/* 负载均衡池行(toggle / weight 控件) */
.detail-row--pool { align-items: center; }
.detail-value--control { display: flex; align-items: center; gap: 8px; font-family: var(--df-font-mono); }
.pool-weight-hint { font-size: 11px; color: var(--df-text-dim); }

/* 表单(本面板特有输入控件样式;.setting-select 走全局) */
.form-grid { display: flex; flex-direction: column; gap: 14px; }
.form-field { display: flex; flex-direction: column; gap: 4px; }
.form-label { font-size: 12px; font-weight: 500; color: var(--df-text-secondary); }
/* 字段级辅助说明(默认模型提示等):弱化色 + 小字,与 label/input 区分层级。
   复用现有 --df-text-dim token(对齐 .empty-hint / .pool-weight-hint 风格)。 */
.form-field-hint { font-size: 11px; color: var(--df-text-dim); line-height: 1.5; }
.form-actions { display: flex; justify-content: flex-end; padding-top: 4px; }

/* P0-2: 保存失败持久错误横幅(常驻 form-actions 上方,醒目于自隐 toast) */
.save-error-banner {
  background: var(--df-danger-bg);
  border: 0.5px solid var(--df-danger);
  border-radius: var(--df-radius-sm);
  padding: 10px 12px;
}
.save-error-msg {
  margin: 0;
  font-family: var(--df-font-sans);
  font-size: 12px;
  line-height: 1.55;
  color: var(--df-danger);
  white-space: pre-wrap;       /* 保留 title\ndetail 换行 + 长建议自动折行 */
  word-break: break-word;
}

.setting-input { padding: 6px 12px; background: var(--df-bg); border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm); color: var(--df-text); font-size: 13px; outline: none; min-width: 260px; }
.setting-input:focus { border-color: var(--df-accent); }
.setting-input--sm { min-width: 0; width: 70px; padding: 4px 8px; font-size: 12px; }

/* Toggle 小号变体(本面板负载均衡池用;.toggle 基础走全局) */
.toggle--sm { width: 32px; height: 18px; }
.toggle--sm .toggle-slider::before { width: 12px; height: 12px; left: 3px; bottom: 3px; }
.toggle--sm input:checked + .toggle-slider::before { transform: translateX(14px); }

/* M32: 模型列表有未保存调整时的提示(弱 warning 色,贴合 model-list-hint 之下) */
.model-list-unsaved-hint {
  margin-top: 4px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--df-warning);
}
</style>
