<script setup lang="ts">
// AgentKind 配置文件管理面板（admin only，仅编辑模式可用）。
//
// 两种编辑形态：
//  - 常用配置表单：针对 claude_code / codex / gemini 的规范配置文件
//    （.claude/settings.json / .codex/config.toml / .gemini/settings.json），
//    只管理常用字段，保存时合并回原文件（未识别字段原样保留）。
//  - 高级模式：任意 rel_path 的原始文本编辑，按扩展名做 JSON/TOML 校验。
//
// 配置在 spawn 前物化到受管 home 并通过 CLAUDE_CONFIG_DIR / CODEX_HOME /
// GEMINI_CLI_HOME 注入；agent 运行期对受管文件的改动会在下次 spawn 被 DB 版本覆盖。
import { ref, computed, onMounted, watch } from 'vue'
import { NInput, NButton, NSelect, NSpin } from 'naive-ui'
import { parse as parseToml, stringify as stringifyToml } from 'smol-toml'
import { acpApi, type AgentConfigFile, type AgentClientType } from '@/api/acp'
import { useConfirm } from '@/composables/useConfirm'

const props = defineProps<{
  kindId: string
  clientType: AgentClientType
}>()

const files = ref<AgentConfigFile[]>([])
const loading = ref(false)
const error = ref('')
const confirm = useConfirm()

// ===== 客户端规范配置文件 =====

const canonicalPath = computed(() => {
  switch (props.clientType) {
    case 'claude_code':
      return '.claude/settings.json'
    case 'codex':
      return '.codex/config.toml'
    case 'gemini':
      return '.gemini/settings.json'
    default:
      return null
  }
})

interface FormState {
  model: string
  // claude_code
  defaultMode: string
  allow: string
  deny: string
  ask: string
  // codex
  approvalPolicy: string | null
  sandboxMode: string | null
}
const form = ref<FormState>({
  model: '',
  defaultMode: '',
  allow: '',
  deny: '',
  ask: '',
  approvalPolicy: null,
  sandboxMode: null,
})

const approvalPolicyOptions = [
  { label: 'untrusted', value: 'untrusted' },
  { label: 'on-request', value: 'on-request' },
  { label: 'never', value: 'never' },
]
const sandboxModeOptions = [
  { label: 'read-only', value: 'read-only' },
  { label: 'workspace-write', value: 'workspace-write' },
  { label: 'danger-full-access', value: 'danger-full-access' },
]

function findFile(relPath: string): AgentConfigFile | undefined {
  return files.value.find((f) => f.rel_path === relPath)
}

function linesToArr(s: string): string[] {
  return s
    .split('\n')
    .map((x) => x.trim())
    .filter((x) => x !== '')
}

// loadForm 从规范文件解析常用字段；文件缺失或解析失败时保持空表单。
function loadForm() {
  const f = canonicalPath.value ? findFile(canonicalPath.value) : undefined
  form.value = {
    model: '',
    defaultMode: '',
    allow: '',
    deny: '',
    ask: '',
    approvalPolicy: null,
    sandboxMode: null,
  }
  if (!f) return
  try {
    if (props.clientType === 'codex') {
      const doc = parseToml(f.content) as Record<string, unknown>
      form.value.model = typeof doc.model === 'string' ? doc.model : ''
      form.value.approvalPolicy =
        typeof doc.approval_policy === 'string' ? doc.approval_policy : null
      form.value.sandboxMode = typeof doc.sandbox_mode === 'string' ? doc.sandbox_mode : null
    } else if (props.clientType === 'claude_code') {
      const doc = JSON.parse(f.content) as Record<string, unknown>
      form.value.model = typeof doc.model === 'string' ? doc.model : ''
      const perms = (doc.permissions ?? {}) as Record<string, unknown>
      form.value.defaultMode = typeof perms.defaultMode === 'string' ? perms.defaultMode : ''
      form.value.allow = Array.isArray(perms.allow) ? perms.allow.join('\n') : ''
      form.value.deny = Array.isArray(perms.deny) ? perms.deny.join('\n') : ''
      form.value.ask = Array.isArray(perms.ask) ? perms.ask.join('\n') : ''
    } else if (props.clientType === 'gemini') {
      const doc = JSON.parse(f.content) as Record<string, unknown>
      const model = (doc.model ?? {}) as Record<string, unknown>
      form.value.model = typeof model.name === 'string' ? model.name : ''
    }
  } catch {
    // 文件内容非法时表单留空；用户可在高级模式修复
  }
}

