<template>
  <div class="api-skill-page">
    <div class="api-skill-header">
      <h1>UAP管理API接口skill（X-Admin-Api-Key）</h1>
      <p class="lead">
        在 Cursor 等编辑器中为本仓库或业务项目提供 <strong>UAP 管理 API 接口对接</strong> 相关的助手能力：使用 <code>X-Admin-Api-Key</code> 永久 Key 调用 <code>/admin-api/users</code>、<code>/admin-api/organizations</code> 接口。下载下方 Skill 文件，按说明放入技能目录即可。
      </p>
    </div>

    <el-card shadow="never" class="block-card">
      <template #header>
        <span>这是什么</span>
      </template>
      <p>
        Cursor <strong>Skill</strong> 是一段给 AI 读的说明（Markdown），用于在对话里约束「如何使用永久 API Key 调用统一认证平台的管理接口、scopes 权限模型、白名单字段约束、操作审计与安全注意点」等。
      </p>
      <p>
        与「UAP应用接口skill」是两套**互不兼容**的对接体系，各自一份 Skill：
      </p>
      <ul class="sub-list">
        <li><strong>UAP应用接口skill（uap-api）</strong>：业务应用对接 UAP，做登录 / SSO / 消息推送 / 用户映射，使用 <code>APP_ID + APP_SECRET</code>（HMAC 签名）或 <code>APP_ACCESS_TOKEN</code>。</li>
        <li><strong>本 Skill（uap-admin-api）</strong>：平台运维 / 总部系统替超管直接管理 UAP 上的用户与组织，仅用一把 <code>X-Admin-Api-Key</code> Header；不签名、不发消息。</li>
      </ul>
    </el-card>

    <el-card shadow="never" class="block-card">
      <template #header>
        <span>使用前必读</span>
      </template>
      <ul class="sub-list">
        <li><strong>永久凭证</strong>：API Key 一经创建即长期有效，丢失只能<strong>吊销重发</strong>；不可物理删除，仅支持 启用 / 禁用 / 吊销 三种状态。</li>
        <li><strong>明文仅显示一次</strong>：创建 Key 时平台只在响应里返回一次完整明文（<code>key_once</code>），之后只能在后台看到前缀与统计信息。请用户当场妥善保存到服务端环境变量。</li>
        <li><strong>仅服务端</strong>：<code>X-Admin-Api-Key</code> 等同于「超管手里几个常用功能键」，<strong>禁止</strong>写入前端 / 客户端 / git 仓库。</li>
        <li><strong>白名单字段</strong>：更新用户接口（<code>PUT /admin-api/users/{id}</code>）<strong>仅允许</strong> <code>name / english_name / password / organization_id</code>，传 <code>mobile / role / status / is_deleted</code> 直接 422。改手机号、改角色、删除用户须到 UAP 后台人工处理。</li>
        <li><strong>scope 权限</strong>：Key 由超管按需分配 <code>user:read / user:write / org:read / org:write</code> 子集，缺 scope 平台直接 403，不会偷偷放行。</li>
      </ul>
    </el-card>

    <el-card shadow="never" class="block-card">
      <template #header>
        <span>安装步骤（Cursor）</span>
      </template>
      <ol class="steps">
        <li>点击下方按钮下载 <code>uap-admin-api.zip</code>（内含 <code>SKILL.md</code> 与 <code>LLM_ADMIN_API_INTEGRATION.md</code>，两份缺一则 AI 无法读到接口细节）。</li>
        <li>
          选择一个技能目录（二选一）：
          <ul class="sub-list">
            <li><strong>项目级</strong>：<code>&lt;仓库根&gt;/.cursor/skills/</code> —— 仅当前项目可用，会随仓库共享。</li>
            <li><strong>用户级</strong>：<code>~/.cursor/skills/</code>（Windows 为 <code>%USERPROFILE%\.cursor\skills\</code>）—— 本机所有项目都可用。</li>
          </ul>
        </li>
        <li>把 <code>uap-admin-api.zip</code> 解压到上一步选择的目录，最终路径形如 <code>.../.cursor/skills/uap-admin-api/SKILL.md</code> 与 <code>.../.cursor/skills/uap-admin-api/LLM_ADMIN_API_INTEGRATION.md</code>。</li>
        <li>重启 Cursor 或重新打开项目后，在对话中说明「按 UAP 管理 API skill 对接」即可触发相关约束。</li>
      </ol>
      <p class="hint">
        具体目录名以 Cursor 当前版本的 <a href="https://cursor.com/docs" target="_blank" rel="noopener noreferrer">官方文档</a> 为准；不同版本可能使用 <code>skills</code> 或规则目录，请与本地设置对照。
      </p>
    </el-card>

    <el-card shadow="never" class="block-card">
      <template #header>
        <span>下载</span>
      </template>
      <div class="download-row">
        <el-button type="primary" :loading="zipping" @click="downloadZip">
          <el-icon class="btn-icon"><Download /></el-icon>
          {{ zipping ? '正在打包…' : '下载 uap-admin-api.zip（含两份文件）' }}
        </el-button>
      </div>
      <el-alert
        v-if="zipError"
        type="error"
        :closable="false"
        show-icon
        class="zip-error"
        :title="zipError"
      />
      <p class="hint">
        zip 解压后得到 <code>uap-admin-api/</code> 目录，直接放入 <code>.cursor/skills/</code> 即可。<code>SKILL.md</code> 对 <code>LLM_ADMIN_API_INTEGRATION.md</code> 的引用为相对路径。
      </p>
      <details class="fallback">
        <summary>无法打包？分开下载</summary>
        <div class="download-row fallback-row">
          <a :href="skillUrl" download="SKILL.md" class="download-anchor">
            <el-button size="small">下载 SKILL.md</el-button>
          </a>
          <a :href="referenceUrl" download="LLM_ADMIN_API_INTEGRATION.md" class="download-anchor">
            <el-button size="small">下载 LLM_ADMIN_API_INTEGRATION.md</el-button>
          </a>
        </div>
        <div class="path-list">
          <div class="file-path">静态路径：{{ skillUrl }}</div>
          <div class="file-path">静态路径：{{ referenceUrl }}</div>
        </div>
        <p class="hint">两份文件必须放在同一目录（目录名 <code>uap-admin-api</code>）。</p>
      </details>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import JSZip from 'jszip'
