<template>
  <div class="qa-wrap">
    <div class="ambient-glow"></div>
    <div class="panel-card">
      <div class="panel-head">
        <h3>图谱问答</h3>
        <span class="user-tag">userId: {{ userId }}</span>
      </div>
      <div class="qa-opts">
        <label>
          轮次
          <input v-model.number="maxRounds" type="number" min="2" max="5" class="opt-num" />
        </label>
        <label>
          每轮 TopK
          <input v-model.number="topK" type="number" min="2" max="30" class="opt-num" />
        </label>
        <label>
          检索模式
          <select v-model="retrievalMode" class="opt-select">
            <option value="hybrid">混合检索</option>
            <option value="vector">向量检索</option>
            <option value="graph">图谱检索</option>
          </select>
        </label>
      </div>
      <div class="meta-tip">
        <span>重排序模型：{{ rerankModelText }}</span>
        <span>当前检索：{{ retrievalModeText }}</span>
      </div>
      <p v-if="errorMsg" class="error-msg">{{ errorMsg }}</p>
    </div>

    <div class="chat-card">
      <h4>AI 对话</h4>
      <div ref="chatListRef" class="chat-list">
        <div v-for="item in chatMessages" :key="item.id" class="chat-item" :class="item.role">
          <p class="chat-role">{{ item.role === 'user' ? '你' : 'AI' }}</p>
          <details v-if="item.role === 'assistant' && hasThinkingData" class="thinking-block" :open="item.pending">
            <summary>深度思考 / 工具调用</summary>
            <p v-for="line in thinkingLines" :key="line.key" class="thinking-line">{{ line.text }}</p>
          </details>
          <p class="chat-text">{{ item.pending ? '思考中...' : item.text }}</p>
        </div>
        <p v-if="!chatMessages.length" class="empty">等待提问</p>
      </div>
      <div class="chat-compose">
        <input
          v-model.trim="question"
          class="qa-input"
          placeholder="输入问题，例如：我和李文旗现在关系怎么样？"
          :disabled="loading"
          @keyup.enter="runQuery"
        />
        <button class="btn-container tech-btn dark" :disabled="!question || loading" @click="runQuery">
          <span class="btn-label">{{ loading ? '处理中...' : '发送' }}</span>
          <span class="btn-arrow">→</span>
          <span class="corner top-left"></span>
          <span class="corner top-right"></span>
          <span class="corner bottom-left"></span>
          <span class="corner bottom-right"></span>
        </button>
      </div>
    </div>

    <div class="console-card">
      <h4>Console</h4>
      <div class="console-shell">
        <div class="console-head">
          <span class="dot"></span>
          <span class="dot"></span>
          <span class="dot"></span>
          <span class="console-title">graphrag-console</span>
        </div>
        <div ref="consoleBodyRef" class="console-body">
          <p v-for="line in consoleLines" :key="line.key" class="console-line" :class="line.status">
            <span class="console-prefix">[{{ line.statusLabel }}]</span>
            <span class="console-text">{{ line.text }}</span>
          </p>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed, nextTick, ref, watch } from 'vue'

const props = defineProps({
  userId: {
    type: String,
    default: 'default'
  }
})

const question = ref('')
const loading = ref(false)
const errorMsg = ref('')
const maxRounds = ref(3)
const topK = ref(8)
const retrievalMode = ref('hybrid')
const resultMeta = ref(null)
const chatMessages = ref([])
const rounds = ref([])
const queryPlan = ref([])
const graphProbe = ref({
  keywordCount: 0,
  entityCount: 0,
  neighborCount: 0,
  eventCount: 0,
  entities: []
})
const consoleLines = ref([])
const lineSeed = ref(0)
const chatListRef = ref(null)
const consoleBodyRef = ref(null)

const rerankModelText = computed(() => {
  const rerank = resultMeta.value?.rerank
  if (!rerank?.configured) return '未配置'
  if (!rerank?.enabled) return `${rerank.model || '-'}（不可用）`
  return rerank.model || '-'
})