// saveForm 把表单字段合并回规范文件（保留未识别字段），空值删除对应 key。
async function saveForm() {
  if (!canonicalPath.value) return
  const existing = findFile(canonicalPath.value)
  try {
    if (props.clientType === 'codex') {
      let doc: Record<string, unknown> = {}
      if (existing) doc = parseToml(existing.content) as Record<string, unknown>
      if (form.value.model) doc.model = form.value.model
      else delete doc.model
      if (form.value.approvalPolicy) doc.approval_policy = form.value.approvalPolicy
      else delete doc.approval_policy
      if (form.value.sandboxMode) doc.sandbox_mode = form.value.sandboxMode
      else delete doc.sandbox_mode
      await upsert(canonicalPath.value, stringifyToml(doc) + '\n')
    } else {
      let doc: Record<string, unknown> = {}
      if (existing) doc = JSON.parse(existing.content) as Record<string, unknown>
      if (props.clientType === 'claude_code') {
        if (form.value.model) doc.model = form.value.model
        else delete doc.model
        const perms = (doc.permissions ?? {}) as Record<string, unknown>
        if (form.value.defaultMode) perms.defaultMode = form.value.defaultMode
        else delete perms.defaultMode
        for (const key of ['allow', 'deny', 'ask'] as const) {
          const arr = linesToArr(form.value[key])
          if (arr.length) perms[key] = arr
          else delete perms[key]
        }
        if (Object.keys(perms).length) doc.permissions = perms
        else delete doc.permissions
      } else if (props.clientType === 'gemini') {
        const model = (doc.model ?? {}) as Record<string, unknown>
        if (form.value.model) model.name = form.value.model
        else delete model.name
        if (Object.keys(model).length) doc.model = model
        else delete doc.model
      }
      await upsert(canonicalPath.value, JSON.stringify(doc, null, 2) + '\n')
    }
  } catch (e) {
    error.value = `保存失败：${e instanceof Error ? e.message : '现有文件内容无法解析，请在高级模式修复'}`
  }
}

// ===== 高级模式：原始文件编辑 =====

const editingPath = ref<string | null>(null)
const editingContent = ref('')
const editingIsNew = ref(false)
const newRelPath = ref('')
const syntaxError = ref('')

// 按 client_type 提供的新建模板
const templates = computed<{ label: string; rel_path: string; content: string }[]>(() => {
  switch (props.clientType) {
    case 'claude_code':
      return [
        {
          label: '.claude/settings.json',
          rel_path: '.claude/settings.json',
          content: '{\n  "permissions": {\n    "allow": [],\n    "deny": []\n  }\n}\n',
        },
      ]
    case 'codex':
      return [
        {
          label: '.codex/config.toml',
          rel_path: '.codex/config.toml',
          content: 'approval_policy = "on-request"\nsandbox_mode = "workspace-write"\n',
        },
        {
          label: '.codex/auth.json',
          rel_path: '.codex/auth.json',
          content: '{\n  "OPENAI_API_KEY": ""\n}\n',
        },
      ]
    case 'gemini':
      return [
        {
          label: '.gemini/settings.json',
          rel_path: '.gemini/settings.json',
          content: '{\n  "mcpServers": {}\n}\n',
        },
      ]
    default:
      return []
  }
})

