<template>
  <section class="documents-page">
    <article class="documents-list panel">
      <nav class="status-tabs document-scope-tabs" aria-label="单据工作视角">
        <button
          v-for="tab in scopeTabItems"
          :key="tab.value"
          type="button"
          :class="{ active: activeScopeTab === tab.value }"
          @click="activeScopeTab = tab.value"
        >
          <span class="scope-tab-label">
            {{ tab.label }}
            <span v-if="tab.badgeCount > 0" class="scope-tab-badge" aria-label="新增单据数">
              {{ tab.badgeCount > 99 ? '99+' : tab.badgeCount }}
            </span>
          </span>
        </button>
      </nav>

      <div class="document-toolbar">
        <div class="filter-set">
          <div class="list-search">
            <i class="mdi mdi-magnify"></i>
            <input v-model="listKeyword" type="search" :placeholder="activeFilterConfig.searchPlaceholder" />
          </div>

          <div class="document-status-filter" :aria-label="activeFilterConfig.statusTitle">
            <div class="document-filter status-dropdown-filter" :class="{ open: openFilterKey === 'status' }">
              <button
                class="filter-btn status-filter-trigger"
                type="button"
                :aria-expanded="openFilterKey === 'status'"
                @click="toggleFilter('status')"
              >
                <i class="mdi mdi-filter-variant"></i>
                <span>{{ statusFilterLabel }}</span>
                <i class="mdi mdi-chevron-down"></i>
              </button>
              <div
                v-if="openFilterKey === 'status'"
                class="document-filter-menu status-filter-menu"
                role="listbox"
                aria-label="风险等级"
              >
                <button
                  v-for="option in statusFilterOptions"
                  :key="option.value"
                  type="button"
                  role="option"
                  :aria-selected="activeStatusTab === option.value"
                  :class="{ active: activeStatusTab === option.value }"
                  @click="selectStatusTab(option.value)"
                >
                  {{ option.label }}
                </button>
              </div>
            </div>
          </div>

          <div v-if="showDocumentTypeFilter" class="document-filter">
            <button class="filter-btn" type="button" @click="toggleFilter('documentType')">
              <span>{{ documentTypeFilterLabel }}</span>
              <i class="mdi mdi-chevron-down"></i>
            </button>
            <div v-if="openFilterKey === 'documentType'" class="document-filter-menu">
              <button
                v-for="option in documentTypeOptions"
                :key="option.value"
                type="button"
                :class="{ active: activeDocumentType === option.value }"
                @click="selectDocumentType(option.value)"
              >
                {{ option.label }}
              </button>
            </div>
          </div>

          <div class="document-filter">
            <button class="filter-btn" type="button" @click="toggleFilter('scene')">
              <span>{{ sceneFilterLabel }}</span>
              <i class="mdi mdi-chevron-down"></i>
            </button>
            <div v-if="openFilterKey === 'scene'" class="document-filter-menu">
              <button
                v-for="option in sceneFilterOptions"
                :key="option.value"
                type="button"
                :class="{ active: activeScene === option.value }"
                @click="selectScene(option.value)"
              >
                {{ option.label }}
              </button>
            </div>
          </div>

          <div class="date-range-filter" :class="{ open: datePopover }">
            <button class="filter-btn date-range-trigger" type="button" @click="toggleDatePopover">
              <span class="date-range-label">{{ dateRangeLabel }}</span>
              <i class="mdi mdi-calendar"></i>
            </button>
            <div v-if="datePopover" class="date-range-popover" role="dialog" aria-label="选择时间段">
              <header>
                <strong>选择时间段</strong>
                <button type="button" aria-label="关闭" @click="datePopover = false">
                  <i class="mdi mdi-close"></i>
                </button>
              </header>
              <div class="date-range-fields">
                <label>
                  <span>开始日期</span>
                  <input v-model="rangeStart" type="date" />
                </label>
                <label>
                  <span>结束日期</span>
                  <input v-model="rangeEnd" type="date" />
                </label>
              </div>
              <footer>
                <button class="ghost-btn" type="button" @click="clearDateRange">清空</button>
                <button class="apply-btn" type="button" :disabled="!rangeStart || !rangeEnd" @click="applyDateRange">应用</button>
              </footer>
            </div>
          </div>
        </div>

        <div v-if="showToolbarActions" class="document-actions">
          <button
            v-if="totalNewDocumentCount > 0"
            class="mark-read-btn"
            type="button"
            @click="markAllDocumentsRead"
          >
            <i class="mdi mdi-check-all"></i>
            <span>一键已读</span>
          </button>
          <button v-if="activeScopeTab === DOCUMENT_SCOPE_APPLICATION" class="create-request-btn" type="button" @click="emit('create-application')">
            <i class="mdi mdi-file-plus-outline"></i>
            <span>发起申请</span>
          </button>
          <button v-if="activeScopeTab === DOCUMENT_SCOPE_REIMBURSEMENT" class="create-request-btn" type="button" @click="emit('create-request')">
            <i class="mdi mdi-plus-circle-outline"></i>
            <span>发起报销</span>
          </button>
        </div>
      </div>

      <div class="table-wrap" :class="{ 'is-empty': showEmpty }">
        <div v-if="showLoading" class="table-state">
          <TableLoadingState
            title="单据数据同步中"
            message="正在汇总当前报销、审批待办与归档单据"
            icon="mdi mdi-file-document-multiple-outline"
            floating
          />
        </div>

        <div v-else-if="showError" class="table-state error">
          <i class="mdi mdi-alert-circle-outline"></i>
          <strong>单据中心加载失败</strong>
          <p>{{ errorMessage }}</p>
          <button class="retry-btn" type="button" @click="reloadAll">重新加载</button>
        </div>

        <TableEmptyState
          v-else-if="showEmpty"
          :eyebrow="emptyState.eyebrow"
          :title="emptyState.title"
          :description="emptyState.desc"
          :icon="emptyState.icon"
          :action-label="emptyState.actionLabel"
          :action-icon="emptyState.actionIcon"
          :tone="emptyState.tone"
          :art-label="emptyState.artLabel"
          :tips="emptyState.tips"
          @action="handleEmptyAction"
        />

        <table v-else>
          <colgroup>
            <col class="col-id">
            <col class="col-created">
            <col v-if="showStayTimeColumn" class="col-stay">
            <col class="col-doc-type">
            <col class="col-scene">
            <col class="col-initiator">
            <col class="col-title">
            <col class="col-amount">
            <col class="col-node">
            <col class="col-risk">
            <col class="col-updated">
          </colgroup>
          <thead>
            <tr>
              <th>单号</th>
              <th>创建时间</th>
              <th v-if="showStayTimeColumn">停留时间</th>
              <th>单据类型</th>
              <th>费用场景</th>
              <th>发起人</th>
              <th>事项</th>
              <th>金额</th>
              <th>当前环节</th>
              <th>风险等级</th>
              <th>更新时间</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="row in visibleRows" :key="row.documentKey" @click="openDocument(row)">
              <td data-label="单号">
                <span v-if="row.isNewDocument" class="new-document-badge">NEW</span>
                <strong class="doc-id">{{ row.documentNo }}</strong>
              </td>
              <td data-label="创建时间">{{ row.createdAtDisplay }}</td>
              <td v-if="showStayTimeColumn" data-label="停留时间">{{ row.stayTimeDisplay }}</td>
              <td data-label="单据类型"><span class="doc-kind-tag" :class="row.documentTypeCode">{{ row.documentTypeLabel }}</span></td>
              <td data-label="费用场景"><span class="type-tag" :class="row.typeTone">{{ row.typeLabel }}</span></td>
              <td data-label="发起人">{{ row.initiatorName }}</td>
              <td data-label="事项">{{ row.reason }}</td>
              <td data-label="金额">{{ row.amountDisplay }}</td>
              <td data-label="当前环节">{{ row.node }}</td>
              <td data-label="风险等级">
                <span class="risk-level-tags">
                  <span
                    v-for="tag in row.riskTags"
                    :key="tag.label"
                    class="risk-level-tag"
                    :class="tag.tone"
                  >
                    {{ tag.label }}
                  </span>
                </span>
              </td>
              <td data-label="更新时间">{{ row.updatedAtDisplay }}</td>
            </tr>
          </tbody>
        </table>
      </div>

      <EnterprisePagination
        v-if="showTable"
        :current-page="currentPage"
        :page-size="pageSize"
        :page-size-options="pageSizeOptions"
        :summary="pageSummary"
        :total-pages="totalPages"
        @page-size-change="changePageSize"
        @update:current-page="currentPage = $event"
      />
    </article>
  </section>
