<script setup>
import { computed, onMounted, reactive, ref } from 'vue'
import { useMessage } from 'naive-ui'
import {
  addRagQueryExampleCandidates, deleteRagQueryExample, getGraphGovernance, getGraphReadiness, listRagQueryExamples, refreshRagCapability,
  suggestRagGovernance, updateGraphGovernance, verifyRagQueryExample
} from '../../api/rag'
import { getGraphSources } from '../../api/graphsource'

const message = useMessage()
const form = reactive({ sourceType: 'GRAPH', sourceId: null, description: '' })
const graphSources = ref([])
const profile = ref(null)
const suggestion = ref(null)
const examples = ref([])
const governanceConfig = ref(null)
const readiness = ref(null)
const loading = reactive({ profile: false, suggest: false, apply: false })
const selectedLabels = ref([])
const selectedRelationships = ref([])
const selectedProperties = reactive({})
const rulesText = ref('')
const sourceOptions = computed(() => graphSources.value.map(source => ({
  label: `${source.name}（ID: ${source.id}）`, value: String(source.id)
})))

const graph = computed(() => profile.value?.graphSchema || { nodes: [], relationships: [] })
const relationshipGroups = computed(() => {
  const groups = new Map()
  for (const relation of graph.value.relationships) {
    const current = groups.get(relation.type) || { type: relation.type, startLabels: [], endLabels: [], properties: {} }
    current.startLabels = [...new Set([...current.startLabels, ...(relation.startLabels || [])])]
    current.endLabels = [...new Set([...current.endLabels, ...(relation.endLabels || [])])]
    Object.assign(current.properties, relation.properties || {})
    groups.set(relation.type, current)
  }
  return [...groups.values()]
})

function clearSelection() {
  selectedLabels.value = []
  selectedRelationships.value = []
  Object.keys(selectedProperties).forEach(key => delete selectedProperties[key])
}

function currentBindingConfig() {
  const raw = governanceConfig.value?.config || {}
  try { return typeof raw === 'string' ? JSON.parse(raw || '{}') : raw }
  catch { return {} }
}

function ownerProperties(owner) {
  const node = graph.value.nodes.find(item => item.label === owner)
  if (node) return Object.keys(node.properties || {})
  const relation = relationshipGroups.value.find(item => item.type === owner)
  return Object.keys(relation?.properties || {})
}

function updateLabels(values) {
  selectedLabels.value = values
  values.forEach(owner => { if (!Array.isArray(selectedProperties[owner])) selectedProperties[owner] = ownerProperties(owner) })
}

function updateRelationships(values) {
  selectedRelationships.value = values
  values.forEach(owner => { if (!Array.isArray(selectedProperties[owner])) selectedProperties[owner] = ownerProperties(owner) })
}

function updateOwnerProperties(owner, values, ownerType) {
  selectedProperties[owner] = values
  if (!values.length) return
  if (ownerType === 'label' && !selectedLabels.value.includes(owner))
    selectedLabels.value = [...selectedLabels.value, owner]
  if (ownerType === 'relationship' && !selectedRelationships.value.includes(owner))
    selectedRelationships.value = [...selectedRelationships.value, owner]
}

function initializeFromBinding() {
  clearSelection()
  rulesText.value = ''
  if (!governanceConfig.value?.configured) return
  const config = currentBindingConfig()
  const actualLabels = graph.value.nodes.map(node => node.label)
  const actualRelationships = relationshipGroups.value.map(rel => rel.type)
  selectedLabels.value = Array.isArray(config.allowedLabels) && config.allowedLabels.length
    ? config.allowedLabels.filter(name => actualLabels.includes(name)) : actualLabels
  selectedRelationships.value = Array.isArray(config.allowedRelationships) && config.allowedRelationships.length
    ? config.allowedRelationships.filter(name => actualRelationships.includes(name)) : actualRelationships
  const configuredProperties = config.allowedProperties || {}
  ;[...selectedLabels.value, ...selectedRelationships.value].forEach(owner => {
    selectedProperties[owner] = Array.isArray(configuredProperties[owner])
      ? configuredProperties[owner].filter(name => ownerProperties(owner).includes(name))
      : ownerProperties(owner)
  })
  rulesText.value = (config.generationRules || config.rules || []).join('\n')
}

async function loadProfile() {
  if (!form.sourceId) return message.warning('请先选择图数据库')
  loading.profile = true
  try {
    profile.value = await refreshRagCapability(form.sourceType, form.sourceId)
    examples.value = await listRagQueryExamples(form.sourceType, form.sourceId)
    governanceConfig.value = await getGraphGovernance(form.sourceId)
    readiness.value = await getGraphReadiness(form.sourceId)
    initializeFromBinding()
  } catch (error) { message.error(error.message || 'Schema 刷新失败') }
  finally { loading.profile = false }
}

