<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import { PaperPlaneOutline } from '@vicons/ionicons5'
import { NButton, NIcon, NInput, useMessage } from 'naive-ui'
import RagSourcePanel from '../../components/rag/RagSourcePanel.vue'
import RagChatPanel from '../../components/rag/RagChatPanel.vue'
import RagEvidencePanel from '../../components/rag/RagEvidencePanel.vue'
import { generateRagAnswer, getGraphReadiness, sendRagQuestion } from '../../api/rag'
import { getDataSources, getStructuredRagProfile, refreshStructuredRagProfile } from '../../api/datasource'
import { getGraphSources } from '../../api/graphsource'

const message = useMessage()
const input = ref('东海海上目标救援任务中，可用救援力量和任务阶段分别是什么？')
const question = ref('')
const answer = ref('')
const busy = ref(false)
const topK = ref(5)
const strategy = ref('hybrid')
const debug = ref(true)
const enabled = ref({ document: true, structured: true, graph: true })
const structuredSourceId = ref(null)
const graphSourceId = ref(null)
const structuredSourceOptions = ref([])
const graphSourceOptions = ref([])
const structuredProfile = ref(null)
const structuredProfileLoading = ref(false)
const graphReadiness = ref(null)
const graphReadinessLoading = ref(false)
const documentEvidence = ref([])
const structuredEvidence = ref([])
const graphEvidence = ref([])
const enabledSources = ref([])
const diagnostics = ref({})
const now = () => new Date().toLocaleTimeString('zh-CN', { hour12: false })
const createSteps = () => [
  { key: 'document', title: '检索文档知识库', description: '等待开始', status: 'pending', time: '' },
  { key: 'structured', title: '查询结构化数据', description: '等待开始', status: 'pending', time: '' },
  { key: 'graph', title: '查询 Neo4j 图谱', description: '等待开始', status: 'pending', time: '' },
  { key: 'fusion', title: '融合多源证据', description: '等待开始', status: 'pending', time: '' }
]
const steps = ref(createSteps())
const evidenceCount = computed(() => documentEvidence.value.length + structuredEvidence.value.length + graphEvidence.value.length)

const STRUCTURED_SOURCE_STORAGE_KEY = 'rag.structuredSourceId'
const selectedStructuredSource = computed(() => structuredSourceOptions.value
  .find(option => String(option.value) === String(structuredSourceId.value))?.source || null)

async function loadStructuredSources() {
  const response = await getDataSources()
  structuredSourceOptions.value = (response.data || []).map(source => ({
    label: `${source.name}（ID:${source.id}）`, value: source.id, source
  }))
  const currentExists = structuredSourceOptions.value.some(option => String(option.value) === String(structuredSourceId.value))
  if (currentExists) return
  const saved = localStorage.getItem(STRUCTURED_SOURCE_STORAGE_KEY)
  const restored = structuredSourceOptions.value.find(option => String(option.value) === String(saved))
  structuredSourceId.value = restored?.value ?? null
}

async function loadStructuredProfile(force = false) {
  if (structuredSourceId.value == null) { structuredProfile.value = null; return }
  structuredProfileLoading.value = true
  try {
    if (force) await refreshStructuredRagProfile(structuredSourceId.value)
    const response = await getStructuredRagProfile(structuredSourceId.value)
    structuredProfile.value = response.data || null
  } catch (error) {
    structuredProfile.value = { scanStatus: 'ERROR', lastError: error.message }
  } finally { structuredProfileLoading.value = false }
}

async function loadGraphReadiness() {
  if (graphSourceId.value == null) { graphReadiness.value = null; return }
  graphReadinessLoading.value = true
  try {
    const response = await getGraphReadiness(graphSourceId.value)
    graphReadiness.value = response?.data || response || null
  } catch (error) {
    graphReadiness.value = { readinessScore: '-', recommendations: [error.message] }
  } finally { graphReadinessLoading.value = false }
}

async function loadGraphSources() {
  const response = await getGraphSources()
  graphSourceOptions.value = (response.data || []).map(source => ({ label: `${source.name}（ID:${source.id}）`, value: source.id }))
  const currentExists = graphSourceOptions.value.some(option => String(option.value) === String(graphSourceId.value))
  if (!currentExists) graphSourceId.value = graphSourceOptions.value[0]?.value ?? null
  else await loadGraphReadiness()
}

watch(structuredSourceId, value => {
  if (value == null) localStorage.removeItem(STRUCTURED_SOURCE_STORAGE_KEY)
  else localStorage.setItem(STRUCTURED_SOURCE_STORAGE_KEY, String(value))
  loadStructuredProfile()
})

watch(graphSourceId, () => loadGraphReadiness())

onMounted(async () => {
  await Promise.allSettled([loadStructuredSources(), loadGraphSources()])
})

function describe(key, data) {
  if (key === 'document') return `已召回 ${data?.evidences?.length || 0} 个文档片段`
  if (key === 'structured') return `已获取 ${data?.evidences?.[0]?.payload?.rowCount || 0} 条查询结果`
  if (key === 'graph') return `已发现 ${data?.evidences?.[0]?.metadata?.nodeCount || 0} 个节点 / ${data?.evidences?.[0]?.metadata?.edgeCount || 0} 条关系`
  return '检索完成'
}

function updateStep(key, status, data, error) {
  const step = steps.value.find(item => item.key === key)
  if (!step) return
  step.status = status
  step.time = now()
  step.error = error?.message || ''
  if (status === 'running') step.description = key === 'document' ? '正在检索文档知识库...' : key === 'structured' ? '正在生成并执行 SQL...' : key === 'graph' ? '正在生成并执行 Cypher...' : '正在处理...'
  if (status === 'success') step.description = describe(key, data)
}

