<script setup lang="ts">
import { ref, computed, onBeforeUnmount, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import MarkdownView from '@/components/MarkdownView.vue'
import { getCompare, chatWithPort } from '@/api/modules/compare'
import type { LoadedModel } from '@/types'

const route = useRoute()
const taskId = route.query.taskId as string
const question = decodeURIComponent(route.query.question as string || '')
const systemPrompt = decodeURIComponent(route.query.systemPrompt as string || '')
const temperature = Number(route.query.temperature || 0.7)
const topP = Number(route.query.topP || 0.9)
const topK = Number(route.query.topK || 40)
const maxTokens = Number(route.query.maxTokens || 2048)

const taskName = route.query.taskName as string

interface ModelResult {
  name: string
  content: string
  displayContent: string
  isTyping: boolean
  status: 'loading' | 'done' | 'error'
  stats?: { charsPerSec?: number; totalTime?: number }
}

const results = ref<ModelResult[]>([])
const started = ref(false)

const loadedModels = ref<LoadedModel[]>([])
const typewriterTimers = new Set<ReturnType<typeof setInterval>>()

async function init() {
  if (started.value) return
  started.value = true
  try {
    const task: any = await getCompare(taskId)
    let models: LoadedModel[] = []
    if (task.load_status) {
      const ls = typeof task.load_status === 'string' ? JSON.parse(task.load_status) : task.load_status
      models = ls.loaded_models || []
    }
    loadedModels.value = models
    // 初始化结果占位
    results.value = models.map((m) => ({
      name: m.model_name || '模型',
      content: '',
      displayContent: '',
      isTyping: false,
      status: 'loading',
    }))

    // 并行推理
    await Promise.all(models.map((m, idx) => inferOne(m, idx)))
  } catch (e) {
    // ignore
  }
}

/** 单模型推理（带超时） */
async function inferOne(model: LoadedModel, idx: number) {
  const startTime = Date.now()
  try {
    // 尝试通过端口代理调用
    const res: any = await withTimeout(
      chatWithPort({
        port: model.port,
        model_name: model.model_name,
        messages: [
          ...(systemPrompt ? [{ role: 'system', content: systemPrompt }] : []),
          { role: 'user', content: question },
        ],
        temperature,
        top_p: topP,
        top_k: topK,
        max_tokens: maxTokens,
      }),
      300000,
    )

    const content = res?.response || res?.content || res?.data || JSON.stringify(res)
    const totalTime = (Date.now() - startTime) / 1000
    results.value[idx].content = content
    results.value[idx].status = 'done'
    results.value[idx].stats = {
      totalTime,
      charsPerSec: totalTime > 0 ? Number((content.length / totalTime).toFixed(1)) : 0,
    }
    // 模拟打字机效果
    typewriterDisplay(idx, content)
  } catch (e: any) {
    results.value[idx].content = '推理失败: ' + (e.message || '')
    results.value[idx].status = 'error'
  }
}

async function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
  let timeoutId: ReturnType<typeof setTimeout> | null = null
  try {
    return await Promise.race([
      promise,
      new Promise<T>((_, reject) => {
        timeoutId = setTimeout(() => reject(new Error('推理超时')), timeoutMs)
      }),
    ])
  } finally {
    if (timeoutId) clearTimeout(timeoutId)
  }
}

/** 打字机效果逐字展示 */
function typewriterDisplay(idx: number, content: string) {
  let pos = 0
  results.value[idx].isTyping = true
  // 将更新次数控制在约 30 次，避免长回答逐字触发 Markdown 全文解析。
  const step = Math.max(2, Math.ceil(content.length / 30))
  const interval = setInterval(() => {
    pos += step
    results.value[idx].displayContent = content.slice(0, pos)
    if (pos >= content.length) {
      clearInterval(interval)
      typewriterTimers.delete(interval)
      results.value[idx].displayContent = content
      results.value[idx].isTyping = false
    }
  }, 50)
  typewriterTimers.add(interval)
}

const allDone = computed(() => results.value.length > 0 && results.value.every((r) => r.status === 'done' || r.status === 'error'))

onMounted(init)
onBeforeUnmount(() => {
  typewriterTimers.forEach(clearInterval)
  typewriterTimers.clear()
})
</script>

<template>
  <div class="compare-result">
    <div class="result-header">
      <h2>对比结果{{ taskName ? ` - ${taskName}` : '' }}</h2>
      <div class="header-actions">
        <el-button @click="$router.push('/model-inference')">返回列表</el-button>
      </div>
    </div>

    <!-- 问题 -->
    <el-alert type="info" :closable="false" show-icon class="question-box">
      <template #title>
        <strong>问题：</strong>{{ question }}
      </template>
    </el-alert>

    <!-- 模型结果网格 -->
    <div class="result-grid">
      <el-card v-for="(r, idx) in results" :key="idx" shadow="hover" class="result-card">
        <template #header>
          <div class="card-header">
            <span class="model-name">{{ r.name }}</span>
            <el-tag v-if="r.status === 'loading'" type="warning" size="small">生成中...</el-tag>
            <el-tag v-else-if="r.status === 'done'" type="success" size="small">完成</el-tag>
            <el-tag v-else type="danger" size="small">失败</el-tag>
          </div>
        </template>

        <div v-if="r.status === 'error'" class="error-text">{{ r.content }}</div>
        <div v-else-if="r.isTyping" class="streaming-text">{{ r.displayContent }}</div>
        <MarkdownView v-else-if="r.displayContent" :content="r.displayContent" />
        <div v-else class="loading-text">
          <i class="fa fa-spinner fa-spin" /> 正在生成回答...
        </div>

        <div v-if="r.stats" class="result-stats">
          <span>耗时 {{ r.stats.totalTime?.toFixed(1) }}s</span>
          <span>速度 {{ r.stats.charsPerSec }} 字/秒</span>
        </div>
      </el-card>
    </div>
  </div>
</template>

<style scoped lang="scss">
.compare-result {
  max-width: 1200px;
  margin: 0 auto;
}

.result-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;

  h2 {
    font-size: 18px;
    font-weight: 500;
    margin: 0;
  }
}

.question-box {
  margin-bottom: 20px;
}

.result-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
  gap: 16px;
}

.result-card {
  .card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;

    .model-name {
      font-weight: 500;
      color: #303133;
    }
  }

  .loading-text,
  .error-text {
    color: #909399;
    min-height: 80px;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .error-text {
    color: #f56c6c;
  }

  .streaming-text {
    min-height: 80px;
    line-height: 1.7;
    white-space: pre-wrap;
    word-break: break-word;
  }

  .result-stats {
    display: flex;
    gap: 16px;
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid #ebeef5;
    font-size: 12px;
    color: #909399;
  }
}
</style>