async function generateSuggestion() {
  loading.suggest = true
  try {
    suggestion.value = await suggestRagGovernance(form.sourceType, form.sourceId, form.description)
    const config = suggestion.value?.suggestedConfig || suggestion.value?.config || suggestion.value
    selectedLabels.value = config.allowedLabels || selectedLabels.value
    selectedRelationships.value = config.allowedRelationships || selectedRelationships.value
    Object.keys(selectedProperties).forEach(key => delete selectedProperties[key])
    Object.assign(selectedProperties, config.allowedProperties || {})
    rulesText.value = (config.generationRules || config.rules || []).join('\n')
    message.success('已生成建议，请审核后再应用')
  } catch (error) { message.error(error.message || '建议生成失败') }
  finally { loading.suggest = false }
}

function buildConfig() {
  const original = governanceConfig.value?.config || {}
  const config = typeof original === 'string' ? JSON.parse(original || '{}') : { ...original }
  Object.assign(config, {
    retrievalMode: 'AUTO_GENERATE', topK: config.topK || 5,
    generationRules: rulesText.value.split('\n').map(v => v.trim()).filter(Boolean)
  })
  Object.assign(config, {
    allowTextToCypher: true, allowedLabels: selectedLabels.value,
    allowedRelationships: selectedRelationships.value,
    allowedProperties: Object.fromEntries(Object.entries(selectedProperties).filter(([owner]) =>
      selectedLabels.value.includes(owner) || selectedRelationships.value.includes(owner)))
  })
  return config
}

async function applyConfig() {
  if (!selectedLabels.value.length) return message.error('至少授权一个 Label，空白名单会造成权限语义不明确')
  const orphan = Object.entries(selectedProperties).find(([owner, values]) => values?.length
    && !selectedLabels.value.includes(owner) && !selectedRelationships.value.includes(owner))
  if (orphan) return message.error(`${orphan[0]} 已选择属性但未授权所属 Label/Relationship`)
  loading.apply = true
  try {
    governanceConfig.value = await updateGraphGovernance(form.sourceId, buildConfig())
    message.success('授权配置已应用')
  } catch (error) { message.error(error.message || '配置应用失败') }
  finally { loading.apply = false }
}

async function setVerified(item, value) {
  try { Object.assign(item, await verifyRagQueryExample(item.id, value)); message.success(value ? '案例已审核通过' : '案例已取消授权') }
  catch (error) { message.error(error.message || '案例审核失败') }
}

async function removeExample(item) {
  try {
    await deleteRagQueryExample(item.id)
    examples.value = examples.value.filter(example => example.id !== item.id)
    message.success('案例已删除')
  } catch (error) { message.error(error.message || '案例删除失败') }
}

async function importCandidates() {
  const candidates = suggestion.value?.examples || []
  if (!candidates.length) return message.warning('当前建议中没有可导入案例')
  try {
    const result = await addRagQueryExampleCandidates(form.sourceType, form.sourceId, candidates)
    examples.value = result.examples || []
    const rejected = result.rejected?.length || 0
    message[rejected ? 'warning' : 'success'](`已通过预检并导入 ${result.accepted || 0} 条，拒绝 ${rejected} 条`)
  } catch (error) { message.error(error.message || '案例导入失败') }
}

async function initialize() {
  try {
    const response = await getGraphSources()
    graphSources.value = response.data || []
    if (graphSources.value.length) {
      form.sourceId = String(graphSources.value[0].id)
      await loadProfile()
    } else message.warning('尚未配置图数据库，请先在知识图谱页面新增图源')
  } catch (error) { message.error(error.message || '图数据库列表加载失败') }
}

onMounted(initialize)
</script>