function validateSyntax(relPath: string, content: string): string {
  const lower = relPath.toLowerCase()
  try {
    if (lower.endsWith('.json')) JSON.parse(content)
    else if (lower.endsWith('.toml')) parseToml(content)
  } catch (e) {
    return e instanceof Error ? e.message : 'syntax error'
  }
  return ''
}

watch([editingContent, newRelPath], () => {
  const p = editingIsNew.value ? newRelPath.value : (editingPath.value ?? '')
  syntaxError.value = p ? validateSyntax(p, editingContent.value) : ''
})

function startEdit(f: AgentConfigFile) {
  editingIsNew.value = false
  editingPath.value = f.rel_path
  editingContent.value = f.content
  syntaxError.value = ''
}

function startNew(template?: { rel_path: string; content: string }) {
  editingIsNew.value = true
  editingPath.value = null
  newRelPath.value = template?.rel_path ?? ''
  editingContent.value = template?.content ?? ''
  syntaxError.value = ''
}

function cancelEdit() {
  editingPath.value = null
  editingIsNew.value = false
  newRelPath.value = ''
  editingContent.value = ''
  syntaxError.value = ''
}

async function saveEdit() {
  const relPath = editingIsNew.value ? newRelPath.value.trim() : editingPath.value
  if (!relPath) {
    error.value = '请填写文件路径'
    return
  }
  if (syntaxError.value) return
  await upsert(relPath, editingContent.value)
  cancelEdit()
}

async function upsert(relPath: string, content: string) {
  error.value = ''
  try {
    await acpApi.configFiles.upsert(props.kindId, relPath, content)
    await refresh()
  } catch (e) {
    error.value = `保存失败：${e instanceof Error ? e.message : 'unknown'}`
  }
}

async function removeFile(relPath: string) {
  const ok = await confirm(`删除配置文件 ${relPath}？`)
  if (!ok) return
  error.value = ''
  try {
    await acpApi.configFiles.remove(props.kindId, relPath)
    await refresh()
  } catch (e) {
    error.value = `删除失败：${e instanceof Error ? e.message : 'unknown'}`
  }
}

async function refresh() {
  loading.value = true
  try {
    files.value = await acpApi.configFiles.list(props.kindId)
    loadForm()
  } catch (e) {
    error.value = `加载失败：${e instanceof Error ? e.message : 'unknown'}`
  } finally {
    loading.value = false
  }
}

onMounted(refresh)
watch(() => props.clientType, loadForm)
</script>

