<template>
  <div class="app-container ai-review-add">
    <!-- 顶部操作栏 -->
    <el-card shadow="never" class="topbar">
      <div class="bar">
        <div class="left">
          <el-button size="small" icon="el-icon-back" @click="goBack">返回列表</el-button>
          <span class="title"><i class="el-icon-magic-stick" /> 新增 AI 审核</span>
        </div>
        <div class="right">
          <el-radio-group v-model="reviewType" size="small" :disabled="streaming">
            <el-radio-button label="contract">合同审核</el-radio-button>
            <el-radio-button label="resume">简历审核</el-radio-button>
          </el-radio-group>
          <el-input v-if="reviewType === 'resume'" v-model="position" size="small"
            placeholder="目标岗位（选填）" clearable :disabled="streaming" style="width: 200px" />
          <el-upload action="#" :auto-upload="false" :show-file-list="false" :limit="1"
            :on-change="onFileChange" accept=".pdf,.doc,.docx" :disabled="streaming">
            <el-button size="small" icon="el-icon-paperclip" :disabled="streaming">
              {{ fileName || '选择文件' }}
            </el-button>
          </el-upload>
          <el-button size="small" type="primary" icon="el-icon-cpu" :loading="streaming"
            @click="start">{{ streaming ? '审核中…' : '开始审核' }}</el-button>
        </div>
      </div>
      <div class="bar-hint">
        {{ reviewType === 'contract'
          ? '从“我方”利益角度审查合同，找出不利条款并给出利好我方的修改/补充建议。'
          : '评估候选人，分析与目标岗位的匹配度、优势、短板与面试建议。' }}
        支持 PDF / Word(.doc/.docx)，≤ 20MB。
      </div>
    </el-card>

    <el-row :gutter="12" class="body">
      <!-- 左：流式输出 -->
      <el-col :span="12" :xs="24">
        <el-card shadow="never" class="panel out-panel">
          <div slot="header" class="hd">
            <span><i class="el-icon-document" /> 审核结果</span>
            <div class="hd-tags">
              <span v-if="streaming" class="streaming-dot">● 实时生成中</span>
              <el-tag v-if="done && reviewType === 'resume' && matchScore != null" size="mini"
                type="success" effect="dark">匹配度 {{ matchScore }}</el-tag>
              <el-tag v-if="done && reviewType === 'contract' && riskLevel" size="mini"
                :type="riskTagType(riskLevel)" effect="dark">{{ riskLevel }}风险</el-tag>
              <el-button v-if="done && savedId" type="text" size="mini" icon="el-icon-view"
                @click="goDetail">查看详情</el-button>
            </div>
          </div>

          <div ref="outBody" class="out-body">
            <div v-if="!streaming && !content && !reasoning" class="placeholder">
              <i class="el-icon-cpu" />
              <div>选择文件后点击“开始审核”，结果将实时流式输出</div>
            </div>

            <!-- 思考过程 -->
            <div v-if="reasoning" class="reasoning">
              <div class="reasoning-hd" @click="showReasoning = !showReasoning">
                <i :class="showReasoning ? 'el-icon-arrow-down' : 'el-icon-arrow-right'" />
                <i class="el-icon-loading" v-if="streaming && !content" />
                思考过程
              </div>
              <pre v-show="showReasoning" class="reasoning-body">{{ reasoning }}</pre>
            </div>

            <!-- 正文（markdown 实时渲染） -->
            <div v-if="content" class="md-body" v-html="renderedMd" />
            <div v-else-if="streaming && reasoning" class="thinking-tip">AI 正在分析文档，马上输出结论…</div>
          </div>
        </el-card>
      </el-col>

      <!-- 右：文档预览 -->
      <el-col :span="12" :xs="24">
        <el-card shadow="never" class="panel preview-panel">
          <div slot="header" class="hd"><span><i class="el-icon-view" /> 文档预览</span></div>
          <div class="preview-body">
            <iframe v-if="previewUrl && isPdf" :src="previewUrl" class="pdf-frame" />
            <div v-else-if="previewUrl && !isPdf" class="preview-placeholder">
              <i class="el-icon-document" />
              <div>{{ fileName }}</div>
              <div class="sub">Word 文档暂不支持在线预览，审核结果见左侧</div>
            </div>
            <div v-else class="preview-placeholder">
              <i class="el-icon-picture-outline" />
              <div>选择 PDF 文件可在此预览</div>
            </div>
          </div>
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>