</template>

<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import EnterprisePagination from '../components/shared/EnterprisePagination.vue'
import TableEmptyState from '../components/shared/TableEmptyState.vue'
import TableLoadingState from '../components/shared/TableLoadingState.vue'
import { useMinimumVisibleState } from '../composables/useMinimumVisibleState.js'
import { mapExpenseClaimToRequest } from '../composables/useRequests.js'
import {
  extractExpenseClaimItems,
  fetchAllApprovalExpenseClaims,
  fetchAllArchivedExpenseClaims
} from '../services/reimbursements.js'
import { countClaimRisks, resolveArchiveRiskTone } from '../utils/archiveCenterListFilters.js'
import { fetchNotificationStates, patchNotificationStates } from '../services/notificationStates.js'
import {
  buildDocumentViewedStatePatch,
  buildDocumentsViewedStatePatches,
  countNewDocuments,
  isNewDocument,
  markDocumentViewed,
  markDocumentsViewed,
  mergeNotificationStatesIntoViewedDocumentKeys,
  readDocumentScope,
  readViewedDocumentKeys,
  writeDocumentScope
} from '../utils/documentCenterNewState.js'
import { sortDocumentRowsByLatestTime } from '../utils/documentCenterSort.js'
import { extractDateText, formatDocumentListTime, resolveDocumentSortTime, resolveDocumentStayTimeDisplay } from '../utils/documentCenterTime.js'
import { excludeArchivedDocumentRows, filterApplicationScopeNewRows, isArchivedDocumentRow, prepareApplicationScopeRows } from '../utils/documentCenterRows.js'
import { normalizeRequestForUi } from '../utils/requestViewModel.js'
const DOCUMENT_TYPE_ALL = 'all'
const DOCUMENT_TYPE_APPLICATION = 'application'
const DOCUMENT_TYPE_REIMBURSEMENT = 'reimbursement'
const SCENE_ALL = 'all'
const DOCUMENT_SCOPE_ALL = '全部'
const DOCUMENT_SCOPE_APPLICATION = '申请单'
const DOCUMENT_SCOPE_REIMBURSEMENT = '报销单'
const DOCUMENT_SCOPE_REVIEW = '审核单'
const DOCUMENT_SCOPE_ARCHIVE = '归档'
const scopeTabs = [DOCUMENT_SCOPE_ALL, DOCUMENT_SCOPE_APPLICATION, DOCUMENT_SCOPE_REIMBURSEMENT, DOCUMENT_SCOPE_REVIEW, DOCUMENT_SCOPE_ARCHIVE]
const DOCUMENT_LOADING_MIN_VISIBLE_MS = 720
const DOCUMENT_CENTER_QUERY_KEYS = new Set([
  'dc_page',
  'dc_page_size',
  'dc_scope',
  'dc_status',
  'dc_doc_type',
  'dc_scene',
  'dc_q',
  'dc_start',
  'dc_end'
])
const riskLevelTabs = ['全部', '高风险', '中风险', '低风险', '无风险']
const RISK_TONE_META = {
  high: { label: '高风险', tone: 'high' },
  medium: { label: '中风险', tone: 'medium' },
  low: { label: '低风险', tone: 'low' },
  none: { label: '无风险', tone: 'none' }
}
const FILTER_CONFIG_BY_SCOPE = {
  [DOCUMENT_SCOPE_ALL]: {
    searchPlaceholder: '搜索单号、事项、费用场景...',
    sceneFallbackLabel: '单据场景',
    dateLabel: '单据时间',
    statusTitle: '风险等级',
    statusTabs: riskLevelTabs,
    showDocumentType: true
  },
  [DOCUMENT_SCOPE_APPLICATION]: {
    searchPlaceholder: '搜索申请单号、申请事项、申请场景...',
    sceneFallbackLabel: '申请场景',
    dateLabel: '申请时间',
    statusTitle: '风险等级',
    statusTabs: riskLevelTabs,
    showDocumentType: false
  },
  [DOCUMENT_SCOPE_REIMBURSEMENT]: {
    searchPlaceholder: '搜索报销单号、报销事由、费用场景...',
    sceneFallbackLabel: '费用场景',
    dateLabel: '报销时间',
    statusTitle: '风险等级',
    statusTabs: riskLevelTabs,
    showDocumentType: false
  },
  [DOCUMENT_SCOPE_REVIEW]: {
    searchPlaceholder: '搜索审核单号、事项、当前环节...',
    sceneFallbackLabel: '审核场景',
    dateLabel: '审核时间',
    statusTitle: '风险等级',
    statusTabs: riskLevelTabs,
    showDocumentType: false
  },
  [DOCUMENT_SCOPE_ARCHIVE]: {
    searchPlaceholder: '搜索归档单号、事项、费用场景...',
    sceneFallbackLabel: '归档场景',
    dateLabel: '归档时间',
    statusTitle: '风险等级',
    statusTabs: riskLevelTabs,
    showDocumentType: false
  }
}
const pageSizeOptions = [10, 20, 50].map((size) => ({ label: `${size} 条/页`, value: size }))
const pageSizeValues = pageSizeOptions.map((item) => item.value)
const documentTypeOptions = [
  { value: DOCUMENT_TYPE_ALL, label: '单据类型' },
  { value: DOCUMENT_TYPE_APPLICATION, label: '申请单' },
  { value: DOCUMENT_TYPE_REIMBURSEMENT, label: '报销单' }
]
const route = useRoute()
const router = useRouter()
const props = defineProps({
  filteredRequests: { type: Array, required: true },
  hasData: { type: Boolean, default: false },
  loading: { type: Boolean, default: false },
  error: { type: String, default: '' },
  refreshToken: { type: Number, default: 0 }
})
const emit = defineEmits([
  'open-document',
  'create-request',
  'create-application',
  'reload',
  'summary-change'
])

