<template>
  <div class="document-compare-page" @click="onPageClick">
    <intelligence-left-tab-bar />

    <div class="compare-main">
      <header class="page-header">
        <div class="page-header-left">
          <img class="header-file-icon" src="./icons/file-type-icon.png" alt="" />
          <span class="page-header-file-name">{{ displayFileName }}</span>
        </div>

        <div v-if="isCompareLoading" class="page-header-status">
          <span class="status-loading-dot" />
          <span class="page-header-status-title">文档解析中</span>
          <span class="page-header-status-text">已完成...{{ translationProgress }}%，已用时{{ elapsedSeconds }}秒</span>
        </div>

        <div v-else class="page-header-action-group" @click.stop>
          <button
            v-for="action in headerActions"
            :key="action.key"
            type="button"
            class="header-action-btn"
            :class="action.variant === 'primary' ? 'header-action-btn--primary' : ''"
            @click="onHeaderAction(action.key)"
          >
            {{ action.label }}
          </button>
        </div>
      </header>

      <div class="compare-body">
        <div v-if="isCompareLoading" class="compare-loading-placeholder">
          <img class="compare-loading-placeholder-img" :src="comparePlaceholderUrl" alt="" />
          <div class="compare-loading-placeholder-text">{{ compareLoadingText }}</div>
        </div>

        <template v-else>
          <div class="compare-content">
            <aside class="article-nav-tree">
              <div class="tree-title">导航</div>
              <el-tree
                ref="articleTreeRef"
                :data="articleTree"
                node-key="id"
                :props="treeProps"
                :current-node-key="currentNodeKey"
                highlight-current
                default-expand-all
                @node-click="onClickTreeNode"
              />
            </aside>

            <div ref="compareScrollRef" class="sync-compare-scroll" @scroll="onSyncScroll">
              <section
                v-for="page in pageBlocks"
                :key="page.pageId"
                :id="page.pageId"
                class="page-block"
                :class="{ 'page-block--active': currentPageId === page.pageId }"
              >
                <div class="page-block__header">{{ page.pageTitle }}</div>
                <div class="page-block__body">
                  <article
                    v-for="item in page.items"
                    :key="`${page.pageId}-${item.id}`"
                    class="page-item"
                    :data-section-id="item.id"
                  >
                    <div class="page-item__text page-item__text--en" :class="[getBlockVariantClass(item.type), shouldCenterBlock(item) ? 'page-item__text--center' : '']">
                      <div v-if="item.type === 'table' && item.tableHtml" class="page-item__html" v-html="item.tableHtml"></div>
                      <img
                        v-else-if="item.type === 'image' && item.previewImgPath"
                        class="page-item__image"
                        :src="item.previewImgPath"
                        alt=""
                      />
                      <div v-else class="page-item__content" v-html="renderBlockHtml(item.text, item.styleSpans)"></div>
                    </div>
                    <div class="page-item__text page-item__text--zh" :class="[getBlockVariantClass(item.type), shouldCenterBlock(item) ? 'page-item__text--center' : '']">
                      <div v-if="item.type === 'table' && item.translatedTableHtml" class="page-item__html" v-html="item.translatedTableHtml"></div>
                      <img
                        v-else-if="item.type === 'image' && item.previewImgPath"
                        class="page-item__image"
                        :src="item.previewImgPath"
                        alt=""
                      />
                      <div v-else class="page-item__content" v-html="renderBlockHtml(item.translatedText || item.text || '（待接入译文）', item.styleSpans)"></div>
                    </div>
                  </article>
                </div>
              </section>
            </div>
          </div>
        </template>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useRoute } from 'vue-router'
import IntelligenceLeftTabBar from '@/components/intelligenceLeftTabBar/index.vue'
import comparePlaceholderUrl from './icons/container-1885-placeholder.png'
import mergedAns from './merged_ans.json'

const route = useRoute()

const ESTIMATED_SECONDS = 65
const headerActions = [
  { key: 'submit-review', label: '提交审核' },
  { key: 'preview', label: '预览' },
  { key: 'export', label: '导出' },
  { key: 'write-report', label: '写报' },
  { key: 'save', label: '保存', variant: 'primary' }
]

