<script setup lang="ts">
import { computed, nextTick, ref, shallowRef, watch } from 'vue'
import {
  getDataProcessOfficePreview,
  getDataProcessSourceRawUrl,
  type DataProcessDocxPreview,
  type DataProcessDocxTableRow,
  type DataProcessOfficePreview,
  type DataProcessXlsxPreview,
  type DataProcessXlsxPreviewRow,
} from '@/api/modules/dataProcess'
import type { PreviewItem } from './types'

const props = defineProps<{
  taskId: string | number | null
  sourceFileId: string | number | null
  fileName: string
  fileFormat?: string
  selectedItem: PreviewItem | null
}>()

const XLSX_PAGE_SIZE = 100
const scrollRef = ref<HTMLElement | null>(null)
const preview = shallowRef<DataProcessOfficePreview | null>(null)
const loading = ref(false)
const errorMessage = ref('')
const activeSheetIndex = ref(0)
const pageOffset = ref(0)
let loadSequence = 0
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches

const normalizedFormat = computed(() => (
  props.fileFormat?.toLowerCase().replace(/^\./, '')
  || props.fileName.split('.').pop()?.toLowerCase()
  || ''
))
const isDocx = computed(() => normalizedFormat.value === 'docx')
const docxPreview = computed((): DataProcessDocxPreview | null => (
  preview.value?.format === 'docx' ? preview.value : null
))
const xlsxPreview = computed((): DataProcessXlsxPreview | null => (
  preview.value?.format === 'xlsx' ? preview.value : null
))
const sourceUrl = computed(() => (
  props.taskId != null && props.sourceFileId != null
    ? getDataProcessSourceRawUrl(props.taskId, props.sourceFileId)
    : ''
))
const visibleRowRange = computed(() => {
  const sheet = xlsxPreview.value?.active_sheet
  if (!sheet || !sheet.rows.length) return '当前工作表没有可预览记录'
  const start = sheet.offset + 1
  const end = sheet.offset + sheet.rows.length
  return `第 ${start}–${end} 条记录`
})

function overlapsSelection(start: number, end: number) {
  const item = props.selectedItem
  if (!item || item.sourceStart == null || item.sourceEnd == null) return false
  return end > item.sourceStart && start < item.sourceEnd
}

function tableRowHighlighted(row: DataProcessDocxTableRow) {
  return overlapsSelection(row.source_start, row.source_end)
}

function stableValue(value: unknown): unknown {
  if (Array.isArray(value)) return value.map(stableValue)
  if (value && typeof value === 'object') {
    return Object.fromEntries(
      Object.entries(value as Record<string, unknown>)
        .sort(([left], [right]) => left.localeCompare(right))
        .map(([key, item]) => [key, stableValue(item)]),
    )
  }
  return value
}

function recordKey(value: unknown) {
  try {
    return JSON.stringify(stableValue(value))
  } catch {
    return ''
  }
}

const selectedRecordKey = computed(() => {
  const content = props.selectedItem?.originalContent
  if (!content) return ''
  try {
    return recordKey(JSON.parse(content))
  } catch {
    return ''
  }
})

function xlsxRowHighlighted(row: DataProcessXlsxPreviewRow) {
  return Boolean(selectedRecordKey.value && recordKey(row.record) === selectedRecordKey.value)
}

function displayCell(value: unknown) {
  if (value == null || value === '') return '—'
  if (typeof value === 'object') return JSON.stringify(value)
  return String(value)
}

async function locateSelectedItem() {
  await nextTick()
  const selected = scrollRef.value?.querySelector<HTMLElement>(
    '.docx-block.is-highlighted, .docx-table-row.is-highlighted, .xlsx-row.is-highlighted',
  )
  selected?.scrollIntoView({
    block: 'center',
    inline: 'nearest',
    behavior: prefersReducedMotion ? 'auto' : 'smooth',
  })
}