<template>
  <div class="space-y-4">
    <div>
      <h2 class="text-lg font-semibold">客户端配置文件</h2>
      <p class="text-xs text-gray-500 mt-1">
        配置存数据库，session 启动前物化到受管目录并通过
        <code v-if="clientType === 'claude_code'">CLAUDE_CONFIG_DIR</code>
        <code v-else-if="clientType === 'codex'">CODEX_HOME</code>
        <code v-else-if="clientType === 'gemini'">GEMINI_CLI_HOME</code>
        <code v-else>HOME</code>
        指向该目录。agent 运行期对这些文件的修改会在下次启动时被覆盖。
      </p>
    </div>

    <p v-if="error" class="text-sm text-red-600">
      {{ error }}
    </p>

    <NSpin :show="loading">
      <!-- 常用配置表单（已知客户端类型） -->
      <div v-if="canonicalPath" class="border rounded p-4 space-y-3">
        <div class="text-sm font-medium">
          常用配置
          <span class="text-xs text-gray-500 font-normal"
            >（写入 {{ canonicalPath }}，其余字段保留）</span
          >
        </div>
        <div>
          <label class="block text-sm mb-1">Model</label>
          <NInput
            v-model:value="form.model"
            :placeholder="
              clientType === 'codex'
                ? 'gpt-5.5'
                : clientType === 'gemini'
                  ? 'gemini-2.5-pro'
                  : 'claude-sonnet-4-6'
            "
          />
        </div>
        <template v-if="clientType === 'claude_code'">
          <div>
            <label class="block text-sm mb-1">permissions.defaultMode</label>
            <NInput v-model:value="form.defaultMode" placeholder="acceptEdits" />
          </div>
          <div class="grid grid-cols-3 gap-2">
            <div>
              <label class="block text-sm mb-1">allow（每行一条）</label>
              <NInput
                v-model:value="form.allow"
                type="textarea"
                :autosize="{ minRows: 3, maxRows: 8 }"
                placeholder="Bash(git status)"
              />
            </div>
            <div>
              <label class="block text-sm mb-1">deny（每行一条）</label>
              <NInput
                v-model:value="form.deny"
                type="textarea"
                :autosize="{ minRows: 3, maxRows: 8 }"
                placeholder="Read(~/.ssh/**)"
              />
            </div>
            <div>
              <label class="block text-sm mb-1">ask（每行一条）</label>
              <NInput
                v-model:value="form.ask"
                type="textarea"
                :autosize="{ minRows: 3, maxRows: 8 }"
                placeholder="Write(src/**)"
              />
            </div>
          </div>
        </template>
        <template v-if="clientType === 'codex'">
          <div class="grid grid-cols-2 gap-2">
            <div>
              <label class="block text-sm mb-1">approval_policy</label>
              <NSelect
                v-model:value="form.approvalPolicy"
                :options="approvalPolicyOptions"
                clearable
              />
            </div>
            <div>
              <label class="block text-sm mb-1">sandbox_mode</label>
              <NSelect v-model:value="form.sandboxMode" :options="sandboxModeOptions" clearable />
            </div>
          </div>
        </template>
        <NButton size="small" type="primary" @click="saveForm"> 保存常用配置 </NButton>
      </div>

      <!-- 高级模式：文件列表 + 原始编辑 -->
      <div class="border rounded p-4 space-y-3 mt-4">
        <div class="text-sm font-medium">全部配置文件（高级模式）</div>
        <div v-if="files.length === 0" class="text-sm text-gray-500">暂无配置文件</div>
        <div
          v-for="f in files"
          :key="f.rel_path"
          class="flex items-center justify-between text-sm border-b pb-1"
        >
          <code>{{ f.rel_path }}</code>
          <div class="flex gap-2 items-center">
            <span class="text-xs text-gray-400">{{ f.updated_at }}</span>
            <NButton size="tiny" @click="startEdit(f)"> 编辑 </NButton>
            <NButton size="tiny" quaternary type="error" @click="removeFile(f.rel_path)">
              删除
            </NButton>
          </div>
        </div>

        <div class="flex gap-2 flex-wrap">
          <NButton size="small" dashed @click="startNew()"> + 新建文件 </NButton>
          <NButton
            v-for="t in templates.filter((x) => !files.some((f) => f.rel_path === x.rel_path))"
            :key="t.rel_path"
            size="small"
            dashed
            @click="startNew(t)"
          >
            + {{ t.label }}
          </NButton>
        </div>

        <div v-if="editingIsNew || editingPath !== null" class="space-y-2 pt-2">
          <div v-if="editingIsNew">
            <label class="block text-sm mb-1"
              >文件路径（相对受管 home，如 .claude/settings.json）</label
            >
            <NInput v-model:value="newRelPath" placeholder=".claude/settings.json" />
          </div>
          <div v-else>
            <code class="text-sm">{{ editingPath }}</code>
          </div>
          <NInput
            v-model:value="editingContent"
            type="textarea"
            class="font-mono"
            :autosize="{ minRows: 8, maxRows: 24 }"
          />
          <p v-if="syntaxError" class="text-xs text-red-600">语法错误：{{ syntaxError }}</p>
          <div class="flex gap-2">
            <NButton size="small" type="primary" :disabled="!!syntaxError" @click="saveEdit">
              保存
            </NButton>
            <NButton size="small" @click="cancelEdit"> 取消 </NButton>
          </div>
        </div>
      </div>
    </NSpin>
  </div>
</template>