const normalizedPages = ref([])
const normalizedBlocks = ref([])
const articleTree = ref([])
const articleTreeRef = ref(null)
const compareScrollRef = ref(null)
const currentNodeKey = ref('')
const currentPageId = ref('')
const isCompareLoading = ref(true)
const elapsedSeconds = ref(0)
const translationProgress = ref(0)
const timerId = ref(null)
const treeProps = { label: 'title', children: 'children' }

const translatedTextLookup = new Map([
  ['page0_node0', 'I. 基本信息'],
  ['page0_node1', '美国能源部（DOE）'],
  ['page0_node2', '科学办公室（SC）、关键矿产与能源创新办公室（CMEI）、环境管理办公室（EM）、电力办公室（OE）、核能办公室（NE）以及碳氢化合物与地热能源办公室（HGEO）'],
  ['page0_node3', '执行摘要'],
  ['page0_node4', 'DOE 相关办公室现公开征集跨学科团队申请，围绕 Genesis Mission 国家科学与技术挑战，推动利用新型人工智能模型和框架加速科学发现与研发流程。'],
  ['page0_node5', 'DOE 正在征集 FY26 第一阶段小团队与第二阶段大团队申请，涵盖先进制造、生物技术、关键材料、核裂变、核聚变、量子信息科学、半导体与微电子、发现科学和能源等领域。'],
  ['page0_node6', '本次 RFA 将继续开放，允许 FY26 第一阶段获奖者申请更大规模的第二阶段资助。'],
  ['page0_node7', '在对应截止日期之后，仍可提交第一阶段和第二阶段申请，但 DOE 保留拒绝此类申请且不予审查的权利。'],
  ['page0_node8', '1. Genesis Mission 联盟成员'],
  ['page0_node9', 'Genesis Mission 联盟成员可提交申请，资金将通过 DOE 的其他交易授权（OTA）提供。'],
  ['page1_node0', '就 OTA 而言，本 RFA 的部分行政规定可能不适用于申请人或其分包商。'],
  ['page1_node1', '请注意，是否属于 Genesis Mission 联盟成员并非申请本 RFA 或参与入选项目的必要条件。'],
  ['page1_node2', '所有申请人注意事项：'],
  ['page1_node3', '获得本 RFA 下的奖励并不赋予，也不要求申请人加入 Genesis Mission 联盟。'],
  ['page1_node4', '申请人可在申请中提议非本土实体作为分包方。'],
  ['page1_node5', '资助详情'],
  ['page1_node6', '预计总可用资金、奖项数量、单项资助金额与项目周期见表。'],
  ['page1_node7', '关键信息'],
  ['page1_node8', 'RFA 名称、编号、公告类型、援助清单、法定授权与适用法规。'],
  ['page2_node0', '表格信息摘要'],
  ['page2_node1', '关键日期'],
  ['page2_node2', '关键日期见本 RFA 封面页。'],
  ['page2_node3', '提交组队要求'],
  ['page2_node4', '本 RFA 要求提交内容为多机构团队。'],
  ['page2_node5', '机构联系信息'],
  ['page2_node6', 'Grants.gov 客服与项目联系人信息。'],
  ['page2_node7', '说明性网络研讨会 / 办公时间'],
  ['page2_node8', 'DOE 将于 2026 年 3 月 26 日星期四下午 3 点（东部时间）举办说明性网络研讨会。'],
  ['page2_node9', '建议'],
  ['page2_node10', '建议尽早完成各系统注册并尽早提交申请。'],
  ['page2_node11', '3']
])

const displayFileName = computed(() => {
  const fileName = route.query.fileName
  return typeof fileName === 'string' && fileName ? fileName : '未命名文档（占位）'
})

const compareLoadingText = computed(() => `文档解析中，预计用时${ESTIMATED_SECONDS}秒，已用时${elapsedSeconds.value}秒...`)

const pageBlocks = computed(() => {
  return normalizedPages.value.map((page) => ({
    pageId: `page-${page.pageIdx}`,
    pageTitle: `第 ${page.pageIdx + 1} 页`,
    items: normalizedBlocks.value.filter((block) => block.pageIdx === page.pageIdx)
  }))
})