function readDocumentCenterQueryText(key) {
  const value = route.query?.[key]
  return String(Array.isArray(value) ? value[0] || '' : value || '').trim()
}

function readDocumentCenterQueryNumber(key, fallback) {
  const parsed = Number(readDocumentCenterQueryText(key))
  return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback
}

function resolveInitialScopeTab() {
  const queryScope = readDocumentCenterQueryText('dc_scope')
  if (scopeTabs.includes(queryScope)) {
    return queryScope
  }

  return readDocumentScope(DOCUMENT_SCOPE_ALL, scopeTabs)
}

function resolveInitialStatusTab(scope) {
  const queryStatus = readDocumentCenterQueryText('dc_status') || '全部'
  const config = FILTER_CONFIG_BY_SCOPE[scope] || FILTER_CONFIG_BY_SCOPE[DOCUMENT_SCOPE_ALL]
  return config.statusTabs.includes(queryStatus) ? queryStatus : '全部'
}

function resolveInitialDocumentType() {
  const queryType = readDocumentCenterQueryText('dc_doc_type')
  return documentTypeOptions.some((item) => item.value === queryType)
    ? queryType
    : DOCUMENT_TYPE_ALL
}

function resolveInitialPageSize() {
  const queryPageSize = readDocumentCenterQueryNumber('dc_page_size', 20)
  return pageSizeValues.includes(queryPageSize) ? queryPageSize : 20
}

