<template>
  <!-- ═══ 设置导入 / 导出(阶段6 UX 重构)═══
       导出:聚合 appSettings 全 KV(已解析)+ 知识库配置 → JSON 文件下载
            (默认不含 provider keyring 密钥,scope 标注)
       导入:input[type=file] 读 JSON → 解析校验 → useConfirm 覆盖确认
            → 逐 KV settings.set + knowledgeApi.saveConfig → toast 提示
       复用 settings.css 全局 .btn/.btn-primary/.btn-ghost/.btn-sm;
       confirm/toast 由父 Settings.vue 经 props 注入,共用壳层弹层与 toast。 -->
  <div class="settings-io">
    <button class="btn btn-ghost btn-sm" :disabled="busy" @click="onExport">
      ⬆ {{ $t('settings.export') }}
    </button>
    <button class="btn btn-ghost btn-sm" :disabled="busy" @click="triggerImport">
      ⬇ {{ $t('settings.import') }}
    </button>
    <!-- 隐藏的文件选择器(点击「导入」触发) -->
    <input
      ref="fileInputRef"
      type="file"
      accept="application/json,.json"
      class="file-input"
      @change="onFilePicked"
    />
  </div>
</template>

<script setup lang="ts">
// ============================================================
// 阶段6 导入 / 导出 — 纯前端实现
// ------------------------------------------------------------
// 导出:从 appSettings.cache 取已解析的全 KV(类型正确,非后端原始 JSON 字符串),
//   叠加 knowledgeApi.getConfig(),组成 {metadata, appSettings, knowledge} JSON。
//   用 Blob + a[download] 下载(不依赖 Tauri fs/dialog 权限)。
// 导入:<input type=file> + FileReader.readAsText → 解析 → metadata.scope 校验
//   → confirmDialog 覆盖确认 → appSettings.set 逐 KV + knowledgeApi.saveConfig
//   → toast 反馈(并发/轮次需重启或下次对话生效)。
// 安全:scope 仅含 appSettings + 知识库配置,默认不含 provider keyring 密钥
//   (密钥在独立 store,本流程不触及)。
// ============================================================

import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppSettingsStore } from '@/stores/appSettings'
import { knowledgeApi } from '@/api/knowledge'
import type { KnowledgeConfig } from '@/api/types'

const props = defineProps<{
  /** 覆盖确认弹层(复用 Settings.vue 壳层 confirmState) */
  confirmDialog: (msg: string, dangerLabel?: string) => Promise<boolean>
  /** toast 反馈通道(复用 Settings.vue 壳层 showToast) */
  toast: (msg: string, type?: 'error' | 'warning' | 'info') => void
}>()

const { t } = useI18n()
const appSettings = useAppSettingsStore()

const busy = ref(false)
const fileInputRef = ref<HTMLInputElement | null>(null)

/** 导出的 JSON 备份载荷结构 */
interface SettingsBackup {
  metadata: {
    version: number
    date: string
    scope: 'appSettings+knowledge'
    app: string
  }
  appSettings: Record<string, unknown>
  knowledge: KnowledgeConfig | null
}

/** 当前日期戳(用于导出文件名:devflow-settings-YYYYMMDD.json) */
function dateStamp(): string {
  const d = new Date()
  const y = d.getFullYear()
  const m = String(d.getMonth() + 1).padStart(2, '0')
  const day = String(d.getDate()).padStart(2, '0')
  return `${y}${m}${day}`
}