const retrievalModeText = computed(() => resultMeta.value?.retrieval_mode_requested || retrievalMode.value)
const hasThinkingData = computed(
  () => queryPlan.value.length > 0 || rounds.value.length > 0 || graphProbe.value.entityCount > 0 || graphProbe.value.keywordCount > 0
)
const thinkingLines = computed(() => {
  const lines = []
  if (queryPlan.value.length) {
    lines.push(...queryPlan.value.map((q, idx) => ({ key: `plan_${idx}`, text: `问题拆解 #${idx + 1}：${q}` })))
  }
  if (graphProbe.value.keywordCount || graphProbe.value.entityCount) {
    lines.push({
      key: 'probe_summary',
      text: `图谱探查：关键词 ${graphProbe.value.keywordCount} · 实体 ${graphProbe.value.entityCount} · 邻居 ${graphProbe.value.neighborCount} · 事件 ${graphProbe.value.eventCount}`
    })
    lines.push(
      ...graphProbe.value.entities.map((item) => ({
        key: `probe_${item.id}`,
        text: `命中实体：${item.name}(${item.type}) · 邻居 ${item.neighborCount} · 事件 ${item.eventCount}`
      }))
    )
  }
  if (rounds.value.length) {
    lines.push(
      ...rounds.value.flatMap((r, idx) => [
        {
          key: `round_${idx}`,
          text: `第 ${r.round} 轮检索：${r.subQuery || '-'} · 模式 ${r.retrievalMode || '-'} · 片段 ${r.chunkCount || 0} · 时间线 ${r.timelineCount || 0}`
        },
        ...(r.answer ? [{ key: `round_answer_${idx}`, text: `阶段结论：${r.answer}` }] : [])
      ])
    )
  }
  return lines
})

const stageLabel = {
  pipeline_start: '任务初始化',
  graph_probe: '图谱探查',
  query_plan: '问题拆解',
  knowledge_retrieval: '知识检索',
  relation_organize: '关系整理',
  evidence_synthesis: '证据汇总',
  final_review: '终审判别'
}

const stageStatusText = (status) => {
  if (status === 'running') return '执行中'
  if (status === 'done') return '已完成'
  if (status === 'error') return '失败'
  return '等待中'
}

const pushConsole = (status, text) => {
  lineSeed.value += 1
  consoleLines.value = [...consoleLines.value, { key: lineSeed.value, status, statusLabel: stageStatusText(status), text }]
}

const scrollToBottom = (targetRef) => {
  const el = targetRef?.value
  if (!el) return
  el.scrollTop = el.scrollHeight
}

watch(
  () => chatMessages.value.length,
  async () => {
    await nextTick()
    scrollToBottom(chatListRef)
  }
)

watch(
  () => consoleLines.value.length,
  async () => {
    await nextTick()
    scrollToBottom(consoleBodyRef)
  }
)

watch(
  () => `${queryPlan.value.length}_${rounds.value.length}_${graphProbe.value.entityCount}_${graphProbe.value.keywordCount}`,
  async () => {
    await nextTick()
    scrollToBottom(chatListRef)
  }
)

const resetRunState = () => {
  errorMsg.value = ''
  resultMeta.value = null
  rounds.value = []
  queryPlan.value = []
  graphProbe.value = {
    keywordCount: 0,
    entityCount: 0,
    neighborCount: 0,
    eventCount: 0,
    entities: []
  }
  consoleLines.value = []
}

const updateAssistantMessage = (id, text, pending = false) => {
  const idx = chatMessages.value.findIndex((item) => item.id === id)
  if (idx < 0) return
  const current = chatMessages.value[idx]
  const next = { ...current, text, pending }
  chatMessages.value.splice(idx, 1, next)
}