import { Download } from '@element-plus/icons-vue'

const skillUrl = '/skills/uap-admin-api/SKILL.md'
const referenceUrl = '/skills/uap-admin-api/LLM_ADMIN_API_INTEGRATION.md'

const zipping = ref(false)
const zipError = ref('')

async function fetchText(url: string): Promise<string> {
  const resp = await fetch(url, { cache: 'no-cache' })
  if (!resp.ok) {
    throw new Error(`下载 ${url} 失败：HTTP ${resp.status}`)
  }
  return await resp.text()
}

async function downloadZip() {
  if (zipping.value) return
  zipping.value = true
  zipError.value = ''
  try {
    const [skillText, referenceText] = await Promise.all([
      fetchText(skillUrl),
      fetchText(referenceUrl),
    ])
    const zip = new JSZip()
    const folder = zip.folder('uap-admin-api')
    if (!folder) throw new Error('创建 zip 目录失败')
    folder.file('SKILL.md', skillText)
    folder.file('LLM_ADMIN_API_INTEGRATION.md', referenceText)
    const blob = await zip.generateAsync({ type: 'blob' })
    const href = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = href
    a.download = 'uap-admin-api.zip'
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(href)
  } catch (err) {
    zipError.value = err instanceof Error ? err.message : '打包失败，请稍后重试或使用下方分开下载。'
  } finally {
    zipping.value = false
  }
}
</script>

<style scoped>
.api-skill-page {
  padding: 30px;
  max-width: 900px;
  margin: 0 auto;
  background-color: #fff;
  border-radius: 8px;
  min-height: 80vh;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

.api-skill-header {
  border-bottom: 1px solid #eaecef;
  margin-bottom: 24px;
  padding-bottom: 16px;
}

h1 {
  font-size: 28px;
  color: #1f2f3d;
  font-weight: 600;
  margin: 0 0 12px;
}

.lead {
  margin: 0;
  color: #606266;
  line-height: 1.6;
  font-size: 15px;
}

.block-card {
  margin-bottom: 16px;
}

.block-card :deep(.el-card__body) {
  color: #606266;
  line-height: 1.7;
  font-size: 14px;
}

.steps {
  margin: 0 0 12px;
  padding-left: 20px;
}

.steps li {
  margin-bottom: 8px;
}

.hint {
  margin: 0;
  font-size: 13px;
  color: #909399;
}

.download-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

.btn-icon {
  margin-right: 6px;
  vertical-align: middle;
}

.download-anchor {
  text-decoration: none;
  display: inline-flex;
}

.path-list {
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.file-path {
  font-size: 13px;
  color: #909399;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  word-break: break-all;
}

.sub-list {
  margin: 6px 0 0;
  padding-left: 20px;
}

.sub-list li {
  margin-bottom: 4px;
}

.zip-error {
  margin: 12px 0;
}

.fallback {
  margin-top: 16px;
  padding: 10px 12px;
  border: 1px dashed #e4e7ed;
  border-radius: 4px;
  background: #fafafa;
}

.fallback > summary {
  cursor: pointer;
  color: #606266;
  font-size: 13px;
  user-select: none;
}

.fallback[open] > summary {
  margin-bottom: 8px;
}

.fallback-row {
  margin-top: 4px;
}

code {
  font-size: 13px;
  background: #f5f7fa;
  padding: 2px 6px;
  border-radius: 4px;
}
</style>