function shouldCenterBlock(block) {
  if (!block) return false
  if (block.type !== 'heading') return false
  const text = String(block.text || '').trim()
  if (!text) return false
  const strongHeading = block.headingLevel != null
  const chapterLike = /^(I|II|III|IV|V|VI|VII|VIII|IX|X|XI|XII)[\.|、\s]/i.test(text)
  const alphaHeading = /^[A-Z][a-zA-Z\s/&-]{3,}$/.test(text) && !/^[A-Z]{1,3}\b/.test(text)
  const chineseHeading = /^[一二三四五六七八九十]+[、.]/.test(text)
  const notNumericList = !/^\d+[\.|、]/.test(text)
  return strongHeading || (notNumericList && (chapterLike || alphaHeading || chineseHeading))
}

function mapSemanticTypeToNodeType(semanticType, contentPayload = {}) {
  if (semanticType === 'heading' || contentPayload.heading_level != null) return 'heading'
  if (semanticType === 'paragraph') return 'paragraph'
  if (semanticType === 'table' || contentPayload.type === 'table') return 'table'
  if (semanticType === 'page_number') return 'page_number'
  if (semanticType === 'image' || contentPayload.preview_img_path) return 'image'
  return 'unknown'
}

function getBlockTypeLabel(type) {
  const labelMap = { heading: '标题', paragraph: '正文', table: '表格', image: '图片', page_number: '页码', unknown: '未知' }
  return labelMap[type] || '未知'
}

function getBlockVariantClass(type) {
  return `document-block--${type || 'unknown'}`
}

function escapeHtml(text = '') {
  return String(text)
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&#39;')
}

function renderBlockHtml(text = '', styleSpans = []) {
  const safeText = escapeHtml(text)
  if (!Array.isArray(styleSpans) || styleSpans.length === 0) return safeText.replaceAll('\n', '<br />')

  const sorted = [...styleSpans].sort((a, b) => a.start - b.start)
  let html = ''
  let cursor = 0

  sorted.forEach((span) => {
    const start = Math.max(0, span.start || 0)
    const end = Math.max(start, span.end || start)
    if (start > cursor) html += escapeHtml(text.slice(cursor, start))
    const spanText = escapeHtml(text.slice(start, end))
    const style = span?.style || {}
    const inlineStyle = [
      style.bold ? 'font-weight:700' : '',
      style.italic ? 'font-style:italic' : '',
      style.underline ? 'text-decoration:underline' : '',
      style.color_hex ? `color:${style.color_hex}` : ''
    ].filter(Boolean).join(';')
    html += `<span style="${inlineStyle}">${spanText}</span>`
    cursor = end
  })

  if (cursor < text.length) html += escapeHtml(text.slice(cursor))
  return html.replaceAll('\n', '<br />')
}

function buildNormalizedBlocks(rawData) {
  const pages = Array.isArray(rawData?.data?.pages) ? rawData.data.pages : []
  const nodes = Array.isArray(rawData?.data?.merged_nodes) ? rawData.data.merged_nodes : []

  normalizedPages.value = pages.map((page) => ({
    pageIdx: page.page_idx,
    width: page.width,
    height: page.height,
    rotation: page.rotation,
    nodeIds: Array.isArray(page.node_ids) ? page.node_ids : []
  }))

  normalizedBlocks.value = nodes.map((node, index) => {
    const contentPayload = node?.content_payload || {}
    const type = mapSemanticTypeToNodeType(node.semantic_type, contentPayload)
    return {
      id: node.node_id || `node_${index}`,
      nodeId: node.node_id || '',
      pageIdx: node.page_idx ?? null,
      type,
      semanticType: node.semantic_type || 'unknown',
      readingOrder: node?.layout?.reading_order ?? index,
      title: contentPayload.text || node.node_id || `未命名块${index + 1}`,
      text: contentPayload.text || '',
      translatedText: translatedTextLookup.get(node.node_id) || '',
      html: contentPayload.html || '',
      headingLevel: contentPayload.heading_level ?? null,
      tableHtml: contentPayload.table_body_html || '',
      translatedTableHtml: contentPayload.table_body_html || '',
      previewImgPath: contentPayload.preview_img_path || '',
      layout: {
        bbox: node?.layout?.bbox || [],
        bboxNorm: node?.layout?.bbox_norm || [],
        angle: node?.layout?.angle ?? 0,
        link: node?.layout?.link ?? null,
        zIndex: node?.layout?.z_index ?? null
      },
      styleSpans: Array.isArray(node.style_spans) ? node.style_spans : []
    }
  })
}