const handleProgress = (data) => {
  if (!data?.stage) return
  const label = stageLabel[data.stage] || data.stage
  if (data.rerank || data.retrieval_mode_requested) {
    resultMeta.value = {
      ...(resultMeta.value || {}),
      ...(data.rerank ? { rerank: data.rerank } : {}),
      ...(data.retrieval_mode_requested ? { retrieval_mode_requested: data.retrieval_mode_requested } : {})
    }
  }
  if (data.stage === 'query_plan') {
    queryPlan.value = Array.isArray(data.queries) ? data.queries : []
    pushConsole(data.status || 'done', `${label} · 子问题 ${queryPlan.value.length} 条`)
    return
  }
  if (data.stage === 'graph_probe') {
    graphProbe.value = {
      keywordCount: Number(data.keywordCount || 0),
      entityCount: Number(data.entityCount || 0),
      neighborCount: Number(data.neighborCount || 0),
      eventCount: Number(data.eventCount || 0),
      entities: Array.isArray(data.entities) ? data.entities : []
    }
    pushConsole(data.status || 'done', `${label} · 关键词 ${graphProbe.value.keywordCount} · 实体 ${graphProbe.value.entityCount}`)
    return
  }
  if (data.stage === 'knowledge_retrieval') {
    if (data.status === 'done') {
      rounds.value = [...rounds.value.filter((r) => r.round !== data.round), data].sort((a, b) => a.round - b.round)
      pushConsole('done', `${label} · 第 ${data.round} 轮完成（片段 ${data.chunkCount || 0} · 时间线 ${data.timelineCount || 0}）`)
      return
    }
    pushConsole(data.status || 'running', `${label} · 第 ${data.round}/${data.totalRounds} 轮执行中`)
    return
  }
  if (data.stage === 'relation_organize') {
    pushConsole(data.status || 'done', `${label} · 关系线索 ${data.relationHints?.length || 0} 条`)
    return
  }
  if (data.stage === 'evidence_synthesis') {
    pushConsole(data.status || 'done', `${label} · 证据 ${data.evidenceCount || 0} 条`)
    return
  }
  if (data.stage === 'pipeline_start') {
    pushConsole(data.status || 'done', `${label} · 问题：${data.question || '-'}`)
    return
  }
  if (data.stage === 'final_review') {
    pushConsole(data.status || 'done', `${label} · 已完成`)
  }
}

const processSseBlock = (block) => {
  const lines = block.split(/\r?\n/)
  let event = 'message'
  const dataLines = []
  for (const line of lines) {
    const normalized = line.trimStart()
    if (normalized.startsWith('event:')) event = normalized.slice(6).trim()
    if (normalized.startsWith('data:')) dataLines.push(normalized.slice(5).trim())
  }
  if (!dataLines.length) return null
  const raw = dataLines.join('\n')
  let data = null
  try {
    data = JSON.parse(raw)
  } catch {
    return null
  }
  if (event === 'progress') {
    handleProgress(data)
    return null
  }
  if (event === 'done') {
    if (Array.isArray(data?.rounds)) rounds.value = data.rounds
    if (data?.meta) resultMeta.value = data.meta
    pushConsole('done', '流式响应结束')
    return data
  }
  if (event === 'error') {
    throw new Error(data?.message || '流式请求失败')
  }
  return null
}

const runQuery = async () => {
  if (!question.value || loading.value) return
  loading.value = true
  resetRunState()
  const userText = question.value
  const userMessageId = `user_${Date.now()}`
  const assistantMessageId = `assistant_${Date.now()}`
  chatMessages.value.push({ id: userMessageId, role: 'user', text: userText, pending: false })
  chatMessages.value.push({ id: assistantMessageId, role: 'assistant', text: '', pending: true })
  pushConsole('running', '请求已发送')
  try {
    const apiBase = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000'
    const res = await fetch(`${apiBase}/query/graphrag/multi/stream`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        userId: props.userId || 'default',
        query_text: userText,
        max_rounds: maxRounds.value,
        top_k: topK.value,
        retrieval_mode: retrievalMode.value
      })
    })
    if (!res.ok || !res.body) {
      const text = await res.text()
      throw new Error(text || `请求失败(${res.status})`)
    }
    const reader = res.body.getReader()
    const decoder = new TextDecoder('utf-8')
    let buffer = ''
    let donePayload = null
    while (true) {
      const { value, done } = await reader.read()
      if (done) break
      buffer += decoder.decode(value, { stream: true })
      const parts = buffer.split(/\r?\n\r?\n/)
      buffer = parts.pop() || ''
      for (const part of parts) {
        if (!part.trim()) continue
        const payload = processSseBlock(part)
        if (payload) donePayload = payload
      }
    }
    if (buffer.trim()) {
      const payload = processSseBlock(buffer)
      if (payload) donePayload = payload
    }
    const finalAnswer = donePayload?.final_review?.answer || donePayload?.final_answer || '未获取到最终回答'
    updateAssistantMessage(assistantMessageId, finalAnswer, false)
  } catch (e) {
    const text = `问答失败：${e.message}`
    errorMsg.value = text
    pushConsole('error', text)
    updateAssistantMessage(assistantMessageId, text, false)
  } finally {
    loading.value = false
    question.value = ''
  }
}
</script>

