<template>
  <section class="approval-task-queue" aria-label="审批任务工作台">
    <header class="approval-task-toolbar">
      <div class="approval-task-filters">
        <label class="approval-task-search">
          <i class="mdi mdi-magnify"></i>
          <input
            v-model="draftFilters.keyword"
            type="search"
            placeholder="搜索单号、申请人或部门"
            @keydown.enter.prevent="applyFilters"
          />
        </label>
        <label>
          <span>风险</span>
          <select v-model="draftFilters.riskLevel" @change="applyFilters">
            <option value="">全部风险</option>
            <option value="critical">重大风险</option>
            <option value="high">高风险</option>
            <option value="medium">中风险</option>
            <option value="low">低风险</option>
          </select>
        </label>
        <label>
          <span>SLA</span>
          <select v-model="draftFilters.slaState" @change="applyFilters">
            <option value="">全部 SLA</option>
            <option value="due_soon">即将超时</option>
            <option value="overdue">已超时</option>
            <option value="escalated">已升级</option>
          </select>
        </label>
        <button type="button" class="filter-apply" @click="applyFilters">查询</button>
      </div>

      <div class="approval-task-batch-actions">
        <span v-if="queue.selectedCount.value">已选择 {{ queue.selectedCount.value }} 项</span>
        <button
          type="button"
          :disabled="!queue.selectedCount.value || queue.batchBusy.value"
          @click="batchConfirmOpen = true"
        >
          <i class="mdi mdi-check-all"></i>
          批量通过
        </button>
        <button type="button" :disabled="queue.loading.value" @click="reload">
          <i :class="queue.loading.value ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-refresh'"></i>
          刷新
        </button>
      </div>
    </header>

    <p v-if="queue.errorMessage.value" class="approval-task-error" role="alert">
      {{ queue.errorMessage.value }}
    </p>

    <ApprovalBatchResultPanel
      :result="queue.lastBatchResult.value"
      :retryable-count="queue.selectedCount.value"
      @close="queue.clearBatchResult"
      @retry-failed="batchConfirmOpen = true"
    />

    <div class="approval-task-table-wrap" :aria-busy="queue.loading.value">
      <div v-if="queue.loading.value && !rows.length" class="approval-task-state">
        <i class="mdi mdi-loading mdi-spin"></i>
        正在加载审批任务
      </div>
      <div v-else-if="!rows.length" class="approval-task-state">
        <i class="mdi mdi-clipboard-check-outline"></i>
        当前筛选条件下没有审批任务
      </div>
      <table v-else>
        <thead>
          <tr>
            <th class="selection-column">
              <input
                type="checkbox"
                aria-label="选择当前页可批量审批的任务"
                :checked="queue.pageSelection.value.allSelected"
                :indeterminate="queue.pageSelection.value.indeterminate"
                :disabled="!queue.pageSelection.value.eligibleCount"
                @change="queue.toggleCurrentPage($event.target.checked)"
              />
            </th>
            <th>单号 / 申请人</th>
            <th>金额</th>
            <th>当前节点</th>
            <th>优先级</th>
            <th>风险 / 证据</th>
            <th>SLA / 升级</th>
            <th>当前处理人</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr
            v-for="(row, index) in rows"
            :key="row.id"
            :ref="(element) => setRowRef(row.id, element)"
            :class="{
              focused: focusedTaskId === row.id,
              readonly: !row.actions.length
            }"
            :tabindex="focusedTaskId === row.id || (!focusedTaskId && index === 0) ? 0 : -1"
            @click="openDetail(row.source)"
            @focus="focusedTaskId = row.id"
            @keydown="handleRowKeydown($event, index, row)"
          >
            <td class="selection-column" @click.stop>
              <input
                type="checkbox"
                :aria-label="`选择审批任务 ${row.claimNo}`"
                :checked="selectedSet.has(row.id)"
                :disabled="!selectionMeta(row.source).selectable"
                :title="selectionMeta(row.source).reason"
                @change="queue.toggleTask(row.source, $event.target.checked)"
              />
            </td>
            <td>
              <strong>{{ row.claimNo }}</strong>
              <span>{{ row.applicant }} · {{ row.department }}</span>
            </td>
            <td><strong>{{ row.amountLabel }}</strong></td>
            <td>
              <strong>{{ row.nodeLabel }}</strong>
              <span>{{ row.taskKind === 'root' ? '主任务' : row.taskKind }}</span>
            </td>
            <td>
              <em class="priority" :class="row.priorityTier">
                {{ row.priorityScore }} · {{ row.priorityTier }}
              </em>
            </td>
            <td>
              <em class="risk" :class="row.riskLevel">{{ row.riskLabel }}</em>
              <span>{{ row.openRiskCount }} 项开放风险 · 证据 {{ row.evidenceLabel }}</span>
            </td>
            <td>
              <strong class="sla" :class="row.sla.tone" :title="row.sla.title">
                {{ row.sla.label }}
              </strong>
              <span v-if="row.sla.escalationLabel">
                已升级 {{ row.sla.escalationLabel }}
              </span>
              <span v-else>尚未升级</span>
            </td>
            <td>
              <strong>{{ row.assigneeName }}</strong>
              <span v-if="row.readOnlyReason" :title="row.readOnlyReason">
                {{ row.readOnlyReason }}
              </span>
            </td>
            <td @click.stop>
              <div v-if="row.actions.length" class="row-actions">
                <button
                  v-for="action in row.actions"
                  :key="action.action"
                  type="button"
                  :class="action.tone"
                  @click="emitAction(action.action, row.source)"
                >
                  <i :class="action.icon"></i>
                  {{ action.label }}
                </button>
              </div>
              <span v-else class="readonly-label" :title="row.readOnlyReason">
                只读
              </span>
            </td>
          </tr>
        </tbody>
      </table>
    </div>

    <EnterprisePagination
      v-if="rows.length || queue.total.value"
      :current-page="queue.page.value"
      :page-size="queue.pageSize.value"
      :page-size-options="pageSizeOptions"
      :total="queue.total.value"
      :total-pages="Math.max(1, queue.totalPages.value)"
      :summary="pageSummary"
      @update:current-page="changePage"
      @page-size-change="changePageSize"
    />

    <ConfirmDialog
      :open="batchConfirmOpen"
      badge="批量审批"
      :title="`确认通过所选 ${queue.selectedCount.value} 项审批任务吗？`"
      description="服务器会逐项复核权限、任务版本、单据状态和风险前置条件；部分失败不会被显示为全部成功。"
      cancel-text="返回核对"
      confirm-text="确认批量通过"
      busy-text="逐项审批中..."
      confirm-icon="mdi mdi-check-all"
      :busy="queue.batchBusy.value"
      :confirm-disabled="!queue.selectedCount.value"
      @close="closeBatchConfirm"
      @confirm="confirmBatchApprove"
    >
      <label class="batch-opinion">
        <span>统一审批意见（可选）</span>
        <textarea
          v-model="batchOpinion"
          maxlength="500"
          :disabled="queue.batchBusy.value"
          placeholder="不填写则由服务端使用审批任务默认意见"
        ></textarea>
      </label>
    </ConfirmDialog>
  </section>
