<template>
  <div class="document-compare-page">
    <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>
      </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">
            <ArticleNavTree :tree-data="outlineTree" :current-node-key="activeOutlineNodeId || currentNodeKey" @node-click="onClickTreeNode" />
            <div class="compare-panels">
              <section class="panel panel--source">
                <div class="panel__header">
                  <div>
                    <div class="panel__title">原文预览</div>
                    <div class="panel__subtitle">优先加载 PDF 原文，无法获取时展示占位提示</div>
                  </div>
                </div>
                <PdfPreview
                  :pdf-url="pdfPreviewUrl"
                  :file-name="displayFileName"
                  :fallback-src="comparePlaceholderUrl"
                  :active-page="currentPageIdx + 1"
                  @page-change="onPreviewPageChange"
                />
              </section>
              <section class="panel panel--target">
                <div class="panel__header">
                  <div>
                    <div class="panel__title">译文编辑器</div>
                    <div class="panel__subtitle">基于 block 模型初始化，可直接编辑与导出</div>
                  </div>
                </div>
                <TranslationEditor
                  :doc="currentEditorDoc"
                  :block-count="blockModel.blocks.length"
                  :readonly-block-count="blockModel.blocks.filter((block) => block.isReadOnly).length"
                  :readonly-blocks="blockModel.blocks.filter((block) => block.isReadOnly)"
                  :active-page="currentPageIdx + 1"
                  @update:doc="onEditorDocUpdate"
                />
              </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 ArticleNavTree from './components/ArticleNavTree.vue'
import PdfPreview from './components/PdfPreview.vue'
import TranslationEditor from './components/TranslationEditor.vue'
import { fetchDocumentMeta, fetchDocumentPage, toBlockListFromEditorDoc } from './services/documentCompareService'

const route = useRoute()
const ESTIMATED_SECONDS = 65

const summary = ref(null)
const pageIndexList = ref([])
const outlineTree = ref([])
const pageBlockMap = ref({})
const loadingPageSet = ref(new Set())
const currentPageIdx = ref(0)
const currentNodeKey = ref('')
const selectedNodeKey = ref('')
const activeOutlineNodeId = ref('')
const isCompareLoading = ref(true)
const isJumpingToTarget = ref(false)
const elapsedSeconds = ref(0)
const translationProgress = ref(0)
const timerId = ref(null)
const jumpTimerId = ref(null)
const blockModel = ref({ blocks: [], pageMap: {}, nodeMap: {}, pageNodeMap: {} })
const currentEditorDoc = ref({ type: 'doc', content: [] })

const displayFileName = computed(() => {
  const fileName = route.query.fileName
  if (typeof fileName === 'string' && fileName) return fileName
  return summary.value?.source_file_name || '未命名文档（占位）'
})
const compareLoadingText = computed(() => `文档解析中，预计用时${ESTIMATED_SECONDS}秒，已用时${elapsedSeconds.value}秒...`)
const pdfPreviewUrl = computed(() => route.query.pdfUrl || summary.value?.source_file_url || summary.value?.preview_url || summary.value?.document_url || '')

function getFirstPageIdx() {
  if (outlineTree.value.length > 0) return Number(outlineTree.value[0]?.page_idx || 0)
  return Number(pageIndexList.value[0]?.page_idx || 0)
}

function getBlockSummary() {
  return {
    blockCount: Array.isArray(blockModel.value.blocks) ? blockModel.value.blocks.length : 0,
    pageCount: Array.isArray(pageIndexList.value) ? pageIndexList.value.length : 0,
    outlineCount: Array.isArray(outlineTree.value) ? outlineTree.value.length : 0
  }
}

async function ensurePageLoaded(pageIdx) {
  const pageIndex = Number(pageIdx)
  if (Number.isNaN(pageIndex)) return
  if (pageBlockMap.value[pageIndex]) return
  if (loadingPageSet.value.has(pageIndex)) return
  loadingPageSet.value.add(pageIndex)
  try {
    const response = await fetchDocumentPage(pageIndex)
    const pageData = response?.data
    pageBlockMap.value = {
      ...pageBlockMap.value,
      [pageIndex]: {
        page: pageData?.page || { page_idx: pageIndex },
        nodes: Array.isArray(pageData?.nodes) ? pageData.nodes : []
      }
    }
  } catch (error) {
    ElMessage.error(error?.message || `第 ${pageIndex + 1} 页加载失败`)
  } finally {
    loadingPageSet.value.delete(pageIndex)
  }
}

async function preloadNeighborPages(centerPageIdx) {
  const targetIndexes = [centerPageIdx - 1, centerPageIdx, centerPageIdx + 1, centerPageIdx + 2]
    .filter((index) => index >= 0 && index < pageIndexList.value.length)
  for (const pageIdx of targetIndexes) {
    await ensurePageLoaded(pageIdx)
  }
}

async function preloadPagesForJump(targetPageIdx) {
  const safeTargetIdx = Math.max(0, Number(targetPageIdx || 0))
  for (let pageIdx = 0; pageIdx <= safeTargetIdx; pageIdx += 1) {
    await ensurePageLoaded(pageIdx)
  }
}

function onActiveNodeChange(nodeId) {
  if (!nodeId) return
  currentNodeKey.value = String(nodeId)
  activeOutlineNodeId.value = String(nodeId)
}

function onPreviewPageChange(pageNumber) {
  const nextPageIdx = Math.max(0, Number(pageNumber || 1) - 1)
  if (!Number.isNaN(nextPageIdx)) {
    currentPageIdx.value = nextPageIdx
    syncOutlineActiveByPage(nextPageIdx)
  }
}