function buildNavigationTree(rawData) {
  const nodes = Array.isArray(rawData?.data?.merged_nodes) ? rawData.data.merged_nodes : []
  articleTree.value = nodes
    .map((node, index) => {
      const contentPayload = node?.content_payload || {}
      const nodeType = mapSemanticTypeToNodeType(node.semantic_type, contentPayload)
      const text = contentPayload.text || node.node_id || '未命名块'
      if (nodeType !== 'heading') return null
      if (!shouldCenterBlock({ ...node, text })) return null
      return {
        id: node.node_id || `heading_${index}`,
        title: text,
        pageIdx: node.page_idx ?? 0,
        nodeType,
        sectionId: node.node_id || `heading_${index}`,
        children: []
      }
    })
    .filter(Boolean)
}

function findBlockElementById(blockId) {
  return document.querySelector(`[data-section-id='${blockId}']`)
}

async function onClickTreeNode(nodeData) {
  currentNodeKey.value = nodeData.id
  const targetPage = pageBlocks.value.find((page) => page.items.some((item) => item.id === nodeData.id))
  currentPageId.value = targetPage?.pageId || currentPageId.value
  await nextTick()
  findBlockElementById(nodeData.id)?.scrollIntoView({ behavior: 'smooth', block: 'center' })
}

function onSyncScroll() {
  const scroller = compareScrollRef.value
  if (!scroller) return
  const pageEls = Array.from(scroller.querySelectorAll('.page-block'))
  const viewportTop = scroller.getBoundingClientRect().top
  let activePage = pageBlocks.value[0]?.pageId || ''

  for (const pageEl of pageEls) {
    const rect = pageEl.getBoundingClientRect()
    if (rect.top - viewportTop <= 96) activePage = pageEl.id
  }

  currentPageId.value = activePage
  const activePageBlocks = pageBlocks.value.find((page) => page.pageId === activePage)?.items || []
  const activeHeading = articleTree.value.find((item) => activePageBlocks.some((block) => block.id === item.id))
  if (activeHeading) currentNodeKey.value = activeHeading.id
}

function onPageClick() {
  currentNodeKey.value = currentPageId.value || currentNodeKey.value
}

function onHeaderAction(actionKey) {
  ElMessage.info(`顶部功能按钮占位：${actionKey}`)
}

function getFirstNavigableNode(nodes) {
  if (!Array.isArray(nodes)) return null
  return nodes.find((node) => node?.id) || null
}

onMounted(async () => {
  const requestStartTime = Date.now()
  const updateElapsedSeconds = () => {
    elapsedSeconds.value = Math.floor((Date.now() - requestStartTime) / 1000)
  }

  updateElapsedSeconds()
  timerId.value = window.setInterval(updateElapsedSeconds, 1000)

  try {
    buildNormalizedBlocks(mergedAns)
    buildNavigationTree(mergedAns)
    translationProgress.value = Number(mergedAns?.data?.pages?.length ? 100 : 0)
    const firstNode = getFirstNavigableNode(articleTree.value)
    if (firstNode) {
      currentNodeKey.value = firstNode.id
      currentPageId.value = `page-${firstNode.pageIdx ?? 0}`
    }
  } finally {
    isCompareLoading.value = false
  }
})

onUnmounted(() => {
  if (timerId.value !== null) {
    clearInterval(timerId.value)
    timerId.value = null
  }
})
</script>

<style scoped lang="scss">
.document-compare-page {
  display: flex;
  height: calc(100vh - 64px);
  min-height: calc(100vh - 64px);
  overflow: hidden;
  background: #fff;
}

.compare-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  position: relative;
}

.page-header {
  box-sizing: border-box;
  flex-shrink: 0;
  width: 100%;
  height: 64px;
  padding: 0 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  background: var(--bg-white-100);
  box-shadow: 0 0 20px 0 rgba(25, 69, 130, 0.1);
  border-bottom: 1px solid var(--bg-black-5);
}

.compare-body {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  padding: 8px;
  background: var(--color-primary-2);
  box-sizing: border-box;
}