async function loadPreview(options: { reset?: boolean } = {}) {
  const sequence = ++loadSequence
  if (options.reset) {
    activeSheetIndex.value = 0
    pageOffset.value = 0
    preview.value = null
  }
  errorMessage.value = ''
  if (props.taskId == null || props.sourceFileId == null) {
    errorMessage.value = '缺少原文件标识，无法加载预览'
    return
  }

  loading.value = true
  try {
    const result = await getDataProcessOfficePreview(
      props.taskId,
      props.sourceFileId,
      isDocx.value
        ? {}
        : {
            sheet_index: activeSheetIndex.value,
            offset: pageOffset.value,
            limit: XLSX_PAGE_SIZE,
          },
    )
    if (sequence !== loadSequence) return
    preview.value = result
    if (result.format === 'xlsx') activeSheetIndex.value = result.active_sheet.index
    await locateSelectedItem()
  } catch (error) {
    if (sequence !== loadSequence) return
    errorMessage.value = error instanceof Error ? error.message : 'Office 原文件预览加载失败'
  } finally {
    if (sequence === loadSequence) loading.value = false
  }
}

function changeSheet(value: string | number) {
  activeSheetIndex.value = Number(value)
  pageOffset.value = 0
  void loadPreview()
}

function previousPage() {
  pageOffset.value = Math.max(0, pageOffset.value - XLSX_PAGE_SIZE)
  void loadPreview()
}

function nextPage() {
  if (!xlsxPreview.value?.active_sheet.has_more) return
  pageOffset.value += XLSX_PAGE_SIZE
  void loadPreview()
}

watch(
  () => [props.taskId, props.sourceFileId, normalizedFormat.value],
  () => void loadPreview({ reset: true }),
  { immediate: true },
)

watch(
  () => props.selectedItem?.id,
  () => void locateSelectedItem(),
)
</script>