async function submit() {
  const query = input.value.trim()
  if (!query || busy.value) return
  if (!Object.values(enabled.value).some(Boolean)) return message.warning('请至少启用一个数据源')
  if (enabled.value.structured && structuredSourceId.value == null) return message.warning('请选择结构化数据源')
  if (enabled.value.graph && graphSourceId.value == null) return message.warning('请选择图谱数据源')
  busy.value = true
  question.value = query
  answer.value = ''
  steps.value = createSteps()
  documentEvidence.value = []; structuredEvidence.value = []; graphEvidence.value = []; diagnostics.value = {}
  for (const step of steps.value.slice(0, 3)) {
    if (!enabled.value[step.key]) { step.description = '当前数据源未启用'; step.time = now() }
  }
  try {
    const result = await sendRagQuestion({
      query,
      topK: topK.value,
      strategy: strategy.value,
      enabled: enabled.value,
      structuredSourceIds: structuredSourceId.value == null ? [] : [String(structuredSourceId.value)],
      graphSourceIds: graphSourceId.value == null ? [] : [String(graphSourceId.value)],
      onStep: updateStep
    })
    documentEvidence.value = result.document?.evidences || []
    structuredEvidence.value = result.structured?.evidences || []
    graphEvidence.value = result.graph?.evidences || []
    enabledSources.value = [
      ...(enabled.value.document ? ['DOCUMENT'] : []),
      ...(result.kb?.enabledSources || (enabled.value.structured ? ['STRUCTURED_DATA'] : [])),
      ...(!result.kb && enabled.value.graph ? ['GRAPH'] : [])
    ].filter((value, index, array) => array.indexOf(value) === index)
    diagnostics.value = { document: result.document?.diagnostics, structured: result.structured?.diagnostics, graph: result.graph?.diagnostics, kb: result.kb?.diagnostics }
    updateStep('fusion', 'running')
    if (!result.evidences.length) throw new Error('未召回可用于生成回答的证据')
    const generated = await generateRagAnswer(query, result.evidences)
    answer.value = generated.answer
    const fusion = steps.value.find(item => item.key === 'fusion')
    Object.assign(fusion, { status: 'success', description: `已基于 ${evidenceCount.value} 条证据完成生成`, time: now() })
  } catch (error) {
    const fusion = steps.value.find(item => item.key === 'fusion')
    Object.assign(fusion, { status: 'failed', error: /timeout/i.test(error.message) ? '请求超时，可能是自动 SQL/Cypher 生成较慢' : error.message, time: now() })
    message.error(fusion.error)
  } finally { busy.value = false }
}
</script>

<template>
  <div class="rag-page">
    <div class="rag-heading"><div class="breadcrumb">知识库管理 <span>/</span> RAG</div><h1>多源 RAG 问答工作台</h1><p>问题输入 → 多源检索 → 证据召回 → 融合生成</p></div>
    <div class="workbench-grid">
      <RagSourcePanel v-model:enabled="enabled" v-model:topK="topK" v-model:strategy="strategy" v-model:structured-source-id="structuredSourceId" v-model:graph-source-id="graphSourceId" :structured-source-options="structuredSourceOptions" :graph-source-options="graphSourceOptions" :structured-source-name="selectedStructuredSource?.name || ''" :structured-profile="structuredProfile" :structured-profile-loading="structuredProfileLoading" :graph-readiness="graphReadiness" :graph-readiness-loading="graphReadinessLoading" :busy="busy" @refresh-structured-sources="loadStructuredSources" @refresh-structured-profile="loadStructuredProfile(true)" @refresh-graph-readiness="loadGraphReadiness" />
      <RagChatPanel :question="question" :steps="steps" :answer="answer" :busy="busy" />
      <RagEvidencePanel :document-evidence="documentEvidence" :structured-evidence="structuredEvidence" :graph-evidence="graphEvidence" :enabled-sources="enabledSources" :diagnostics="diagnostics" :debug="debug" />
    </div>
    <div class="composer"><n-input v-model:value="input" type="textarea" :autosize="{ minRows: 1, maxRows: 3 }" placeholder="请输入问题..." :disabled="busy" @keydown.enter.exact.prevent="submit" /><n-button type="primary" :loading="busy" @click="submit"><template #icon><n-icon><PaperPlaneOutline /></n-icon></template>发送</n-button></div>
  </div>
</template>

<style scoped>
.rag-page{height:calc(100vh - var(--header-height) - 56px);min-height:690px;display:flex;flex-direction:column;color:#eef6ff}.rag-heading{flex:0 0 auto;margin-bottom:12px}.breadcrumb{font-size:12px;color:#8399b7;margin-bottom:5px}.breadcrumb span{margin:0 7px;color:#415b7b}.rag-heading h1{font-size:27px;line-height:1.25}.rag-heading p{font-size:13px;color:#8ea4c1;margin-top:5px}.workbench-grid{flex:1;min-height:0;display:grid;grid-template-columns:minmax(290px,26fr) minmax(420px,40fr) minmax(350px,34fr);gap:12px}.composer{flex:0 0 auto;display:flex;gap:10px;margin-top:12px}.composer .n-input{flex:1;border:1px solid rgba(62,130,210,.38)!important;border-radius:8px}.composer .n-button{height:auto;min-width:116px;font-size:14px;background:linear-gradient(135deg,#175fd2,#239df5)!important}@media(max-width:1250px){.workbench-grid{grid-template-columns:290px minmax(430px,1fr)}.workbench-grid>:last-child{grid-column:1/-1;min-height:420px}.rag-page{height:auto}.workbench-grid{min-height:1100px}}@media(max-width:850px){.workbench-grid{display:flex;flex-direction:column}.workbench-grid>*{min-height:560px}.rag-page{min-height:1900px}}
</style>
