<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import { NInput, NButton, NCheckbox, NSelect } from 'naive-ui'
import type { AgentClientType, McpServerSpec } from '@/api/acp'

const props = defineProps<{
  modelValue: {
    name: string
    display_name: string
    description: string
    binary_path: string
    args: string[]
    env: Record<string, string>
    enabled: boolean
    tool_allowlist: string[]
    client_type: AgentClientType
    mcp_servers: McpServerSpec[]
  }
  isEdit: boolean
  existingEnvKeys?: string[]
}>()

const clientTypeOptions: { label: string; value: AgentClientType }[] = [
  { label: 'Claude Code', value: 'claude_code' },
  { label: 'Codex', value: 'codex' },
  { label: 'Gemini CLI', value: 'gemini' },
  { label: '通用（仅注入 HOME）', value: 'generic' },
]

const emit = defineEmits<{
  (e: 'update:modelValue', value: typeof props.modelValue): void
  (e: 'submit'): void
  (e: 'cancel'): void
}>()

// v-model 双向同步，但必须打破「W1 回填 → local 变 → W2 emit → 父 modelValue 变 → W1 再回填」
// 的无限循环（曾导致表单任意输入/NSelect 选择即卡死浏览器）。
// 关键：W2 emit 出去的就是 local 本身（同一引用），父组件直接持有该引用。当 W1 看到的
// modelValue 与我们最近一次 emit 出去的值是同一个对象时，说明是回环而非真正的外部变更，
// 必须忽略；否则（如编辑模式下父组件 onMounted 用全新对象回填）才同步进 local。
const local = ref({ ...props.modelValue })
let lastEmitted: typeof props.modelValue | undefined
watch(
  () => props.modelValue,
  (v) => {
    if (v === lastEmitted) return
    local.value = { ...v }
  },
)
watch(
  local,
  (v) => {
    lastEmitted = v
    emit('update:modelValue', v)
  },
  { deep: true },
)

const argsText = computed({
  get: () => local.value.args.join('\n'),
  set: (v: string) => {
    local.value.args = v.split('\n').filter((x) => x.trim() !== '')
  },
})

const allowlistText = computed({
  get: () => (local.value.tool_allowlist ?? []).join('\n'),
  set: (v: string) => {
    local.value.tool_allowlist = v
      .split('\n')
      .map((x) => x.trim())
      .filter((x) => x !== '')
  },
})

interface EnvRow {
  id: string
  k: string
  v: string
}
const envEntries = ref<EnvRow[]>(
  Object.entries(local.value.env).map(([k, v]) => ({ id: crypto.randomUUID(), k, v })),
)

watch(
  envEntries,
  (entries) => {
    const m: Record<string, string> = {}
    for (const { k, v } of entries) {
      if (k.trim()) m[k] = v
    }
    local.value.env = m
  },
  { deep: true },
)

function addEnv() {
  envEntries.value.push({ id: crypto.randomUUID(), k: '', v: '' })
}
function removeEnv(idx: number) {
  envEntries.value.splice(idx, 1)
}

// ===== 第三方 MCP servers =====
// session/new 握手时随自家 ACW MCP 一并下发。stdio 是 ACP 基线；
// http/sse 在 agent 未声明对应能力时会被后端逐条跳过。

const mcpTypeOptions = [
  { label: 'stdio', value: 'stdio' },
  { label: 'http', value: 'http' },
  { label: 'sse', value: 'sse' },
]

interface McpRow {
  id: string
  name: string
  type: 'stdio' | 'http' | 'sse'
  command: string
  argsText: string
  envText: string
  url: string
  headersText: string
}

function kvToText(m?: Record<string, string>): string {
  return Object.entries(m ?? {})
    .map(([k, v]) => `${k}=${v}`)
    .join('\n')
}

function parseKV(s: string): Record<string, string> | undefined {
  const m: Record<string, string> = {}
  for (const line of s.split('\n')) {
    const t = line.trim()
    if (!t) continue
    const i = t.indexOf('=')
    if (i <= 0) continue
    m[t.slice(0, i).trim()] = t.slice(i + 1)
  }
  return Object.keys(m).length ? m : undefined
}

function specsToRows(specs: McpServerSpec[]): McpRow[] {
  return specs.map((s) => ({
    id: crypto.randomUUID(),
    name: s.name,
    type: s.type,
    command: s.command ?? '',
    argsText: (s.args ?? []).join('\n'),
    envText: kvToText(s.env),
    url: s.url ?? '',
    headersText: kvToText(s.headers),
  }))
}

function rowsToSpecs(rows: McpRow[]): McpServerSpec[] {
  return rows.map((r) => {
    const spec: McpServerSpec = { name: r.name.trim(), type: r.type }
    if (r.type === 'stdio') {
      spec.command = r.command.trim()
      const args = r.argsText.split('\n').filter((x) => x.trim() !== '')
      if (args.length) spec.args = args
      const env = parseKV(r.envText)
      if (env) spec.env = env
    } else {
      spec.url = r.url.trim()
      const headers = parseKV(r.headersText)
      if (headers) spec.headers = headers
    }
    return spec
  })
}

const mcpRows = ref<McpRow[]>(specsToRows(local.value.mcp_servers ?? []))

watch(
  mcpRows,
  (rows) => {
    local.value.mcp_servers = rowsToSpecs(rows)
  },
  { deep: true },
)