<template>
  <div
    class="office-source-viewer"
    :aria-label="`${normalizedFormat.toUpperCase()} 预览：${fileName}`"
  >
    <div class="office-toolbar">
      <template v-if="xlsxPreview">
        <div class="sheet-selector">
          <span>工作表</span>
          <el-select
            :model-value="activeSheetIndex"
            size="small"
            aria-label="选择 Excel 工作表"
            @update:model-value="changeSheet"
          >
            <el-option
              v-for="sheet in xlsxPreview.sheets"
              :key="sheet.index"
              :label="sheet.name"
              :value="sheet.index"
            />
          </el-select>
        </div>
        <span>{{ visibleRowRange }}</span>
      </template>
      <template v-else>
        <span>Word 网页版式预览</span>
        <span v-if="docxPreview?.truncated">文档较长，仅展示前 2,000 个内容块</span>
      </template>
      <span v-if="loading && preview" class="toolbar-loading" role="status">
        <i class="fa fa-spinner fa-spin" /> 正在更新预览
      </span>
      <a
        v-if="sourceUrl"
        class="source-file-link"
        :href="sourceUrl"
        target="_blank"
        rel="noopener noreferrer"
      >
        <i class="fa fa-external-link" /> 打开原文件
      </a>
    </div>

    <div v-if="loading && !preview" class="office-state" role="status">
      <i class="fa fa-spinner fa-spin" />
      <strong>正在加载原文件预览</strong>
      <span>{{ isDocx ? '正在还原 Word 文档结构' : '正在读取 Excel 工作表' }}</span>
    </div>

    <div v-else-if="errorMessage" class="office-state is-error" role="alert">
      <i class="fa fa-exclamation-circle" />
      <strong>{{ isDocx ? 'Word 预览失败' : 'Excel 预览失败' }}</strong>
      <span>{{ errorMessage }}</span>
      <div>
        <el-button type="primary" size="small" @click="loadPreview()">重试</el-button>
        <el-button v-if="sourceUrl" tag="a" :href="sourceUrl" target="_blank" size="small">
          打开原文件
        </el-button>
      </div>
    </div>

    <div v-else-if="docxPreview" ref="scrollRef" class="docx-scroll">
      <article class="docx-page">
        <template v-for="(block, index) in docxPreview.blocks" :key="index">
          <component
            :is="block.heading_level ? `h${block.heading_level}` : 'p'"
            v-if="block.type === 'paragraph'"
            class="docx-block"
            :class="{
              'is-highlighted': overlapsSelection(block.source_start, block.source_end),
              'is-list': block.is_list,
            }"
            :style="{ textAlign: block.alignment }"
            :data-source-start="block.source_start"
          >
            {{ block.text }}
          </component>
          <div v-else class="docx-table-wrap">
            <table class="docx-table">
              <tbody>
                <tr
                  v-for="(row, rowIndex) in block.rows"
                  :key="rowIndex"
                  class="docx-table-row"
                  :class="{ 'is-highlighted': tableRowHighlighted(row) }"
                  :data-source-start="row.source_start"
                >
                  <td v-for="(cell, cellIndex) in row.cells" :key="cellIndex">{{ cell || ' ' }}</td>
                </tr>
              </tbody>
            </table>
          </div>
        </template>
        <div v-if="!docxPreview.blocks.length" class="office-empty">文档中没有可预览的正文</div>
      </article>
    </div>

    <template v-else-if="xlsxPreview">
      <div ref="scrollRef" class="xlsx-scroll">
        <table v-if="xlsxPreview.active_sheet.columns.length" class="xlsx-grid">
          <thead>
            <tr>
              <th class="row-number-cell">#</th>
              <th
                v-for="column in xlsxPreview.active_sheet.columns"
                :key="column"
                :title="column"
              >
                {{ column }}
              </th>
            </tr>
          </thead>
          <tbody>
            <tr
              v-for="row in xlsxPreview.active_sheet.rows"
              :key="row.row_number"
              class="xlsx-row"
              :class="{ 'is-highlighted': xlsxRowHighlighted(row) }"
            >
              <th class="row-number-cell">{{ row.row_number }}</th>
              <td
                v-for="(value, cellIndex) in row.values"
                :key="cellIndex"
                :title="displayCell(value)"
              >
                {{ displayCell(value) }}
              </td>
            </tr>
          </tbody>
        </table>
        <div v-else class="office-empty">当前工作表没有可预览记录</div>
      </div>
      <div class="xlsx-pagination">
        <el-button
          size="small"
          :disabled="pageOffset === 0 || loading"
          aria-label="上一页工作表记录"
          @click="previousPage"
        >
          <i class="fa fa-angle-left" /> 上一页
        </el-button>
        <span>{{ visibleRowRange }}</span>
        <el-button
          size="small"
          :disabled="!xlsxPreview.active_sheet.has_more || loading"
          aria-label="下一页工作表记录"
          @click="nextPage"
        >
          下一页 <i class="fa fa-angle-right" />
        </el-button>
      </div>
    </template>
  </div>
</template>

<style scoped lang="scss">
.office-source-viewer {
  display: flex;
  min-width: 0;
  min-height: 0;
  flex: 1;
  flex-direction: column;
  background: #f4f6f9;
}

.office-toolbar {
  display: flex;
  min-height: 42px;
  flex: none;
  align-items: center;
  gap: 12px;
  padding: 6px 12px;
  color: #7d8798;
  background: #fff;
  border-bottom: 1px solid #e5e8ee;
  font-size: 11px;

}

.sheet-selector {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: 8px;

  > span {
    flex: none;
  }

  :deep(.el-select) {
    width: min(220px, 28vw);
  }
}

.source-file-link {
  flex: none;
  margin-left: auto;
  color: #5147df;
  text-decoration: none;

  &:hover,
  &:focus-visible {
    text-decoration: underline;
  }
}

.toolbar-loading {
  color: #5b50f2;
  white-space: nowrap;
}

.office-state {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 9px;
  padding: 28px;
  color: #667085;
  text-align: center;

  > i {
    color: #5b50f2;
    font-size: 28px;
  }

  > strong {
    color: #344054;
    font-size: 14px;
  }

  > span {
    max-width: 420px;
    font-size: 12px;
    line-height: 1.6;
  }

  &.is-error > i {
    color: #d92d20;
  }
}