function buildDocumentCenterRouteQuery() {
  const nextQuery = {}
  Object.entries(route.query || {}).forEach(([key, value]) => {
    if (!DOCUMENT_CENTER_QUERY_KEYS.has(key)) {
      nextQuery[key] = value
    }
  })

  if (currentPage.value > 1) nextQuery.dc_page = String(currentPage.value)
  if (pageSize.value !== 20) nextQuery.dc_page_size = String(pageSize.value)
  if (activeScopeTab.value !== DOCUMENT_SCOPE_ALL) nextQuery.dc_scope = activeScopeTab.value
  if (activeStatusTab.value !== '全部') nextQuery.dc_status = activeStatusTab.value
  if (showDocumentTypeFilter.value && activeDocumentType.value !== DOCUMENT_TYPE_ALL) {
    nextQuery.dc_doc_type = activeDocumentType.value
  }
  if (activeScene.value !== SCENE_ALL) nextQuery.dc_scene = activeScene.value
  if (listKeyword.value.trim()) nextQuery.dc_q = listKeyword.value.trim()
  if (appliedStart.value) nextQuery.dc_start = appliedStart.value
  if (appliedEnd.value) nextQuery.dc_end = appliedEnd.value

  return nextQuery
}

function routeQueryEquals(left, right) {
  const leftEntries = Object.entries(left || {}).map(([key, value]) => [key, Array.isArray(value) ? value.join(',') : String(value ?? '')])
  const rightEntries = Object.entries(right || {}).map(([key, value]) => [key, Array.isArray(value) ? value.join(',') : String(value ?? '')])
  if (leftEntries.length !== rightEntries.length) return false
  const rightMap = new Map(rightEntries)
  return leftEntries.every(([key, value]) => rightMap.get(key) === value)
}

const initialScopeTab = resolveInitialScopeTab()
const initialAppliedStart = readDocumentCenterQueryText('dc_start')
const initialAppliedEnd = readDocumentCenterQueryText('dc_end')
const activeScopeTab = ref(initialScopeTab)
const activeStatusTab = ref(resolveInitialStatusTab(initialScopeTab))
const activeDocumentType = ref(resolveInitialDocumentType())
const activeScene = ref(readDocumentCenterQueryText('dc_scene') || SCENE_ALL)
const openFilterKey = ref('')
const listKeyword = ref(readDocumentCenterQueryText('dc_q'))
const datePopover = ref(false)
const rangeStart = ref(initialAppliedStart)
const rangeEnd = ref(initialAppliedEnd)
const appliedStart = ref(initialAppliedStart)
const appliedEnd = ref(initialAppliedEnd)
const currentPage = ref(readDocumentCenterQueryNumber('dc_page', 1))
const pageSize = ref(resolveInitialPageSize())
const archiveRows = ref([])
const approvalRows = ref([])
const supportingLoading = ref(false)
const supportingError = ref('')
const viewedDocumentKeys = ref(readViewedDocumentKeys())
const activeFilterConfig = computed(() =>
  FILTER_CONFIG_BY_SCOPE[activeScopeTab.value] || FILTER_CONFIG_BY_SCOPE[DOCUMENT_SCOPE_APPLICATION]
)
const showDocumentTypeFilter = computed(() => Boolean(activeFilterConfig.value.showDocumentType))
const documentTypeFilterLabel = computed(() =>
  documentTypeOptions.find((item) => item.value === activeDocumentType.value)?.label || '单据类型'
)
const statusFilterOptions = computed(() =>
  activeFilterConfig.value.statusTabs.map((tab) => ({
    value: tab,
    label: tab === '全部' ? '全部风险' : tab
  }))
)

const dateRangeLabel = computed(() => {
  if (appliedStart.value && appliedEnd.value) {
    return `${appliedStart.value} ~ ${appliedEnd.value}`
  }

  return activeFilterConfig.value.dateLabel
})

const ownedRows = computed(() =>
  excludeArchivedDocumentRows(
    props.filteredRequests
      .map((item) => buildDocumentRow(item, { source: 'owned' }))
      .filter(Boolean)
  )
)

const nonArchivedRows = computed(() => mergeDocumentRows([...ownedRows.value, ...approvalRows.value]))
const applicationScopeRows = computed(() => prepareApplicationScopeRows(ownedRows.value))