.page-header-left { display: flex; align-items: center; gap: 8px; min-width: 0; }
.header-file-icon { width: 24px; height: 24px; flex-shrink: 0; display: block; }
.page-header-file-name { font-family: 'Source Han Sans CN', sans-serif; font-size: 18px; font-weight: 400; line-height: 26px; color: var(--text-primary-80-color, #344054); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.page-header-status { display: inline-flex; align-items: center; height: 38px; padding: 0 12px; border: 1px solid var(--color-primary-35); border-radius: 50px; background: var(--color-primary-10); gap: 12px; flex-shrink: 0; box-sizing: border-box; }
.status-loading-dot { width: 14px; height: 14px; border: 2px solid var(--color-primary-35); border-top-color: var(--color-primary-100, #2563eb); border-radius: 50%; animation: compare-spin 0.8s linear infinite; }
.page-header-status-title, .page-header-status-text { font-family: 'Source Han Sans CN', sans-serif; font-size: 14px; line-height: 22px; color: var(--color-primary-100); }
.page-header-status-title { font-weight: 700; }
.page-header-status-text { font-weight: 400; }
.page-header-action-group { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.header-action-btn { box-sizing: border-box; padding: 6px 19px; border: 1px solid var(--bg-black-10); border-radius: 6px; background: #fff; font-family: 'Source Han Sans CN', sans-serif; font-weight: 400; font-size: 16px; line-height: 24px; color: var(--text-primary-80-color, #344054); cursor: pointer; }
.header-action-btn--primary { background: var(--color-primary-100); border: none; color: var(--bg-white-100); }

@keyframes compare-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.compare-loading-placeholder { flex: 1; min-height: 560px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 16px; box-sizing: border-box; background: var(--bg-white-100); border-radius: 8px; }
.compare-loading-placeholder-img { max-width: 100%; max-height: min(720px, calc(100vh - 160px)); width: auto; height: auto; object-fit: contain; display: block; }
.compare-loading-placeholder-text { font-family: 'Source Han Sans CN', sans-serif; font-weight: 400; font-size: 16px; line-height: 30px; color: #344054; }
.compare-content { flex: 1; min-height: 0; display: flex; gap: 12px; overflow: hidden; }
.article-nav-tree { width: 280px; flex-shrink: 0; padding: 16px 19px; display: flex; flex-direction: column; min-height: 0; overflow: hidden; border: 1px solid #e8ecf2; border-radius: 8px; background: #fff; }
.tree-title { font-family: 'Source Han Sans CN', sans-serif; font-weight: 700; font-size: 16px; line-height: 24px; letter-spacing: 1px; color: var(--text-primary-80-color); margin-bottom: 17px; }
:deep(.article-nav-tree .el-tree-node__label) { font-family: 'Source Han Sans CN', sans-serif; font-weight: 400; font-size: 14px; line-height: 22px; color: var(--text-primary-80-color); }
.sync-compare-scroll { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; overflow-x: hidden; border: 1px solid #e8ecf2; border-radius: 8px; background: #fff; padding: 16px; box-sizing: border-box; }
.page-block { display: flex; flex-direction: column; gap: 12px; padding: 12px 0 20px; border-bottom: 1px solid #d9e2ec; }
.page-block--active { background: #f8fafc; }
.page-block__header { font-family: 'Source Han Sans CN', sans-serif; font-size: 15px; font-weight: 700; line-height: 24px; color: #101828; }
.page-block__body { display: flex; flex-direction: column; gap: 12px; }
.page-item { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; align-items: stretch; }
.page-item__text { min-height: 72px; padding: 0; background: transparent; border: none; }
.page-item__text--en { border-right: 1px solid #eef2f7; padding-right: 12px; }
.page-item__text--zh { padding-left: 12px; }
.page-item__text--center .page-item__content { text-align: center; }
.page-item__content { font-size: 14px; line-height: 1.8; color: #344054; white-space: pre-wrap; word-break: break-word; }
.page-item__html :deep(table) { width: 100%; border-collapse: collapse; font-size: 13px; }
.page-item__html :deep(td), .page-item__html :deep(th) { border: 1px solid #d0d5dd; padding: 8px; vertical-align: top; }
.page-item__image { max-width: 100%; display: block; border-radius: 6px; }
.page-item--heading .page-item__content { font-size: 18px; font-weight: 700; color: #101828; }
.page-item--paragraph .page-item__content { font-size: 14px; }
.page-item--table .page-item__content { font-size: 13px; }
.page-item--image .page-item__content { font-size: 13px; color: #667085; }
.page-item--page_number .page-item__content { font-size: 12px; color: #98a2b3; }
.page-item--unknown .page-item__content { color: #667085; font-style: italic; }
</style>