.docx-scroll {
  flex: 1;
  min-height: 0;
  padding: 22px;
  overflow: auto;
  scroll-behavior: smooth;
}

.docx-page {
  width: min(760px, 100%);
  min-height: calc(100% - 2px);
  padding: 54px clamp(30px, 7%, 68px);
  margin: 0 auto;
  color: #262b34;
  background: #fff;
  border: 1px solid #dfe3e9;
  box-shadow: 0 2px 10px rgb(15 23 42 / 8%);
  font-family: "Songti SC", SimSun, serif;
  font-size: 13px;
  line-height: 1.8;
}

.docx-block {
  padding: 2px 6px;
  margin: 0 0 10px;
  border-radius: 3px;
  white-space: pre-wrap;
  word-break: break-word;
  transition: background-color 0.18s ease, box-shadow 0.18s ease;

  &.is-list {
    padding-left: 22px;
  }

  &.is-highlighted {
    background: #fff0b8;
    box-shadow: inset 3px 0 #f0b429;
  }
}

h1.docx-block { font-size: 22px; }
h2.docx-block { font-size: 19px; }
h3.docx-block { font-size: 17px; }
h4.docx-block,
h5.docx-block,
h6.docx-block { font-size: 15px; }

.docx-table-wrap {
  max-width: 100%;
  margin: 12px 0 18px;
  overflow-x: auto;
}

.docx-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;

  td {
    padding: 7px 9px;
    border: 1px solid #9da5b2;
    vertical-align: top;
    white-space: pre-wrap;
    word-break: break-word;
  }
}

.docx-table-row.is-highlighted td {
  background: #fff0b8;
}

.xlsx-scroll {
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow: auto;
  background: #fff;
  scroll-behavior: smooth;
}

.xlsx-grid {
  min-width: 100%;
  color: #344054;
  border-spacing: 0;
  border-collapse: separate;
  table-layout: auto;
  font-size: 11px;

  th,
  td {
    min-width: 120px;
    max-width: 320px;
    height: 36px;
    padding: 7px 10px;
    overflow: hidden;
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
    border-right: 1px solid #e4e7ec;
    border-bottom: 1px solid #e4e7ec;
  }

  thead th {
    position: sticky;
    z-index: 2;
    top: 0;
    color: #475467;
    background: #f2f4f7;
    font-weight: 650;
  }

  tbody tr:hover td,
  tbody tr:hover th {
    background: #f9fafb;
  }
}

.row-number-cell {
  position: sticky;
  z-index: 1;
  left: 0;
  min-width: 54px !important;
  width: 54px;
  color: #98a2b3;
  text-align: center !important;
  background: #f8fafc;
  font-variant-numeric: tabular-nums;
}

thead .row-number-cell {
  z-index: 3;
}

.xlsx-row.is-highlighted {
  td,
  th {
    background: #fff0b8;
    box-shadow: inset 0 2px #f0b429, inset 0 -2px #f0b429;
  }
}

.xlsx-pagination {
  display: flex;
  min-height: 46px;
  flex: none;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  padding: 7px 12px;
  color: #7d8798;
  background: #fff;
  border-top: 1px solid #e5e8ee;
  font-size: 11px;
}

.office-empty {
  display: flex;
  min-height: 180px;
  align-items: center;
  justify-content: center;
  color: #98a2b3;
  font-size: 12px;
}

@media (max-width: 900px) {
  .office-source-viewer {
    height: 360px;
    flex: none;
  }

  .office-toolbar {
    flex-wrap: wrap;
  }

  .docx-scroll {
    padding: 12px;
  }

  .docx-page {
    padding: 34px 24px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .docx-scroll,
  .xlsx-scroll {
    scroll-behavior: auto;
  }

  .docx-block {
    transition: none;
  }
}
</style>