</template>

<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'

import { useApprovalTaskQueue } from '../../composables/useApprovalTaskQueue.js'
import {
  resolveApprovalTaskKeyboardCommand,
  resolveApprovalTaskRow
} from '../../views/scripts/approvalTaskQueueViewModel.js'
import { resolveApprovalTaskSelectionMeta } from '../../views/scripts/approvalTaskQueueState.js'
import ConfirmDialog from '../shared/ConfirmDialog.vue'
import EnterprisePagination from '../shared/EnterprisePagination.vue'
import ApprovalBatchResultPanel from './ApprovalBatchResultPanel.vue'

const props = defineProps({
  initialFilters: { type: Object, default: () => ({}) },
  initialPage: { type: Number, default: 1 },
  initialPageSize: { type: Number, default: 20 },
  refreshToken: { type: [String, Number], default: 0 },
  serviceOverrides: { type: Object, default: () => ({}) }
})

const emit = defineEmits([
  'open-detail',
  'action',
  'batch-result',
  'selection-change',
  'loaded',
  'state-change',
  'error'
])

const queue = useApprovalTaskQueue({
  initialFilters: props.initialFilters,
  initialPage: props.initialPage,
  initialPageSize: props.initialPageSize,
  services: props.serviceOverrides
})
const draftFilters = reactive({
  keyword: String(props.initialFilters.keyword || ''),
  riskLevel: String(props.initialFilters.riskLevel || props.initialFilters.risk_level || ''),
  slaState: String(props.initialFilters.slaState || props.initialFilters.sla_state || '')
})
const focusedTaskId = ref('')
const batchConfirmOpen = ref(false)
const batchOpinion = ref('')
const clock = ref(Date.now())
const rowRefs = new Map()
const pageSizeOptions = [10, 20].map((value) => ({ label: `${value} 条/页`, value }))
const rows = computed(() =>
  queue.items.value.map((item) => resolveApprovalTaskRow(item, clock.value))
)
const selectedSet = computed(() => new Set(queue.selectedTaskIds.value))
const pageSummary = computed(() =>
  `共 ${queue.total.value} 条，当前第 ${queue.page.value} / ${Math.max(1, queue.totalPages.value)} 页`
)