const scopeNewCountMap = computed(() => ({
  [DOCUMENT_SCOPE_ALL]: countNewDocuments(nonArchivedRows.value, viewedDocumentKeys.value),
  [DOCUMENT_SCOPE_APPLICATION]: countNewDocuments(filterApplicationScopeNewRows(applicationScopeRows.value), viewedDocumentKeys.value),
  [DOCUMENT_SCOPE_REIMBURSEMENT]: countNewDocuments(ownedRows.value.filter((row) => row.documentTypeCode === DOCUMENT_TYPE_REIMBURSEMENT), viewedDocumentKeys.value),
  [DOCUMENT_SCOPE_REVIEW]: countNewDocuments(approvalRows.value, viewedDocumentKeys.value),
  [DOCUMENT_SCOPE_ARCHIVE]: countNewDocuments(archiveRows.value, viewedDocumentKeys.value)
}))

const scopeTabItems = computed(() =>
  scopeTabs.map((tab) => ({
    value: tab,
    label: tab,
    badgeCount: scopeNewCountMap.value[tab] || 0
  }))
)
const allReadableDocumentRows = computed(() => [
  ...nonArchivedRows.value,
  ...filterApplicationScopeNewRows(applicationScopeRows.value),
  ...ownedRows.value.filter((row) => row.documentTypeCode === DOCUMENT_TYPE_REIMBURSEMENT),
  ...approvalRows.value
])
const totalNewDocumentCount = computed(() => countNewDocuments(allReadableDocumentRows.value, viewedDocumentKeys.value))
const showCreateDocumentActions = computed(() =>
  [DOCUMENT_SCOPE_APPLICATION, DOCUMENT_SCOPE_REIMBURSEMENT].includes(activeScopeTab.value)
)
const showToolbarActions = computed(() => showCreateDocumentActions.value || totalNewDocumentCount.value > 0)

const activeScopeRows = computed(() => {
  if (activeScopeTab.value === DOCUMENT_SCOPE_ALL) return nonArchivedRows.value

  if (activeScopeTab.value === DOCUMENT_SCOPE_APPLICATION) {
    return applicationScopeRows.value
  }

  if (activeScopeTab.value === DOCUMENT_SCOPE_REIMBURSEMENT) {
    return ownedRows.value.filter((row) => row.documentTypeCode === DOCUMENT_TYPE_REIMBURSEMENT)
  }

  if (activeScopeTab.value === DOCUMENT_SCOPE_REVIEW) {
    return approvalRows.value
  }

  if (activeScopeTab.value === DOCUMENT_SCOPE_ARCHIVE) {
    return archiveRows.value
  }

  return nonArchivedRows.value
})

const sceneFilterOptions = computed(() => {
  const sceneMap = new Map([[SCENE_ALL, activeFilterConfig.value.sceneFallbackLabel]])
  activeScopeRows.value.forEach((row) => {
    if (row.typeCode && row.typeLabel) {
      sceneMap.set(row.typeCode, row.typeLabel)
    }
  })

  return Array.from(sceneMap, ([value, label]) => ({ value, label }))
})

const sceneFilterLabel = computed(() =>
  sceneFilterOptions.value.find((item) => item.value === activeScene.value)?.label || activeFilterConfig.value.sceneFallbackLabel
)

const statusFilterLabel = computed(() =>
  statusFilterOptions.value.find((item) => item.value === activeStatusTab.value)?.label || '全部风险'
)

const filteredRows = computed(() => {
  const keyword = listKeyword.value.trim().toLowerCase()

  return sortDocumentRowsByLatestTime(activeScopeRows.value.filter((row) => {
    const matchesKeyword = !keyword || [
      row.documentNo,
      row.documentTypeLabel,
      row.typeLabel,
      row.initiatorName,
      row.reason,
      row.node,
      row.statusLabel,
      row.riskLabel
    ].filter(Boolean).join('').toLowerCase().includes(keyword)

    const matchesDocumentType =
      !showDocumentTypeFilter.value
      || activeDocumentType.value === DOCUMENT_TYPE_ALL
      || row.documentTypeCode === activeDocumentType.value

    const matchesScene = activeScene.value === SCENE_ALL || row.typeCode === activeScene.value
    const matchesRiskLevel = matchesRiskLevelTab(row, activeStatusTab.value)
    const matchesDateRange = matchesAppliedDateRange(row)

    return matchesKeyword && matchesDocumentType && matchesScene && matchesRiskLevel && matchesDateRange
  }))
})

const totalPages = computed(() => Math.max(1, Math.ceil(filteredRows.value.length / pageSize.value)))
const pageSummary = computed(() => `共 ${filteredRows.value.length} 条，目前第 ${currentPage.value} / ${totalPages.value} 页`)

function resolveLiveDocumentRow(row) {
  return {
    ...row,
    isNewDocument: isNewDocument(row, viewedDocumentKeys.value)
  }
}

const visibleRows = computed(() => {
  const start = (currentPage.value - 1) * pageSize.value
  return filteredRows.value.slice(start, start + pageSize.value).map(resolveLiveDocumentRow)
})