// ============================================================
// 导出
// ============================================================
async function onExport() {
  if (busy.value) return
  busy.value = true
  try {
    // appSettings.cache 为已解析(反序列化)值的响应式缓存 —— 取其快照,
    // 保证导出的 JSON 值类型与运行时一致(非后端原始 JSON 字符串)
    const allKv: Record<string, unknown> = { ...appSettings.cache }
    // 知识库配置独立 IPC 读取(不在 app_settings 表)
    let knowledge: KnowledgeConfig | null = null
    try {
      knowledge = await knowledgeApi.getConfig()
    } catch (e) {
      // 知识库读取失败不阻断导出(appSettings 仍可导),仅 console 记录
      console.error('[设置导出] 读取知识库配置失败:', e)
    }

    const payload: SettingsBackup = {
      metadata: {
        version: 1,
        date: new Date().toISOString(),
        scope: 'appSettings+knowledge',
        app: 'devflow',
      },
      appSettings: allKv,
      knowledge,
    }

    const json = JSON.stringify(payload, null, 2)
    // Blob + a[download] 下载:webview 内原生支持,无需 Tauri fs 权限
    const blob = new Blob([json], { type: 'application/json' })
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = `devflow-settings-${dateStamp()}.json`
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(url)
  } catch (e) {
    props.toast(t('settings.exportFail', { msg: errMsg(e) }), 'error')
  } finally {
    busy.value = false
  }
}

// ============================================================
// 导入
// ============================================================
function triggerImport() {
  // 重置 value 以便「选同一文件」也能再次触发 change(否则 onChange 不再回调)
  if (fileInputRef.value) fileInputRef.value.value = ''
  fileInputRef.value?.click()
}

async function onFilePicked(e: Event) {
  const input = e.target as HTMLInputElement
  const file = input.files?.[0]
  if (!file) return

  if (busy.value) return
  busy.value = true
  try {
    const text = await readFileAsText(file)
    const parsed = parseJsonLoose(text)
    if (!parsed || !isSettingsBackup(parsed)) {
      props.toast(t('settings.importInvalidJson'), 'error')
      return
    }

    // 覆盖确认(复用壳层 confirmState 弹层;用户取消则放弃)
    const ok = await props.confirmDialog(t('settings.importConfirm'), '')
    if (!ok) return

    // 逐 KV 写回 appSettings(经 store.set 同时更新缓存与 debounced 落库)
    const kv = parsed.appSettings ?? {}
    for (const [k, v] of Object.entries(kv)) {
      await appSettings.set(k, v)
    }
    // 知识库配置写回(独立 IPC)
    if (parsed.knowledge) {
      await knowledgeApi.saveConfig(parsed.knowledge)
    }

    props.toast(t('settings.importSuccess'), 'info')
  } catch (e) {
    props.toast(t('settings.importFail', { msg: errMsg(e) }), 'error')
  } finally {
    busy.value = false
  }
}

// ============================================================
// 工具
// ============================================================

/** FileReader 读文本(替代 file.text() 以兼容更早 webview) */
function readFileAsText(file: File): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = () => resolve(String(reader.result ?? ''))
    reader.onerror = () => reject(reader.error ?? new Error('read error'))
    reader.readAsText(file)
  })
}

/** 宽松 JSON.parse:失败返回 null(调用方据此报「格式不符」) */
function parseJsonLoose(text: string): unknown {
  try {
    return JSON.parse(text)
  } catch {
    return null
  }
}

/** 结构校验:必须含 metadata.scope === 'appSettings+knowledge' 且 version 为数 */
function isSettingsBackup(obj: unknown): obj is SettingsBackup {
  if (typeof obj !== 'object' || obj === null) return false
  const o = obj as Record<string, unknown>
  const meta = o.metadata as Record<string, unknown> | undefined
  if (!meta || meta.scope !== 'appSettings+knowledge') return false
  if (typeof meta.version !== 'number') return false
  // appSettings 必须为对象(允许空对象);knowledge 允许 null 或对象
  if (typeof o.appSettings !== 'object' || o.appSettings === null) return false
  return true
}

/** 提取错误信息文案(兜底 Error.message 或 String(e)) */
function errMsg(e: unknown): string {
  if (e instanceof Error) return e.message
  return String(e)
}
</script>

<style scoped>
/* 导入导出按钮组:横向排列,复用全局 .btn/.btn-ghost/.btn-sm */
.settings-io {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
}
/* 隐藏文件选择器(仅以编程方式 click 触发) */
.file-input {
  position: absolute;
  width: 0;
  height: 0;
  opacity: 0;
  pointer-events: none;
}
</style>