function onEditorDocUpdate(doc) {
  const safeDoc = doc || { type: 'doc', content: [] }
  blockModel.value = {
    ...blockModel.value,
    lastEditedDoc: safeDoc,
    editedBlocks: toBlockListFromEditorDoc(safeDoc)
  }
}

async function onClickTreeNode(nodeData) {
  const pageIdx = Number(nodeData?.page_idx || 0)
  const nodeId = String(nodeData?.id || '')
  currentNodeKey.value = nodeId
  activeOutlineNodeId.value = nodeId
  selectedNodeKey.value = nodeId
  currentPageIdx.value = pageIdx
  isJumpingToTarget.value = true
  if (jumpTimerId.value !== null) {
    clearTimeout(jumpTimerId.value)
    jumpTimerId.value = null
  }
  try {
    await preloadPagesForJump(pageIdx)
    await nextTick()
    pageListRef.value?.scrollToNode(nodeId, { behavior: 'auto' })
    await nextTick()
  } finally {
    jumpTimerId.value = window.setTimeout(() => {
      isJumpingToTarget.value = false
      currentNodeKey.value = nodeId
      jumpTimerId.value = null
    }, 200)
  }
}

async function onPageVisible(pageIdx) {
  await ensurePageLoaded(pageIdx)
}

function onNodeSelect(nodeData) {
  const nodeId = String(nodeData?.node_id || '')
  if (!nodeId) return
  selectedNodeKey.value = nodeId
  const block = blockModel.value.nodeMap?.[nodeId]
  if (block) {
    currentPageIdx.value = Number(block.pageIdx || 0)
    currentNodeKey.value = nodeId
  }
}

function syncOutlineActiveByPage(pageIdx) {
  const pageNumber = Number(pageIdx || 0)
  if (Number.isNaN(pageNumber)) return
  const matchedNode = outlineTree.value.find((item) => Number(item?.page_idx || 0) === pageNumber)
  if (matchedNode?.id) {
    activeOutlineNodeId.value = String(matchedNode.id)
    currentNodeKey.value = String(matchedNode.id)
  }
}

async function initPageData() {
  const response = await fetchDocumentMeta()
  const responseData = response?.data || {}
  summary.value = responseData.document_meta || {}
  pageIndexList.value = Array.isArray(responseData.pages) ? responseData.pages : []
  outlineTree.value = Array.isArray(responseData.outline) ? responseData.outline : []
  blockModel.value = responseData.block_model || { blocks: [], pageMap: {}, nodeMap: {}, pageNodeMap: {} }
  currentEditorDoc.value = responseData.initial_tiptap_doc || { type: 'doc', content: [] }
  translationProgress.value = pageIndexList.value.length > 0 ? 100 : 0
  currentPageIdx.value = getFirstPageIdx()
  currentNodeKey.value = String(outlineTree.value[0]?.id || '')
  activeOutlineNodeId.value = String(outlineTree.value[0]?.id || '')
  await preloadNeighborPages(currentPageIdx.value)
  console.debug('[intelligentTranslation] block summary', getBlockSummary())
}

onMounted(async () => {
  const requestStartTime = Date.now()
  const updateElapsedSeconds = () => {
    elapsedSeconds.value = Math.floor((Date.now() - requestStartTime) / 1000)
  }
  updateElapsedSeconds()
  timerId.value = window.setInterval(updateElapsedSeconds, 1000)
  try {
    await initPageData()
  } catch (error) {
    ElMessage.error(error?.message || '初始化数据失败')
  } finally {
    isCompareLoading.value = false
  }
})

onUnmounted(() => {
  if (timerId.value !== null) {
    clearInterval(timerId.value)
    timerId.value = null
  }
  if (jumpTimerId.value !== null) {
    clearTimeout(jumpTimerId.value)
    jumpTimerId.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;
}

.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; }

@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-size: 16px;
  line-height: 30px;
  color: #344054;
}

.compare-content {
  flex: 1;
  min-height: 0;
  display: flex;
  gap: 12px;
  overflow: hidden;
}

.compare-panels {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(420px, 1fr) minmax(520px, 1.2fr);
  gap: 12px;
}

.panel {
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid #e8ecf2;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.04);
}

.panel__header {
  flex-shrink: 0;
  padding: 16px 18px 12px;
  border-bottom: 1px solid #eef2f7;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.panel__title {
  font-size: 16px;
  font-weight: 700;
  color: #101828;
  line-height: 24px;
}

.panel__subtitle {
  margin-top: 4px;
  font-size: 13px;
  line-height: 20px;
  color: #667085;
}

.panel--source {
  background: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%);
}

.page-panel {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  padding: 12px;
  box-sizing: border-box;
}

.jump-loading-mask {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.72);
  backdrop-filter: blur(2px);
  pointer-events: none;
}

.jump-loading-dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--color-primary-35);
  border-top-color: var(--color-primary-100, #2563eb);
  animation: compare-spin 0.8s linear infinite;
}

.jump-loading-text {
  font-family: 'Source Han Sans CN', sans-serif;
  font-size: 14px;
  line-height: 22px;
  color: var(--color-primary-100, #2563eb);
  font-weight: 600;
}

.jump-fade-enter-active,
.jump-fade-leave-active {
  transition: opacity 0.18s ease;
}

.jump-fade-enter-from,
.jump-fade-leave-to {
  opacity: 0;
}
</style>