const documentLoadingSource = computed(() => (props.loading || supportingLoading.value) && !visibleRows.value.length)
const visibleDocumentLoading = useMinimumVisibleState(documentLoadingSource, {
  minVisibleMs: DOCUMENT_LOADING_MIN_VISIBLE_MS
})
const showLoading = computed(() => visibleDocumentLoading.value)
const showError = computed(() => Boolean(props.error) && !visibleRows.value.length)
const errorMessage = computed(() => props.error || supportingError.value || '单据中心加载失败。')
const showEmpty = computed(() => !showLoading.value && !showError.value && visibleRows.value.length === 0)
const showTable = computed(() => !showLoading.value && !showError.value && visibleRows.value.length > 0)
const showStayTimeColumn = computed(() =>
  [DOCUMENT_SCOPE_APPLICATION, DOCUMENT_SCOPE_REVIEW].includes(activeScopeTab.value)
)

const documentSummary = computed(() => {
  const rows = nonArchivedRows.value
  return {
    total: rows.length,
    toSubmit: rows.filter((row) => ['draft', 'pending_submit'].includes(row.statusGroup)).length,
    toProcess: approvalRows.value.length,
    archived: archiveRows.value.length
  }
})

const emptyState = computed(() => {
  const filtered = hasActiveFilters()
  if (
    activeScopeTab.value === DOCUMENT_SCOPE_APPLICATION
    || activeDocumentType.value === DOCUMENT_TYPE_APPLICATION
  ) {
    return {
      eyebrow: '申请单',
      title: '当前还没有申请单数据',
      desc: '费用申请功能接入后，差旅、会务、办公采购等前置申请会统一汇总到这里。',
      icon: 'mdi mdi-file-sign-outline',
      actionLabel: '',
      actionIcon: '',
      tone: 'theme',
      artLabel: 'APPLY',
      tips: ['申请、报销、审批与归档统一在此查看', '申请批准后可继续发起报销']
    }
  }

  return {
    eyebrow: filtered ? '筛选结果为空' : '单据中心',
    title: filtered ? '没有符合当前条件的单据' : `“${activeScopeTab.value}”里暂时没有单据`,
    desc: filtered
      ? '可以清空当前分类下的筛选条件后再看看。'
      : '当前视角暂无可展示单据，可以切换其他视角或发起一笔报销。',
    icon: filtered ? 'mdi mdi-magnify-scan' : 'mdi mdi-file-document-multiple-outline',
    actionLabel: '',
    actionIcon: '',
    tone: 'theme',
    artLabel: filtered ? 'FILTER' : 'DOCS',
    tips: ['单据中心已接入当前报销单据', '归档视角会同步已归档数据']
  }
})

function resolveArchivedDocumentNode(normalized, documentTypeCode) {
  if (documentTypeCode === DOCUMENT_TYPE_APPLICATION) {
    return '申请归档'
  }
  if (normalized.status === 'paid' || normalized.approvalStatus === '已付款') {
    return '已付款'
  }
  return normalized.node || normalized.workflowNode || '财务归档'
}

function resolveArchivedStatusLabel(normalized) {
  if (normalized.status === 'paid' || normalized.approvalStatus === '已付款' || normalized.node === '已付款') {
    return '已付款'
  }
  return '已归档'
}

function buildDocumentRow(request, options = {}) {
  const normalized = normalizeRequestForUi(request)
  if (!normalized) {
    return null
  }

  const archived = Boolean(options.archived)
  const statusGroup = resolveStatusGroup(normalized, archived)
  const statusLabel = archived ? resolveArchivedStatusLabel(normalized) : resolveStatusLabel(normalized, statusGroup)
  const riskMeta = buildDocumentRiskMeta(normalized)
  const documentNo = normalized.documentNo || normalized.id || normalized.claimId || '待生成'
  const claimId = normalized.claimId || normalized.id || documentNo
  const createdAtSource = normalized.createdAt || normalized.submittedAt || normalized.applyTime || normalized.updatedAt
  const updatedAtSource = normalized.updatedAt || normalized.submittedAt || normalized.createdAt || normalized.applyTime
  const createdSortTime = resolveDocumentSortTime(createdAtSource)
  const updatedSortTime = resolveDocumentSortTime(updatedAtSource)
  const documentTypeCode = normalized.documentTypeCode || DOCUMENT_TYPE_REIMBURSEMENT
  const documentTypeLabel =
    normalized.documentTypeLabel
    || (documentTypeCode === DOCUMENT_TYPE_APPLICATION ? '申请单' : '报销单')
  const initiatorName = String(
    normalized.person
    || normalized.employeeName
    || normalized.profileName
    || normalized.applicant
    || request?.employee_name
    || request?.employeeName
    || request?.person
    || ''
  ).trim() || '待补充'

  return {
    ...normalized,
    rawRequest: request,
    documentKey: `${options.source || 'owned'}:${claimId || documentNo}`,
    documentTypeCode,
    documentTypeLabel,
    claimId,
    documentNo,
    initiatorName,
    node: archived ? resolveArchivedDocumentNode(normalized, documentTypeCode) : (normalized.node || normalized.workflowNode || '待提交'),
    statusGroup,
    statusLabel,
    statusTone: archived ? 'archived' : resolveStatusTone(normalized, statusGroup),
    riskTone: riskMeta.tone,
    riskLabel: riskMeta.label,
    riskCount: riskMeta.count,
    riskTags: riskMeta.tags,
    source: options.source || 'owned',
    archived,
    createdAtDisplay: formatDocumentListTime(createdAtSource),
    stayTimeDisplay: resolveDocumentStayTimeDisplay(normalized),
    isNewDocument: archived
      ? false
      : isNewDocument({ ...normalized, source: options.source || 'owned', claimId, documentNo }, viewedDocumentKeys.value),
    updatedAtDisplay: formatDocumentListTime(updatedAtSource),
    createdSortTime,
    updatedSortTime,
    sortTime: Math.max(createdSortTime, updatedSortTime)
  }
}