watch(
  () => queue.selectedTaskIds.value,
  (selectedTaskIds) => emit('selection-change', [...selectedTaskIds]),
  { deep: true }
)

watch(
  () => props.refreshToken,
  () => void reload()
)

watch(
  () => rows.value.map((row) => row.id).join('|'),
  () => {
    if (!rows.value.some((row) => row.id === focusedTaskId.value)) {
      focusedTaskId.value = rows.value[0]?.id || ''
    }
  }
)

let clockTimer = 0
onMounted(() => {
  clockTimer = window.setInterval(() => {
    clock.value = Date.now()
  }, 60000)
  void reload()
})

onBeforeUnmount(() => {
  if (clockTimer) window.clearInterval(clockTimer)
})

async function reload() {
  try {
    const response = await queue.loadQueue()
    publishQueueResponse(response)
  } catch (error) {
    emit('error', error)
  }
}

async function applyFilters() {
  try {
    const response = await queue.setFilters({ ...draftFilters })
    publishQueueResponse(response)
  } catch (error) {
    emit('error', error)
  }
}

async function changePage(page) {
  try {
    publishQueueResponse(await queue.setPage(page))
    focusFirstRow()
  } catch (error) {
    emit('error', error)
  }
}

async function changePageSize(pageSize) {
  try {
    publishQueueResponse(await queue.setPageSize(pageSize))
    focusFirstRow()
  } catch (error) {
    emit('error', error)
  }
}

function publishQueueResponse(response) {
  if (!response || response.ignored) return
  emit('loaded', response.result)
  emit('state-change', {
    filters: { ...queue.filters.value },
    page: queue.page.value,
    pageSize: queue.pageSize.value
  })
}

function selectionMeta(item) {
  return resolveApprovalTaskSelectionMeta(item)
}

function setRowRef(taskId, element) {
  if (element) {
    rowRefs.set(taskId, element)
  } else {
    rowRefs.delete(taskId)
  }
}

function focusRow(index) {
  if (!rows.value.length) return
  const normalizedIndex = Math.min(Math.max(index, 0), rows.value.length - 1)
  const row = rows.value[normalizedIndex]
  focusedTaskId.value = row.id
  nextTick(() => rowRefs.get(row.id)?.focus())
}

function focusFirstRow() {
  nextTick(() => focusRow(0))
}

function handleRowKeydown(event, index, row) {
  const command = resolveApprovalTaskKeyboardCommand(event)
  if (!command) return
  event.preventDefault()
  if (command === 'open') {
    openDetail(row.source)
  } else if (command === 'toggle') {
    queue.toggleTask(row.source)
  } else if (command === 'next') {
    focusRow(index + 1)
  } else if (command === 'previous') {
    focusRow(index - 1)
  }
}

function openDetail(item) {
  emit('open-detail', item)
}

function emitAction(action, item) {
  emit('action', { action, item })
}

function closeBatchConfirm() {
  if (!queue.batchBusy.value) {
    batchConfirmOpen.value = false
  }
}

async function confirmBatchApprove() {
  try {
    const result = await queue.batchApprove({ opinion: batchOpinion.value })
    if (result) {
      batchConfirmOpen.value = false
      batchOpinion.value = ''
      emit('batch-result', result)
    }
  } catch (error) {
    emit('error', error)
  }
}

defineExpose({
  clearSelection: queue.clearSelection,
  queue,
  refreshTask: queue.refreshTask,
  reload
})
</script>

<style scoped>
.approval-task-queue {
  display: grid;
  gap: 12px;
  min-width: 0;
}

.approval-task-toolbar,
.approval-task-filters,
.approval-task-batch-actions,
.approval-task-filters label,
.row-actions {
  display: flex;
  align-items: center;
}