<template>
  <div class="page">
    <header><div><h1>图谱治理</h1><p>根据真实图 Schema 建议业务子图，审核授权范围与动态 Few-shot。</p></div></header>
    <n-card class="toolbar">
      <n-space align="end">
        <n-form-item label="数据源类型"><n-tag type="info">图数据库</n-tag></n-form-item>
        <n-form-item label="图数据库"><n-select v-model:value="form.sourceId" :options="sourceOptions" style="width:240px" @update:value="loadProfile" /></n-form-item>
        <n-form-item label="业务描述"><n-input v-model:value="form.description" placeholder="例如：东海救援任务、力量与阶段" style="width:360px" /></n-form-item>
        <n-button :loading="loading.profile" @click="loadProfile">刷新画像</n-button>
        <n-button type="primary" :loading="loading.suggest" @click="generateSuggestion">生成业务子图与案例</n-button>
      </n-space>
    </n-card>

    <div class="metrics" v-if="profile">
      <n-card size="small"><b>画像版本</b><span>{{ profile.version }}</span></n-card>
      <n-card size="small"><b>Schema 规模</b><span>{{ `${graph.nodes.length} Labels / ${graph.relationships.length} Relationships` }}</span></n-card>
      <n-card size="small"><b>语义目录</b><span>{{ profile.semanticCatalog?.length || 0 }} 项已向量化</span></n-card>
      <n-card size="small"><b>治理状态</b><span>{{ governanceConfig?.configured ? '已生效' : '未配置' }}</span></n-card>
      <n-card size="small"><b>就绪度</b><span>{{ readiness?.readinessScore ?? '-' }}</span></n-card>
    </div>

    <div class="columns">
      <n-card title="授权审核" class="panel">
        <n-collapse :default-expanded-names="['labels']">
          <n-collapse-item name="labels" :title="`Labels（${selectedLabels.length}/${graph.nodes.length}）`">
            <n-checkbox-group :value="selectedLabels" @update:value="updateLabels"><n-collapse accordion>
              <n-collapse-item v-for="node in graph.nodes" :key="node.label" :name="`label:${node.label}`">
                <template #header><n-checkbox :value="node.label" :label="node.label" @click.stop /></template>
                <div class="schema-detail"><span>Properties</span><n-checkbox-group :value="selectedProperties[node.label]" @update:value="values => updateOwnerProperties(node.label, values, 'label')"><n-space><n-checkbox v-for="(_, name) in node.properties" :key="name" :value="name" :label="name" /></n-space></n-checkbox-group><n-empty v-if="!Object.keys(node.properties || {}).length" size="small" description="无属性" /></div>
              </n-collapse-item>
            </n-collapse></n-checkbox-group>
          </n-collapse-item>
          <n-collapse-item name="relationships" :title="`Relationships（${selectedRelationships.length}/${relationshipGroups.length}）`">
            <n-checkbox-group :value="selectedRelationships" @update:value="updateRelationships"><n-collapse accordion>
              <n-collapse-item v-for="rel in relationshipGroups" :key="rel.type" :name="`rel:${rel.type}`">
                <template #header><n-checkbox :value="rel.type" :label="rel.type" @click.stop /></template>
                <div class="schema-detail"><p>{{ rel.startLabels?.join(' | ') }} → {{ rel.endLabels?.join(' | ') }}</p><span>Properties</span><n-checkbox-group :value="selectedProperties[rel.type]" @update:value="values => updateOwnerProperties(rel.type, values, 'relationship')"><n-space><n-checkbox v-for="(_, name) in rel.properties" :key="name" :value="name" :label="name" /></n-space></n-checkbox-group><n-empty v-if="!Object.keys(rel.properties || {}).length" size="small" description="无属性" /></div>
              </n-collapse-item>
            </n-collapse></n-checkbox-group>
          </n-collapse-item>
        </n-collapse>
        <h3>生成规则（每行一条）</h3><n-input v-model:value="rulesText" type="textarea" :rows="5" placeholder="只生成只读查询&#10;优先使用明确关系而非笛卡尔积" />
        <n-button type="primary" block class="apply" :loading="loading.apply" @click="applyConfig">应用审核后的授权配置</n-button>
      </n-card>

      <n-card title="建议与画像" class="panel"><n-alert type="info" :show-icon="false">JSON 是建议草稿：suggestedConfig 会同步到左侧表单，点击“应用”后才生效；examples 需点击导入并逐条审核。</n-alert><n-tag v-if="suggestion" type="warning" class="apply">草稿 · 尚未应用</n-tag><n-button v-if="suggestion?.examples?.length" block class="apply" @click="importCandidates">导入为待审核 Few-shot</n-button><pre>{{ JSON.stringify(suggestion || { message: '点击“生成业务子图与案例”获取建议' }, null, 2) }}</pre></n-card>

      <n-card title="动态 Few-shot 审核" class="panel"><n-empty v-if="!examples.length" description="暂无学习案例" /><div v-for="item in examples" :key="item.id" class="example"><div><b>{{ item.question }}</b><code>{{ item.queryText || item.generatedQuery }}</code></div><n-space vertical align="end"><n-switch :value="item.verified" @update:value="value => setVerified(item, value)" /><n-popconfirm positive-text="删除" negative-text="取消" @positive-click="removeExample(item)"><template #trigger><n-button size="tiny" type="error" ghost>删除</n-button></template>删除后无法恢复，确定删除这条 Few-shot 吗？</n-popconfirm></n-space></div></n-card>
    </div>
  </div>
</template>

<style scoped>
.page{padding:24px;min-height:100vh;background:var(--bg-primary);color:var(--text-primary)}header{display:flex;justify-content:space-between;margin-bottom:18px}h1{margin:0 0 6px;font-size:25px}p{margin:0;color:var(--text-secondary)}.toolbar{margin-bottom:16px}.metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}.metrics :deep(.n-card__content){display:flex;flex-direction:column;gap:8px}.metrics span{color:#38bdf8}.columns{display:grid;grid-template-columns:1.2fr 1fr 1fr;gap:14px;align-items:start}.panel{max-height:calc(100vh - 280px);overflow:auto}.schema-detail{padding:4px 10px 12px 34px}.schema-detail>span{display:block;color:var(--text-secondary);font-size:12px;margin-bottom:8px}.schema-detail p{font-size:12px;color:var(--text-secondary);margin:0 0 10px}.apply{margin-top:18px}pre{white-space:pre-wrap;word-break:break-word;padding:12px;background:#061426;border-radius:8px;color:#93c5fd;font-size:12px}.example{display:flex;justify-content:space-between;gap:12px;padding:12px 0;border-bottom:1px solid var(--border-color)}.example code{display:block;white-space:pre-wrap;margin-top:8px;color:var(--text-secondary)}@media(max-width:1200px){.columns{grid-template-columns:1fr}.metrics{grid-template-columns:repeat(2,1fr)}}
</style>