function resolveStatusGroup(row, archived) {
  if (archived) return 'completed'
  if (row.approvalKey === 'draft') return 'draft'
  if (row.approvalKey === 'supplement' && row.status === 'returned') return 'pending_submit'
  if (row.approvalKey === 'supplement') return 'supplement'
  if (row.approvalKey === 'pending_payment') return 'pending_payment'
  if (row.approvalKey === 'in_progress') return 'in_progress'
  if (row.approvalKey === 'completed') return 'completed'
  return 'other'
}

function resolveStatusLabel(row, statusGroup) {
  if (statusGroup === 'pending_submit') return '待提交'
  if (statusGroup === 'pending_payment') return '待付款'
  return row.approval || row.approvalStatus || '处理中'
}

function resolveStatusTone(row, statusGroup) {
  if (statusGroup === 'pending_submit') return 'warning'
  return row.approvalTone || 'neutral'
}

function resolveDocumentRiskFlags(row) {
  if (Array.isArray(row?.riskFlags)) {
    return row.riskFlags
  }
  if (Array.isArray(row?.risk_flags_json)) {
    return row.risk_flags_json
  }
  return []
}

function buildDocumentRiskMeta(row) {
  const riskFlags = resolveDocumentRiskFlags(row)
  const riskSummary = row?.riskSummary || row?.risk
  const count = countClaimRisks(riskFlags, riskSummary)
  if (!count) {
    const meta = RISK_TONE_META.none
    return {
      ...meta,
      count: 0,
      tags: [{ ...meta }]
    }
  }

  const tone = resolveArchiveRiskTone(riskFlags, riskSummary)
  const meta = RISK_TONE_META[tone] || RISK_TONE_META.medium
  return {
    ...meta,
    count,
    tags: [{ tone: meta.tone, label: `${meta.label} ${count}项` }]
  }
}

function matchesRiskLevelTab(row, tab) {
  if (activeScopeTab.value !== DOCUMENT_SCOPE_ARCHIVE && isArchivedDocumentRow(row)) {
    return false
  }

  if (tab === '全部') return true
  if (tab === '高风险') return row.riskTone === 'high'
  if (tab === '中风险') return row.riskTone === 'medium'
  if (tab === '低风险') return row.riskTone === 'low'
  if (tab === '无风险') return row.riskTone === 'none'
  return true
}

function matchesAppliedDateRange(row) {
  if (!appliedStart.value || !appliedEnd.value) {
    return true
  }

  const date = extractDateText(row.updatedAt || row.submittedAt || row.createdAt || row.applyTime)
  return Boolean(date) && date >= appliedStart.value && date <= appliedEnd.value
}

function mergeDocumentRows(rows) {
  const rowMap = new Map()

  rows.filter(Boolean).forEach((row) => {
    const key = row.claimId || row.documentNo || row.documentKey
    const current = rowMap.get(key)
    if (!current || resolveSourcePriority(row) >= resolveSourcePriority(current)) {
      rowMap.set(key, row)
    }
  })

  return sortDocumentRowsByLatestTime(Array.from(rowMap.values()))
}

function resolveSourcePriority(row) {
  if (row.archived) return 3
  if (row.source === 'approval') return 2
  return 1
}

function hasActiveFilters() {
  return Boolean(
    listKeyword.value.trim()
    || activeStatusTab.value !== '全部'
    || (showDocumentTypeFilter.value && activeDocumentType.value !== DOCUMENT_TYPE_ALL)
    || activeScene.value !== SCENE_ALL
    || appliedStart.value
    || appliedEnd.value
  )
}

function toggleFilter(key) {
  openFilterKey.value = openFilterKey.value === key ? '' : key
  if (openFilterKey.value) {
    datePopover.value = false
  }
}

function toggleDatePopover() {
  datePopover.value = !datePopover.value
  if (datePopover.value) {
    openFilterKey.value = ''
  }
}

function selectDocumentType(value) {
  activeDocumentType.value = value
  openFilterKey.value = ''
}

function selectScene(value) {
  activeScene.value = value
  openFilterKey.value = ''
}

function selectStatusTab(value) {
  activeStatusTab.value = value
  openFilterKey.value = ''
}

function applyDateRange() {
  if (!rangeStart.value || !rangeEnd.value) {
    return
  }

  appliedStart.value = rangeStart.value
  appliedEnd.value = rangeEnd.value
  datePopover.value = false
}

function clearDateRange() {
  rangeStart.value = ''
  rangeEnd.value = ''
  appliedStart.value = ''
  appliedEnd.value = ''
  datePopover.value = false
}

function resetFilters() {
  activeStatusTab.value = '全部'
  activeDocumentType.value = DOCUMENT_TYPE_ALL
  activeScene.value = SCENE_ALL
  listKeyword.value = ''
  clearDateRange()
  openFilterKey.value = ''
  currentPage.value = 1
}