.approval-task-toolbar {
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.approval-task-filters,
.approval-task-batch-actions {
  gap: 8px;
  flex-wrap: wrap;
}

.approval-task-filters label {
  min-height: 38px;
  gap: 6px;
  padding: 0 10px;
  border: 1px solid #dbe5ef;
  border-radius: 6px;
  background: #fff;
  color: #64748b;
  font-size: 12px;
  font-weight: 750;
}

.approval-task-search {
  width: min(320px, 70vw);
}

.approval-task-filters input,
.approval-task-filters select {
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: #0f172a;
}

.approval-task-search input {
  flex: 1;
}

.approval-task-toolbar button,
.row-actions button {
  min-height: 36px;
  padding: 0 12px;
  border: 1px solid #dbe5ef;
  border-radius: 6px;
  background: #fff;
  color: #334155;
  font-weight: 800;
}

.approval-task-batch-actions button:first-of-type,
.filter-apply,
.row-actions button.primary {
  border-color: var(--theme-primary);
  background: var(--theme-primary);
  color: #fff;
}

.approval-task-toolbar button:disabled,
.row-actions button:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

.approval-task-batch-actions span {
  color: #475569;
  font-size: 12px;
  font-weight: 800;
}

.approval-task-error {
  margin: 0;
  padding: 10px 12px;
  border: 1px solid #fecaca;
  border-radius: 6px;
  background: #fef2f2;
  color: #b91c1c;
  font-size: 13px;
}

.approval-task-table-wrap {
  min-width: 0;
  overflow-x: auto;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
}

.approval-task-state {
  min-height: 220px;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 8px;
  color: #64748b;
}

.approval-task-state i {
  font-size: 28px;
}

table {
  width: 100%;
  min-width: 1280px;
  border-collapse: collapse;
}

th,
td {
  padding: 11px 10px;
  border-bottom: 1px solid #edf2f7;
  text-align: left;
  vertical-align: middle;
}

th {
  background: #f8fafc;
  color: #64748b;
  font-size: 12px;
  font-weight: 850;
}

tbody tr {
  cursor: pointer;
  outline: none;
}

tbody tr:hover,
tbody tr.focused,
tbody tr:focus-visible {
  background: #f5faff;
  box-shadow: inset 3px 0 var(--theme-primary);
}

tbody tr.readonly {
  background: #fbfcfe;
}

td strong,
td span {
  display: block;
}

td strong {
  color: #0f172a;
  font-size: 13px;
}

td span {
  margin-top: 3px;
  color: #64748b;
  font-size: 11px;
}

.selection-column {
  width: 44px;
  text-align: center;
}

.selection-column input {
  width: 16px;
  height: 16px;
  accent-color: var(--theme-primary);
}

em.priority,
em.risk,
.sla,
.readonly-label {
  display: inline-flex;
  width: fit-content;
  padding: 3px 7px;
  border-radius: 999px;
  font-size: 11px;
  font-style: normal;
  font-weight: 850;
}

.priority.urgent,
.risk.critical,
.risk.high,
.sla.danger {
  background: #fef2f2;
  color: #b91c1c;
}

.priority.high,
.risk.medium,
.sla.warning {
  background: #fffbeb;
  color: #b45309;
}

.priority.normal,
.risk.low,
.sla.safe {
  background: #ecfdf5;
  color: #047857;
}

.sla.neutral,
.readonly-label {
  background: #f1f5f9;
  color: #64748b;
}

.row-actions {
  gap: 5px;
  flex-wrap: wrap;
}

.row-actions button {
  min-height: 30px;
  padding: 0 8px;
  font-size: 11px;
}

.row-actions button.danger {
  border-color: #fecaca;
  color: #b91c1c;
}

.row-actions button.warning {
  border-color: #fde68a;
  color: #b45309;
}

.batch-opinion {
  display: grid;
  gap: 7px;
}

.batch-opinion span {
  color: #334155;
  font-size: 12px;
  font-weight: 850;
}

.batch-opinion textarea {
  min-height: 80px;
  padding: 10px;
  border: 1px solid #dbe5ef;
  border-radius: 6px;
  resize: vertical;
}

@media (max-width: 900px) {
  .approval-task-toolbar {
    align-items: stretch;
  }

  .approval-task-filters,
  .approval-task-batch-actions {
    width: 100%;
  }
}
</style>