// 编辑模式下 modelValue 异步加载完成后回填行（避免与上面的 watch 互相触发）
watch(
  () => props.modelValue.mcp_servers,
  (v) => {
    if (JSON.stringify(v ?? []) !== JSON.stringify(rowsToSpecs(mcpRows.value))) {
      mcpRows.value = specsToRows(v ?? [])
    }
  },
)

function addMcpServer() {
  mcpRows.value.push({
    id: crypto.randomUUID(),
    name: '',
    type: 'http',
    command: '',
    argsText: '',
    envText: '',
    url: '',
    headersText: '',
  })
}
function removeMcpServer(idx: number) {
  mcpRows.value.splice(idx, 1)
}
</script>

<template>
  <form class="space-y-4" @submit.prevent="emit('submit')">
    <div>
      <label class="block text-sm font-medium mb-1">Name</label>
      <NInput v-model:value="local.name" :readonly="isEdit" placeholder="claude_code" />
      <p v-if="isEdit" class="text-xs text-gray-500 mt-1">Name cannot be changed after creation</p>
    </div>
    <div>
      <label class="block text-sm font-medium mb-1">Display Name</label>
      <NInput v-model:value="local.display_name" placeholder="Claude Code" />
    </div>
    <div>
      <label class="block text-sm font-medium mb-1">Description</label>
      <NInput
        v-model:value="local.description"
        type="textarea"
        :autosize="{ minRows: 2, maxRows: 6 }"
      />
    </div>
    <div>
      <label class="block text-sm font-medium mb-1">客户端类型</label>
      <NSelect v-model:value="local.client_type" :options="clientTypeOptions" />
      <p class="text-xs text-gray-500 mt-1">
        决定 session 启动时注入的配置目录变量与下方配置文件表单
      </p>
    </div>
    <div>
      <label class="block text-sm font-medium mb-1">Binary Path</label>
      <NInput
        v-model:value="local.binary_path"
        placeholder="claude-code or /usr/local/bin/claude-code"
      />
    </div>
    <div>
      <label class="block text-sm font-medium mb-1">Args (one per line)</label>
      <NInput
        v-model:value="argsText"
        type="textarea"
        :autosize="{ minRows: 3, maxRows: 8 }"
        placeholder="--acp"
      />
    </div>
    <div>
      <label class="block text-sm font-medium mb-1">Env</label>
      <p v-if="isEdit && existingEnvKeys?.length" class="text-xs text-gray-500 mb-2">
        Existing keys (values hidden): {{ existingEnvKeys.join(', ') }}. Edit below to replace
        entirely.
      </p>
      <div v-for="(e, idx) in envEntries" :key="e.id" class="flex gap-2 mb-2">
        <NInput v-model:value="e.k" placeholder="KEY" class="flex-1" />
        <NInput
          v-model:value="e.v"
          type="password"
          show-password-on="click"
          placeholder="value"
          class="flex-1"
        />
        <NButton quaternary @click="removeEnv(idx)"> × </NButton>
      </div>
      <NButton size="small" dashed @click="addEnv"> + Add </NButton>
    </div>
    <div>
      <label class="block text-sm font-medium mb-1">
        工具白名单（每行一个，命中则自动放行；填 * 全部放行；留空则全部需人工审批）
      </label>
      <NInput
        v-model:value="allowlistText"
        type="textarea"
        :autosize="{ minRows: 2, maxRows: 8 }"
        placeholder="fs/read_text_file&#10;safe.tool"
      />
    </div>
    <div>
      <label class="block text-sm font-medium mb-1">
        第三方 MCP Servers（session 启动时随平台 MCP 一并下发；名称 acw 为平台保留）
      </label>
      <div v-for="(m, idx) in mcpRows" :key="m.id" class="border rounded p-3 mb-2 space-y-2">
        <div class="flex gap-2">
          <NInput v-model:value="m.name" placeholder="名称（如 context7）" class="flex-1" />
          <NSelect v-model:value="m.type" :options="mcpTypeOptions" class="w-28" />
          <NButton quaternary @click="removeMcpServer(idx)"> × </NButton>
        </div>
        <template v-if="m.type === 'stdio'">
          <NInput v-model:value="m.command" placeholder="command（如 npx）" />
          <NInput
            v-model:value="m.argsText"
            type="textarea"
            :autosize="{ minRows: 1, maxRows: 5 }"
            placeholder="args（每行一个，如 -y&#10;@upstash/context7-mcp）"
          />
          <NInput
            v-model:value="m.envText"
            type="textarea"
            :autosize="{ minRows: 1, maxRows: 5 }"
            placeholder="env（每行 KEY=VALUE）"
          />
        </template>
        <template v-else>
          <NInput v-model:value="m.url" placeholder="URL（如 https://mcp.example.com/mcp）" />
          <NInput
            v-model:value="m.headersText"
            type="textarea"
            :autosize="{ minRows: 1, maxRows: 5 }"
            placeholder="headers（每行 KEY=VALUE，如 Authorization=Bearer xxx）"
          />
          <p v-if="m.type === 'sse'" class="text-xs text-gray-500">
            注意：codex 不支持 sse 类型，会在启动时被跳过
          </p>
        </template>
      </div>
      <NButton size="small" dashed @click="addMcpServer"> + 添加 MCP Server </NButton>
    </div>
    <div class="flex items-center gap-2">
      <NCheckbox v-model:checked="local.enabled"> Enabled </NCheckbox>
    </div>
    <div class="flex gap-2">
      <NButton type="primary" attr-type="submit"> Save </NButton>
      <NButton @click="emit('cancel')"> Cancel </NButton>
    </div>
  </form>
</template>