<style scoped>
.qa-wrap {
  position: relative;
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto minmax(0, 1fr) clamp(170px, 30vh, 300px);
  gap: 10px;
  height: calc(100vh - 132px);
  max-height: calc(100vh - 132px);
  min-height: 0;
  overflow: hidden;
  color: #111111;
  font-family: var(--font-family, -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', 'PingFang SC', sans-serif);
}

.ambient-glow {
  position: absolute;
  top: -220px;
  left: -6%;
  width: 112%;
  height: 360px;
  background: radial-gradient(circle at center, rgba(17, 17, 17, 0.12), rgba(17, 17, 17, 0) 66%);
  pointer-events: none;
  z-index: 0;
}

.panel-card,
.chat-card,
.console-card {
  position: relative;
  z-index: 1;
  background: #ffffff;
  border: 1px solid #dcdcdc;
  border-radius: 10px;
  padding: 16px;
  box-shadow: 0 10px 22px rgba(0, 0, 0, 0.04);
}

.panel-card::before,
.panel-card::after {
  content: '';
  position: absolute;
  width: 10px;
  height: 10px;
  border-color: #111111;
  pointer-events: none;
}

.panel-card::before {
  top: -1px;
  left: -1px;
  border-top: 1px solid #111111;
  border-left: 1px solid #111111;
}

.panel-card::after {
  right: -1px;
  bottom: -1px;
  border-right: 1px solid #111111;
  border-bottom: 1px solid #111111;
}

.panel-card {
  grid-row: 1 / 2;
}

.chat-card {
  grid-row: 2 / 3;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.console-card {
  grid-row: 3 / 4;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  padding: 12px;
  background: #0f0f0f;
  border-color: #2d2d2d;
}

.panel-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

.panel-head h3,
.chat-card h4,
.console-card h4 {
  margin: 0;
  font-size: 16px;
  letter-spacing: 0.8px;
}

.user-tag {
  font-size: 12px;
  color: #333333;
  background: #f8f8f8;
  border: 1px solid #cfcfcf;
  padding: 5px 10px;
  border-radius: 999px;
  letter-spacing: 0.4px;
}

.qa-input {
  flex: 1;
  border: 1px solid #cccccc;
  border-radius: 8px;
  padding: 10px 12px;
  outline: none;
  color: #111111;
  background: #ffffff;
}

.qa-input:focus {
  border-color: #111111;
  box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.08);
}

.qa-opts {
  display: flex;
  gap: 14px;
  margin-top: 12px;
  color: #555555;
  font-size: 13px;
}

.qa-opts label {
  display: flex;
  align-items: center;
  gap: 6px;
}

.opt-num,
.opt-select {
  border: 1px solid #cccccc;
  border-radius: 8px;
  padding: 5px 8px;
  background: #ffffff;
  color: #111111;
  outline: none;
}

.opt-num {
  width: 70px;
}

.meta-tip {
  margin-top: 10px;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  color: #666666;
  font-size: 12px;
}

.chat-list {
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  overflow: auto;
  min-height: 0;
  flex: 1;
  padding: 0 2px 12px;
}

.chat-item {
  border: 1px solid #ebebeb;
  border-radius: 10px;
  padding: 12px 14px;
  background: #ffffff;
  max-width: min(920px, 96%);
  width: fit-content;
}

.chat-item.assistant {
  background: #fcfcfc;
  align-self: flex-start;
}

.chat-item.user {
  background: #f5f7fb;
  border-color: #e7ebf4;
  align-self: flex-end;
}

.chat-item.user .chat-role {
  color: #667085;
}

.chat-role {
  margin: 0;
  color: #666666;
  font-size: 12px;
}

.chat-text {
  margin: 6px 0 0;
  white-space: pre-wrap;
  line-height: 1.6;
  font-size: 14px;
}

.thinking-block {
  margin: 6px 0 0;
  border: 1px solid #e7e7e7;
  border-radius: 8px;
  background: #fafafa;
  padding: 6px 8px;
}

.thinking-block summary {
  cursor: pointer;
  font-size: 12px;
  color: #666666;
}

.thinking-line {
  margin: 6px 0 0;
  font-size: 12px;
  color: #555555;
  line-height: 1.5;
  white-space: pre-wrap;
}

.chat-compose {
  margin-top: 6px;
  display: flex;
  gap: 10px;
  align-items: center;
  border-top: 1px solid #e4e4e4;
  padding-top: 10px;
  position: sticky;
  bottom: 0;
  background: #ffffff;
  z-index: 2;
}

.console-shell {
  margin-top: 2px;
  border: 1px solid #2d2d2d;
  border-radius: 6px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
}

.console-head {
  display: flex;
  align-items: center;
  gap: 6px;
  background: #0f0f0f;
  border-bottom: 1px solid #2e2e2e;
  padding: 8px 10px;
}

.dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #8a8a8a;
}