function handleEmptyAction() {
  if (activeDocumentType.value === DOCUMENT_TYPE_APPLICATION) {
    emit('create-application')
    return
  }

  if (hasActiveFilters()) {
    resetFilters()
    return
  }

  emit('create-request')
}

function changePageSize(size) {
  pageSize.value = size
  currentPage.value = 1
}

function applyRemoteViewedDocumentStates(states) {
  viewedDocumentKeys.value = mergeNotificationStatesIntoViewedDocumentKeys(states, viewedDocumentKeys.value)
}

async function loadRemoteViewedDocumentKeys() {
  try {
    applyRemoteViewedDocumentStates(await fetchNotificationStates())
  } catch {
    // 接口不可用时保留本机已读缓存，避免影响单据中心主流程。
  }
}

async function syncDocumentViewedPatches(patches) {
  const normalizedPatches = (Array.isArray(patches) ? patches : [patches]).filter(Boolean)
  if (!normalizedPatches.length) {
    return
  }

  try {
    applyRemoteViewedDocumentStates(await patchNotificationStates(normalizedPatches))
  } catch {
    // 本机状态已先落地；远端失败时等待下次操作或刷新重试。
  }
}

function openDocument(row) {
  writeDocumentScope(activeScopeTab.value, scopeTabs)
  const viewedPatch = buildDocumentViewedStatePatch(row)
  viewedDocumentKeys.value = markDocumentViewed(row, viewedDocumentKeys.value)
  void syncDocumentViewedPatches([viewedPatch])
  emit('open-document', row.rawRequest || row)
}

function markAllDocumentsRead() {
  if (!totalNewDocumentCount.value) {
    return
  }

  const viewedPatches = buildDocumentsViewedStatePatches(allReadableDocumentRows.value, viewedDocumentKeys.value)
  viewedDocumentKeys.value = markDocumentsViewed(allReadableDocumentRows.value, viewedDocumentKeys.value)
  void syncDocumentViewedPatches(viewedPatches)
}

async function loadSupportingRows() {
  supportingLoading.value = true
  supportingError.value = ''

  const [approvalResult, archiveResult] = await Promise.allSettled([
    fetchAllApprovalExpenseClaims(),
    fetchAllArchivedExpenseClaims()
  ])

  if (approvalResult.status === 'fulfilled') {
    approvalRows.value = excludeArchivedDocumentRows(
      extractExpenseClaimItems(approvalResult.value)
          .map((item) => mapExpenseClaimToRequest(item))
          .map((item) => buildDocumentRow(item, { source: 'approval' }))
          .filter(Boolean)
    )
  } else {
    approvalRows.value = []
  }

  if (archiveResult.status === 'fulfilled') {
    archiveRows.value = extractExpenseClaimItems(archiveResult.value)
      .map((item) => mapExpenseClaimToRequest(item))
      .map((item) => buildDocumentRow(item, { source: 'archive', archived: true }))
      .filter(Boolean)
  } else {
    archiveRows.value = []
    supportingError.value = archiveResult.reason instanceof Error
      ? archiveResult.reason.message
      : '归档数据加载失败。'
  }

  supportingLoading.value = false
}

function reloadAll() {
  emit('reload')
  void loadRemoteViewedDocumentKeys()
  void loadSupportingRows()
}

watch(
  [activeScopeTab, activeStatusTab, activeDocumentType, activeScene, listKeyword, appliedStart, appliedEnd],
  () => {
    currentPage.value = 1
  }
)

watch(
  [currentPage, pageSize, activeScopeTab, activeStatusTab, activeDocumentType, activeScene, listKeyword, appliedStart, appliedEnd],
  () => {
    if (route.name !== 'app-documents') {
      return
    }

    const nextQuery = buildDocumentCenterRouteQuery()
    if (!routeQueryEquals(route.query, nextQuery)) {
      router.replace({ name: 'app-documents', query: nextQuery })
    }
  }
)

watch(activeFilterConfig, () => {
  openFilterKey.value = ''
  datePopover.value = false

  if (!showDocumentTypeFilter.value) {
    activeDocumentType.value = DOCUMENT_TYPE_ALL
  }

  if (!statusFilterOptions.value.some((item) => item.value === activeStatusTab.value)) {
    activeStatusTab.value = '全部'
  }
})

watch(sceneFilterOptions, (options) => {
  if (!options.some((item) => item.value === activeScene.value)) {
    activeScene.value = SCENE_ALL
  }
})

watch(documentSummary, (summary) => {
  emit('summary-change', summary)
}, { immediate: true })

onMounted(() => {
  void loadRemoteViewedDocumentKeys()
  void loadSupportingRows()
})

watch(
  () => props.refreshToken,
  (token, previousToken) => {
    if (token && token !== previousToken) {
      void loadRemoteViewedDocumentKeys()
      void loadSupportingRows()
    }
  }
)
</script>

<style scoped src="../assets/styles/components/document-list-shared.css"></style>
<style scoped src="../assets/styles/views/documents-center-view.css"></style>