<script>
import { getToken } from '@/utils/auth'
const marked = require('marked')

export default {
  name: 'OaAiReviewAdd',
  data () {
    return {
      reviewType: 'contract',
      position: '',
      rawFile: null,
      fileName: '',

      streaming: false,
      done: false,
      reasoning: '',
      content: '',
      showReasoning: true,
      savedId: null,
      matchScore: null,
      riskLevel: null,
      eventCount: 0,

      previewUrl: '',
      isPdf: false
    }
  },
  computed: {
    renderedMd () {
      try { return marked(this.content) } catch (e) { return this.content }
    }
  },
  created () {
    marked.setOptions({ breaks: true })
  },
  beforeDestroy () {
    if (this.previewUrl) URL.revokeObjectURL(this.previewUrl)
  },
  methods: {
    goBack () { this.$router.push('/hint/aiReview') },
    goDetail () { if (this.savedId) this.$router.push('/hint/aiReview/detail/' + this.savedId) },
    riskTagType (r) { return r === '高' ? 'danger' : (r === '中' ? 'warning' : 'success') },

    onFileChange (file) {
      if (!/\.(pdf|doc|docx)$/i.test(file.name)) {
        return this.$modal.msgError('仅支持 PDF / Word(.doc/.docx)')
      }
      if (file.size > 20 * 1024 * 1024) {
        return this.$modal.msgError('文件不能超过 20MB')
      }
      this.rawFile = file.raw
      this.fileName = file.name
      // 立即生成本地预览
      if (this.previewUrl) URL.revokeObjectURL(this.previewUrl)
      this.previewUrl = URL.createObjectURL(file.raw)
      this.isPdf = /\.pdf$/i.test(file.name)
    },

    async start () {
      if (!this.rawFile) return this.$modal.msgError('请先选择文件')
      this.reasoning = ''
      this.content = ''
      this.done = false
      this.savedId = null
      this.matchScore = null
      this.riskLevel = null
      this.showReasoning = true
      this.streaming = true

      const fd = new FormData()
      fd.append('file', this.rawFile)
      fd.append('reviewType', this.reviewType)
      if (this.reviewType === 'resume' && this.position) fd.append('position', this.position)

      try {
        const resp = await fetch(process.env.VUE_APP_BASE_API + '/oa/aiReview/analyzeStream', {
          method: 'POST',
          headers: { Authorization: 'Bearer ' + getToken(), Accept: 'text/event-stream' },
          body: fd
        })
        // 非流式响应（鉴权失败 / 后端异常被包成 JSON / HTML）→ 读出来报错，避免静默
        const ct = resp.headers.get('content-type') || ''
        if (!resp.ok || !resp.body || ct.indexOf('text/event-stream') === -1) {
          let msg = '审核失败（HTTP ' + resp.status + '）'
          try {
            const txt = await resp.text()
            try { const j = JSON.parse(txt); if (j && (j.msg || j.message)) msg = j.msg || j.message }
            catch (e) { if (txt) msg = txt.slice(0, 200) }
          } catch (e) {}
          this.$modal.msgError(msg)
          this.eventCount = 0
          this.streaming = false
          return
        }
        const reader = resp.body.getReader()
        const decoder = new TextDecoder('utf-8')
        let buf = ''
        this.eventCount = 0
        // eslint-disable-next-line no-constant-condition
        while (true) {
          const { value, done } = await reader.read()
          if (done) break
          buf += decoder.decode(value, { stream: true })
          let idx
          while ((idx = buf.indexOf('\n\n')) >= 0) {
            const frame = buf.slice(0, idx)
            buf = buf.slice(idx + 2)
            this.handleFrame(frame)
          }
        }
        if (this.eventCount === 0) {
          this.$modal.msgError('未收到任何流式数据，请检查后端是否可访问 AI 服务')
        }
      } catch (e) {
        this.$modal.msgError('连接中断：' + (e.message || e))
      } finally {
        this.streaming = false
      }
    },

    handleFrame (frame) {
      let data = ''
      for (const line of frame.split('\n')) {
        if (line.startsWith('data:')) data += line.slice(5).trim()
      }
      if (!data) return
      let ev
      try { ev = JSON.parse(data) } catch (e) { return }
      this.eventCount++
      if (ev.type === 'start') {
        // 通道已打开，等待解析/生成
      } else if (ev.type === 'reasoning') {
        this.reasoning += ev.c
        this.scrollBottom()
      } else if (ev.type === 'content') {
        if (this.content === '') this.showReasoning = false // 正文开始时折叠思考过程
        this.content += ev.c
        this.scrollBottom()
      } else if (ev.type === 'done') {
        this.done = true
        this.savedId = ev.id
        this.matchScore = ev.matchScore
        this.riskLevel = ev.riskLevel
        this.$modal.msgSuccess('审核完成')
      } else if (ev.type === 'error') {
        this.$modal.msgError(ev.msg || '审核失败')
      }
    },

    scrollBottom () {
      this.$nextTick(() => {
        const el = this.$refs.outBody
        if (el) el.scrollTop = el.scrollHeight
      })
    }
  }
}
</script>