.console-title {
  margin-left: 6px;
  font-size: 12px;
  color: #dfdfdf;
  letter-spacing: 0.8px;
}

.console-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow: auto;
  background: #111111;
  padding: 10px;
  min-height: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace;
}

.console-line {
  margin: 0;
  font-size: 12px;
  line-height: 1.6;
  display: flex;
  gap: 8px;
  color: #e8e8e8;
}

.console-prefix {
  min-width: 46px;
  color: #9d9d9d;
}

.console-line.running .console-prefix {
  color: #ffffff;
}

.console-line.done .console-prefix {
  color: #cbcbcb;
}

.console-line.error .console-prefix {
  color: #f1f1f1;
}

.tool-block,
.tool-sub-block {
  border: 1px solid #343434;
  border-radius: 6px;
  padding: 6px 8px;
  background: #161616;
  color: #d9d9d9;
}

.tool-sub-block {
  margin-top: 6px;
}

.tool-block summary,
.tool-sub-block summary {
  cursor: pointer;
  font-size: 12px;
}

.tool-line {
  margin: 6px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: #c7c7c7;
  white-space: pre-wrap;
}

.error-msg {
  margin-top: 8px;
  color: #333333;
  font-size: 13px;
  border: 1px solid #cfcfcf;
  padding: 7px 9px;
  border-radius: 8px;
  background: #f8f8f8;
}

.empty {
  color: #888888;
  font-size: 13px;
  margin: 2px 0;
}

.btn-container {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 100px;
  border: 1px solid #111111;
  border-radius: 8px;
  padding: 10px 14px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.tech-btn.dark {
  background: #111111;
  color: #ffffff;
  box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
}

.btn-label {
  font-size: 13px;
  letter-spacing: 1px;
}

.btn-arrow {
  transition: transform 0.2s ease;
}

.btn-container:hover .btn-arrow {
  transform: translateX(4px);
}

.corner {
  position: absolute;
  width: 8px;
  height: 8px;
  border-color: #ffffff;
  transition: transform 0.2s ease;
}

.top-left {
  top: 4px;
  left: 4px;
  border-top: 1px solid #ffffff;
  border-left: 1px solid #ffffff;
}

.top-right {
  top: 4px;
  right: 4px;
  border-top: 1px solid #ffffff;
  border-right: 1px solid #ffffff;
}

.bottom-left {
  bottom: 4px;
  left: 4px;
  border-bottom: 1px solid #ffffff;
  border-left: 1px solid #ffffff;
}

.bottom-right {
  right: 4px;
  bottom: 4px;
  border-right: 1px solid #ffffff;
  border-bottom: 1px solid #ffffff;
}

.btn-container:hover .top-left {
  transform: translate(-2px, -2px);
}

.btn-container:hover .top-right {
  transform: translate(2px, -2px);
}

.btn-container:hover .bottom-left {
  transform: translate(-2px, 2px);
}

.btn-container:hover .bottom-right {
  transform: translate(2px, 2px);
}

.btn-container:disabled {
  cursor: not-allowed;
  background: #666666;
  border-color: #666666;
  box-shadow: none;
}

.btn-container:disabled .corner {
  border-color: #dddddd;
}

@media (max-width: 1100px) {
  .qa-wrap {
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr) clamp(160px, 36vh, 260px);
    height: calc(100vh - 116px);
    max-height: calc(100vh - 116px);
  }
}

@media (max-width: 840px) {
  .chat-compose {
    flex-direction: column;
  }

  .qa-opts {
    flex-wrap: wrap;
    gap: 8px 12px;
  }
}
</style>