<style scoped lang="scss">
.ai-review-add { padding: 10px; }
.topbar { margin-bottom: 8px; ::v-deep .el-card__body { padding: 10px 14px; } }
.bar { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px;
  .left { display: flex; align-items: center; gap: 12px;
    .title { font-weight: 600; font-size: 15px; color: #303133; i { color: #409eff; margin-right: 4px; } }
  }
  .right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
}
.bar-hint { font-size: 12px; color: #909399; margin-top: 8px; }

.body { margin-top: 0; }
.panel { ::v-deep .el-card__header { padding: 9px 14px; } }
.hd { display: flex; justify-content: space-between; align-items: center;
  font-size: 13px; font-weight: 600; color: #303133; i { color: #409eff; margin-right: 4px; }
  .hd-tags { display: flex; align-items: center; gap: 8px; }
  .streaming-dot { color: #67c23a; font-size: 12px; animation: blink 1s infinite; }
}

.out-panel, .preview-panel { height: calc(100vh - 180px); ::v-deep .el-card__body { height: calc(100% - 40px); padding: 0; } }
.out-body { height: 100%; overflow-y: auto; padding: 12px 16px; }
.placeholder, .preview-placeholder {
  text-align: center; color: #c0c4cc; padding: 90px 20px;
  i { font-size: 52px; opacity: .4; display: block; margin-bottom: 14px; }
  .sub { font-size: 12px; margin-top: 6px; }
}
.thinking-tip { color: #909399; font-size: 13px; padding: 8px 0; }

.reasoning { margin-bottom: 12px; border: 1px dashed #e0e3e9; border-radius: 6px; background: #fafbfc; }
.reasoning-hd { cursor: pointer; user-select: none; padding: 6px 10px; font-size: 12px; color: #909399;
  i { margin-right: 4px; } }
.reasoning-body { margin: 0; padding: 0 12px 10px; font-size: 12px; color: #b0b3b8; line-height: 1.6;
  white-space: pre-wrap; word-break: break-word; max-height: 200px; overflow-y: auto; }

.preview-body { height: 100%; }
.pdf-frame { width: 100%; height: 100%; border: none; }

.md-body {
  font-size: 14px; color: #2c3e50; line-height: 1.8;
  ::v-deep {
    h1, h2, h3 { color: #303133; margin: 16px 0 9px; font-weight: 600; }
    h1 { font-size: 19px; } h2 { font-size: 16px; border-left: 3px solid #409eff; padding-left: 8px; }
    h3 { font-size: 14px; }
    p { margin: 8px 0; }
    ul, ol { padding-left: 22px; margin: 8px 0; }
    li { margin: 4px 0; }
    strong { color: #c0392b; }
    code { background: #f4f7fa; padding: 1px 5px; border-radius: 3px; font-size: 13px; color: #e6a23c; }
    table { border-collapse: collapse; width: 100%; margin: 10px 0; }
    th, td { border: 1px solid #ebeef5; padding: 6px 10px; font-size: 13px; }
    th { background: #f5f7fa; }
    blockquote { border-left: 3px solid #dcdfe6; padding-left: 10px; color: #909399; margin: 8px 0; }
  }
}

@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
</style>
