<template>
  <a-modal
    v-model:open="visible"
    :title="modalTitle"
    width="96%"
    :footer="null"
    :mask-closable="false"
    :destroy-on-close="false"
    wrap-class-name="seal-position-sign-wrap"
    :bodyStyle="{ padding: 0, height: 'calc(100vh - 180px)', overflow: 'hidden' }"
  >
    <div class="seal-position-sign-layout">
      <div ref="previewPaneRef" class="preview-pane">
        <div class="preview-toolbar">
          <a-space wrap>
            <a-tag :color="pageState.sealContractId ? 'processing' : 'default'">
              签章平台合同ID：{{ pageState.sealContractId || '未初始化' }}
            </a-tag>
            <a-tag color="blue">我的可签方：{{ activePartyLabel }}</a-tag>
            <a-tag color="gold">必签未完成：{{ currentRequiredMissingCount }}</a-tag>
            <a-button :loading="loadingPage" @click="loadPageState(true)">刷新状态</a-button>
          </a-space>
        </div>

        <div class="preview-scroll">
          <div
            v-for="(imgUrl, pageIndex) in pdfImages"
            :key="`page_${pageIndex}`"
            :ref="(el) => setPageRef(el, pageIndex + 1)"
            class="pdf-page-card"
            :class="{ 'is-active': currentPageNum === pageIndex + 1 }"
            @click="currentPageNum = pageIndex + 1"
          >
            <div class="page-card-header">
              <span>第 {{ pageIndex + 1 }} 页</span>
              <a-space size="small">
                <a-tag color="processing">{{ getPageBoxCount(pageIndex + 1) }} 个签章位</a-tag>
                <a-tag v-if="currentPageNum === pageIndex + 1" color="success">当前页</a-tag>
              </a-space>
            </div>
            <div
              class="pdf-page-shell"
              :style="{ width: `${pageDisplayWidth}px`, height: `${pageDisplayHeight}px` }"
            >
              <img
                :src="imgUrl"
                class="pdf-page-image"
                :style="{ width: `${pageDisplayWidth}px`, height: `${pageDisplayHeight}px` }"
                draggable="false"
              />

              <div
                v-for="box in getPageBoxes(pageIndex + 1)"
                :key="box.id"
                class="seal-position-box"
                :class="getBoxClass(box)"
                :style="getBoxStyle(box)"
                @click.stop="focusBox(box.id, box.pageNum)"
                @dragover.prevent="handleBoxDragOver(box)"
                @drop.prevent="handleDropSealOnBox($event, box)"
              >
                <div class="box-head">
                  <span class="box-title">{{ box.label || '未命名签章位' }}</span>
                  <a-tag v-if="box.required === 'Y'" color="red">必签</a-tag>
                </div>
                <div class="box-party">{{ getPartyLabel(box.partyId) }}</div>
                <div class="box-status">{{ getStatusText(box.signedStatus) }}</div>
                <div v-if="box.sealName" class="box-seal-name">印章：{{ box.sealName }}</div>
                <div v-if="box.signedByName" class="box-signed-by">签署人：{{ box.signedByName }}</div>

                <div
                  v-if="isBoxMine(box) && selectedSealMap[box.id]?.pictureUrl"
                  class="box-seal-preview"
                  :style="getSealPreviewStyle(box)"
                >
                  <img :src="selectedSealMap[box.id].pictureUrl" class="box-seal-image" />
                </div>

                <div v-if="box.id === focusedBoxId" class="box-focus-ring" />
              </div>
            </div>
          </div>

          <a-empty
            v-if="!loadingPage && !pdfImages.length"
            class="empty-preview"
            description="暂无签署预览，请先由管理端配置签章位"
          />
        </div>
      </div>

      <div class="operation-pane">
        <div class="side-card">
          <div class="side-card-title">签署信息</div>
          <div class="meta-item">
            <span>合同名称</span>
            <strong>{{ contractDetail?.contractName || '-' }}</strong>
          </div>
          <div class="meta-item">
            <span>签署状态</span>
            <strong>{{ contractDetail?.status || '-' }}</strong>
          </div>
          <div class="meta-item">
            <span>当前签署方</span>
            <strong>{{ activePartyLabel }}</strong>
          </div>
          <a-form-item v-if="partySelectorVisible" label="切换代表签署方" class="party-selector-item">
            <a-select
              v-model:value="selectedPartyId"
              :options="availablePartyOptions"
              placeholder="请选择当前代表的签署方"
              @change="handlePartyChanged"
            />
          </a-form-item>
          <div class="meta-note">
            本页面采用“预设框内盖章”。只能对属于当前签署方的框放章，放章后系统会吸附到这个框，不允许在其他位置自由拖盖。
          </div>
        </div>

        <div class="side-card">
          <div class="side-card-title">获取印章</div>
          <div class="query-grid">
            <a-form-item label="姓名">
              <a-input v-model:value="queryName" disabled />
            </a-form-item>
            <a-form-item label="手机号">
              <a-input v-model:value="queryMobile" disabled />
            </a-form-item>
            <a-form-item label="公司名称">
              <a-input v-model:value="queryEntName" allow-clear placeholder="可选，筛选印章所属公司" />
            </a-form-item>
          </div>
          <div class="seal-type-wrap">
            <a-checkbox-group v-model:value="querySealTypes">
              <a-row :gutter="8">
                <a-col v-for="type in sealTypeOptions" :key="type.value" :span="12">
                  <a-checkbox :value="type.value">{{ type.label }}</a-checkbox>
                </a-col>
              </a-row>
            </a-checkbox-group>
          </div>
          <a-button type="primary" block :loading="fetchingSeals" @click="fetchSeals">获取印章</a-button>
        </div>

        <div class="side-card side-card-grow">
          <div class="side-card-title">拖动印章到待签框</div>
          <div v-if="sealList.length" class="seal-list">
            <div
              v-for="seal in sealList"
              :key="seal.sealId"
              class="seal-card"
              draggable="true"
              @dragstart="handleSealDragStart($event, seal)"
            >
              <img :src="seal.pictureUrl" class="seal-thumb" />
              <div class="seal-name">{{ seal.sealName }}</div>
            </div>
          </div>
          <a-empty v-else description="请先获取印章" />
        </div>

        <div class="side-card">
          <div class="side-card-title">当前签章处理</div>
          <div class="pending-list">
            <div
              v-for="box in myActionBoxes"
              :key="box.id"
              class="pending-item"
              @click="focusBox(box.id, box.pageNum)"
            >
              <div>
                <div class="pending-title">{{ box.label || '未命名签章位' }}</div>
                <div class="pending-sub">第 {{ box.pageNum }} 页 · {{ getStatusText(box.signedStatus) }} · {{ box.sealName || '未选择印章' }}</div>
              </div>
              <a-button
                v-if="Number(box.signedStatus) === STATUS_SELECTED"
                type="link"
                danger
                size="small"
                @click.stop="clearBoxSelection(box)"
              >
                移除
              </a-button>
            </div>
          </div>
        </div>

        <div class="footer-actions">
          <a-space direction="vertical" style="width: 100%" size="middle">
            <a-button type="primary" block :loading="preSigning" @click="handlePreSignSubmit">
              确认预盖章
            </a-button>
            <a-button block :loading="checkingComplete" @click="handleCompleteSigning">
              签署完成
            </a-button>
          </a-space>
        </div>
      </div>
    </div>

    <a-modal
      v-model:open="qrVisible"
      title="扫码确认预盖章"
      width="520px"
      :footer="null"
      :mask-closable="false"
    >
      <div class="qr-body">
        <img v-if="qrCode" :src="qrCode" class="qr-image" />
        <a-empty v-else description="二维码未生成" />
        <div class="qr-meta">requestId：{{ qrRequestId || '-' }}</div>
        <div class="qr-meta">剩余时间：{{ qrText }}</div>
        <div class="qr-actions">
          <a-space wrap>
            <a-button :loading="mockingCallback" type="primary" @click="handleMockSuccess">
              扫码成功（模拟）
            </a-button>
            <a-button @click="handleCloseQr">关闭</a-button>
          </a-space>
        </div>
      </div>
    </a-modal>
  </a-modal>
</template>

<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import { message } from 'ant-design-vue'
import {
  batchUpdateLaunchSealPositionStatus,
  checkLaunchSealPositions,
  finishLaunchSeal,
  getLaunchSealPositionPage,
} from '@/api/launch'
import {
  completeContract,
  downloadContract,
  getPreSignQrCode,
  getSealBySignerId,
  getUserUniqueId,
  mockPreSignSuccessCallback,
  pollCallback,
} from '@/api/seal'

const emit = defineEmits(['done'])

const visible = ref(false)
const loadingPage = ref(false)
const fetchingSeals = ref(false)
const preSigning = ref(false)
const checkingComplete = ref(false)
const mockingCallback = ref(false)
const previewPaneRef = ref(null)
const pageRefs = reactive({})

const contractDetail = ref(null)
const pageState = reactive({
  sealContractId: '',
  pdfToImageList: [],
  positions: [],
  availablePartyIds: [],
  preferredPartyId: null,
  imageWidth: 793,
  imageHeight: 1122,
})

const DEFAULT_IMAGE_WIDTH = 793
const DEFAULT_IMAGE_HEIGHT = 1122
const CALLBACK_HOST = 'http://47.110.50.12:7005'
const DEFAULT_SEAL_BASE_SIZE = 170
const DEFAULT_SEAL_PADDING_RATIO = 0.82
const STATUS_EMPTY = 0
const STATUS_SELECTED = 1
const STATUS_SIGNING = 2
const STATUS_SIGNED = 3
const STATUS_FAILED = 4

const queryName = ref('周小锋')
const queryMobile = ref('17681520782')
const queryEntName = ref('')
const querySealTypes = ref([0, 20, 10, 30, 40, 50, 60])
const sealList = ref([])
const selectedPartyId = ref(null)
const focusedBoxId = ref(null)
const currentPageNum = ref(1)
const pageDisplayWidth = ref(760)
const qrVisible = ref(false)
const qrCode = ref('')
const qrRequestId = ref('')
const qrExpiresIn = ref(300)
const qrCount = ref(300)
const currentSignerId = ref('')
let dragSealData = null
let qrTimer = null
let pollTimer = null

const selectedSealMap = reactive({})

const sealTypeOptions = [
  { label: '个人章', value: 0 },
  { label: '公章', value: 20 },
  { label: '法人章', value: 10 },
  { label: '财务章', value: 30 },
  { label: '合同章', value: 40 },
  { label: '发票章', value: 50 },
  { label: '其他章', value: 60 },
]

const modalTitle = computed(() => {
  const name = contractDetail.value?.contractName || '合同'
  return `在线签订 - ${name}`
})

const pdfImages = computed(() => normalizePdfImages(pageState.pdfToImageList))
const imageWidth = computed(() => Number(pageState.imageWidth || DEFAULT_IMAGE_WIDTH))
const imageHeight = computed(() => Number(pageState.imageHeight || DEFAULT_IMAGE_HEIGHT))
const scaleRatio = computed(() => pageDisplayWidth.value / imageWidth.value)
const pageDisplayHeight = computed(() => Math.round(imageHeight.value * scaleRatio.value))

const availablePartyOptions = computed(() => {
  const sourcePartyIds = [...new Set((pageState.availablePartyIds || [])
    .map((item) => Number(item))
    .filter((item) => !Number.isNaN(item)))]
  return sourcePartyIds
    .map((partyId) => {
      const party = findPartyById(partyId)
      if (!party) return null
      return {
        value: party.id,
        label: `${party.roleName || party.roleCode || '签署方'} - ${party.companyName || party.subjectCode || party.id}`,
      }
    })
    .filter(Boolean)
})

const partySelectorVisible = computed(() => availablePartyOptions.value.length !== 1)
const activePartyLabel = computed(() => {
  if (!selectedPartyId.value) {
    return availablePartyOptions.value.length ? '请选择签署方' : '当前用户未匹配到可签署方'
  }
  return getPartyLabel(selectedPartyId.value)
})

const myBoxes = computed(() =>
  pageState.positions.filter((item) => Number(item.partyId) === Number(selectedPartyId.value)),
)

const currentRequiredMissingCount = computed(() =>
  myBoxes.value.filter((item) => isRequiredBoxUnfinished(item)).length,
)

const myPendingSubmitBoxes = computed(() =>
  myBoxes.value.filter((item) => Number(item.signedStatus) === STATUS_SELECTED && !!selectedSealMap[item.id]),
)

const myActionBoxes = computed(() =>
  myBoxes.value.filter((item) => {
    const status = Number(item.signedStatus)
    return status === STATUS_SELECTED || status === STATUS_SIGNING
  }),
)

const qrText = computed(() => {
  const minute = Math.floor(qrCount.value / 60)
  const second = qrCount.value % 60
  return `${minute}分${String(second).padStart(2, '0')}秒`
})

function isSuccessResponse(res) {
  return String(res?.code || '') === '0000'
}

function getResult(res) {
  return res?.result || null
}

function normalizePdfImages(list = []) {
  return (Array.isArray(list) ? list : [])
    .map((item) => {
      if (typeof item === 'string') return item
      if (!item || typeof item !== 'object') return ''
      return item.url || item.imageUrl || item.previewUrl || item.fileUrl || item.path || ''
    })
    .filter(Boolean)
}

function roundValue(value) {
  return Math.round(Number(value || 0) * 100) / 100
}

function wait(ms) {
  return new Promise((resolve) => {
    window.setTimeout(resolve, ms)
  })
}

function findPartyById(partyId) {
  return (contractDetail.value?.partyList || []).find((item) => Number(item.id) === Number(partyId)) || null
}

function getPartyLabel(partyId) {
  const party = findPartyById(partyId)
  if (!party) {
    return '未识别签署方'
  }
  return `${party.roleName || party.roleCode || '签署方'} - ${party.companyName || party.subjectCode || party.id}`
}

function getStatusText(status) {
  const value = Number(status)
  if (value === STATUS_SELECTED) return '已选章待提交'
  if (value === STATUS_SIGNING) return '待扫码确认'
  if (value === STATUS_SIGNED) return '已签署'
  if (value === STATUS_FAILED) return '失败待重试'
  return '待签署'
}

function isBoxMine(box) {
  return Number(box.partyId) === Number(selectedPartyId.value)
}

function getPageBoxes(pageNum) {
  return pageState.positions.filter((item) => Number(item.pageNum) === Number(pageNum))
}

function getPageBoxCount(pageNum) {
  return getPageBoxes(pageNum).length
}

function getBoxClass(box) {
  return {
    'is-mine': isBoxMine(box),
    'is-other': !isBoxMine(box),
    'is-signed': Number(box.signedStatus) === STATUS_SIGNED,
    'is-selected-box': focusedBoxId.value === box.id,
    'is-missing': isBoxMissing(box),
  }
}

function getBoxStyle(box) {
  return {
    left: `${Number(box.x || 0) * scaleRatio.value}px`,
    top: `${Number(box.y || 0) * scaleRatio.value}px`,
    width: `${Number(box.width || 0) * scaleRatio.value}px`,
    height: `${Number(box.height || 0) * scaleRatio.value}px`,
  }
}

function getSealPlacement(box) {
  const boxX = Number(box.x || 0)
  const boxY = Number(box.y || 0)
  const boxWidth = Math.max(0, Number(box.width || 0))
  const boxHeight = Math.max(0, Number(box.height || 0))
  const innerSize = Math.max(48, Math.min(boxWidth, boxHeight) * DEFAULT_SEAL_PADDING_RATIO)
  const scale = roundValue(Math.max(0.3, innerSize / DEFAULT_SEAL_BASE_SIZE))
  const renderWidth = roundValue(DEFAULT_SEAL_BASE_SIZE * scale)
  const renderHeight = renderWidth
  const offsetX = roundValue(Math.max(0, (boxWidth - renderWidth) / 2))
  const offsetY = roundValue(Math.max(0, (boxHeight - renderHeight) / 2))
  return {
    x: roundValue(boxX + offsetX),
    y: roundValue(boxY + offsetY),
    width: renderWidth,
    height: renderHeight,
    offsetX,
    offsetY,
    scale,
  }
}

function getSealPreviewStyle(box) {
  const placement = getSealPlacement(box)
  return {
    width: `${placement.width * scaleRatio.value}px`,
    height: `${placement.height * scaleRatio.value}px`,
    left: `${placement.offsetX * scaleRatio.value}px`,
    top: `${placement.offsetY * scaleRatio.value}px`,
  }
}

function setPageRef(el, pageNum) {
  if (el) {
    pageRefs[pageNum] = el
  }
}

function resetSelectionMap() {
  Object.keys(selectedSealMap).forEach((key) => {
    delete selectedSealMap[key]
  })
}

function hydrateSelectedSealMap() {
  const preservedPictureMap = {}
  Object.keys(selectedSealMap).forEach((key) => {
    preservedPictureMap[key] = selectedSealMap[key]?.pictureUrl || ''
  })
  resetSelectionMap()
  pageState.positions.forEach((item) => {
    if (item.sealId && item.sealName && Number(item.signedStatus) !== STATUS_EMPTY) {
      selectedSealMap[item.id] = {
        sealId: item.sealId,
        sealName: item.sealName,
        pictureUrl: preservedPictureMap[item.id] || '',
      }
    }
  })
}

function clearQrState() {
  qrVisible.value = false
  qrCode.value = ''
  qrRequestId.value = ''
  qrExpiresIn.value = 300
  qrCount.value = 300
  stopQrCountdown()
  stopPolling()
}

function markLocalBoxesAsSigning(boxes, requestId) {
  boxes.forEach((box) => {
    const localBox = pageState.positions.find((item) => item.id === box.id)
    if (!localBox) return
    localBox.signedStatus = STATUS_SIGNING
    localBox.requestId = requestId || ''
    localBox.sealId = selectedSealMap[box.id]?.sealId || box.sealId || ''
    localBox.sealName = selectedSealMap[box.id]?.sealName || box.sealName || ''
    localBox.signedByName = ''
    localBox.signedById = ''
    localBox.signedTime = null
  })
}

async function refreshPageStateAfterCallback(requestId) {
  if (!requestId) {
    await loadPageState(false)
    return
  }
  for (let attempt = 0; attempt < 4; attempt += 1) {
    await loadPageState(false)
    const stillSigning = pageState.positions.some((item) => item.requestId === requestId && Number(item.signedStatus) === STATUS_SIGNING)
    if (!stillSigning) {
      return
    }
    await wait(250)
  }
}

function resetState() {
  loadingPage.value = false
  fetchingSeals.value = false
  preSigning.value = false
  checkingComplete.value = false
  mockingCallback.value = false
  sealList.value = []
  selectedPartyId.value = null
  focusedBoxId.value = null
  currentPageNum.value = 1
  pageDisplayWidth.value = 760
  pageState.sealContractId = ''
  pageState.pdfToImageList = []
  pageState.positions = []
  pageState.availablePartyIds = []
  pageState.preferredPartyId = null
  pageState.imageWidth = DEFAULT_IMAGE_WIDTH
  pageState.imageHeight = DEFAULT_IMAGE_HEIGHT
  currentSignerId.value = ''
  dragSealData = null
  clearQrState()
  resetSelectionMap()
  Object.keys(pageRefs).forEach((key) => delete pageRefs[key])
}

function refreshPreviewSize() {
  nextTick(() => {
    const paneWidth = previewPaneRef.value?.clientWidth || 980
    const availableWidth = Math.max(360, paneWidth - 48)
    pageDisplayWidth.value = Math.min(imageWidth.value, availableWidth)
  })
}

function hydratePageState(page) {
  pageState.sealContractId = page?.sealContractId || ''
  pageState.pdfToImageList = Array.isArray(page?.pdfToImageList) ? page.pdfToImageList : []
  pageState.positions = Array.isArray(page?.positions)
    ? page.positions.map((item) => ({
        ...item,
        pageNum: Number(item.pageNum || 1),
        x: roundValue(item.x),
        y: roundValue(item.y),
        width: roundValue(item.width),
        height: roundValue(item.height),
        signedStatus: Number(item.signedStatus ?? STATUS_EMPTY),
        required: item.required === 'N' ? 'N' : 'Y',
      }))
    : []
  pageState.availablePartyIds = Array.isArray(page?.availablePartyIds) ? page.availablePartyIds : []
  pageState.preferredPartyId = page?.preferredPartyId ?? null
  pageState.imageWidth = Number(page?.imageWidth || DEFAULT_IMAGE_WIDTH)
  pageState.imageHeight = Number(page?.imageHeight || DEFAULT_IMAGE_HEIGHT)
  const selectablePartyIds = pageState.availablePartyIds.length
    ? [...new Set(pageState.availablePartyIds.map((item) => Number(item)).filter((item) => !Number.isNaN(item)))]
    : []
  if (!selectedPartyId.value) {
    selectedPartyId.value = pageState.preferredPartyId || selectablePartyIds[0] || null
  } else if (
    selectedPartyId.value
    && !selectablePartyIds.some((item) => Number(item) === Number(selectedPartyId.value))
  ) {
    selectedPartyId.value = pageState.preferredPartyId || selectablePartyIds[0] || null
  }
  hydrateSelectedSealMap()
}

async function loadPageState(showMessage = false) {
  if (!contractDetail.value?.id) return
  loadingPage.value = true
  try {
    const res = await getLaunchSealPositionPage(contractDetail.value.id)
    if (!isSuccessResponse(res) || !getResult(res)) {
      message.error(res?.message || res?.msg || '获取签章位失败')
      return
    }
    hydratePageState(getResult(res))
    refreshPreviewSize()
    if (!focusedBoxId.value && myBoxes.value.length) {
      focusBox(myBoxes.value[0].id, myBoxes.value[0].pageNum)
    }
    if (showMessage) {
      message.success('签署状态已刷新')
    }
  } catch (error) {
    message.error(error?.message || '获取签章位失败')
  } finally {
    loadingPage.value = false
  }
}

function focusBox(boxId, pageNum) {
  focusedBoxId.value = boxId
  currentPageNum.value = Number(pageNum || 1)
  nextTick(() => {
    const pageRef = pageRefs[pageNum]
    pageRef?.scrollIntoView?.({ behavior: 'smooth', block: 'center' })
  })
}

function handlePartyChanged() {
  const firstBox = myBoxes.value[0] || null
  if (firstBox) {
    focusBox(firstBox.id, firstBox.pageNum)
  }
}

function handleSealDragStart(event, seal) {
  dragSealData = {
    sealId: seal.sealId,
    sealName: seal.sealName,
    pictureUrl: seal.pictureUrl,
  }
  event.dataTransfer.effectAllowed = 'copy'
  event.dataTransfer.setData('text/plain', seal.sealId || 'seal')
}

function handleBoxDragOver(box) {
  if (!isBoxMine(box) || Number(box.signedStatus) === STATUS_SIGNED) {
    return
  }
}

async function handleDropSealOnBox(event, box) {
  event.preventDefault()
  if (!dragSealData) return
  if (!isBoxMine(box)) {
    message.warning('只能给当前签署方的签章位放章')
    dragSealData = null
    return
  }
  if (Number(box.signedStatus) === STATUS_SIGNING) {
    message.warning('该签章位正在等待扫码确认，暂时不能更换印章')
    dragSealData = null
    return
  }
  if (Number(box.signedStatus) === STATUS_SIGNED) {
    message.warning('该签章位已签署，不能重复放章')
    dragSealData = null
    return
  }
  selectedSealMap[box.id] = { ...dragSealData }
  box.sealId = dragSealData.sealId
  box.sealName = dragSealData.sealName
  box.signedStatus = STATUS_SELECTED
  focusBox(box.id, box.pageNum)
  dragSealData = null
  try {
    await batchUpdateLaunchSealPositionStatus(contractDetail.value.id, {
      partyId: selectedPartyId.value,
      signedStatus: STATUS_SELECTED,
      items: [{ id: box.id, sealId: box.sealId, sealName: box.sealName }],
    })
  } catch {
    // 请求失败时保留前端选择态，方便用户重试提交
  }
}

function clearBoxSelection(box) {
  if (Number(box.signedStatus) === STATUS_SIGNED) {
    message.warning('已签署的签章位不能清空')
    return
  }
  if (Number(box.signedStatus) === STATUS_SIGNING) {
    message.warning('该签章位正在等待扫码确认，请先关闭二维码或等待回调完成')
    return
  }
  delete selectedSealMap[box.id]
  box.sealId = null
  box.sealName = null
  box.signedStatus = STATUS_EMPTY
  batchUpdateLaunchSealPositionStatus(contractDetail.value.id, {
    partyId: selectedPartyId.value,
    signedStatus: STATUS_EMPTY,
    items: [{ id: box.id }],
  }).catch(() => {})
}

function isBoxMissing(box) {
  return Number(box.partyId) === Number(selectedPartyId.value)
    && box.required === 'Y'
    && ![STATUS_SELECTED, STATUS_SIGNING, STATUS_SIGNED].includes(Number(box.signedStatus))
    && !selectedSealMap[box.id]
}

function isRequiredBoxUnfinished(box) {
  return Number(box.partyId) === Number(selectedPartyId.value)
    && box.required === 'Y'
    && Number(box.signedStatus) !== STATUS_SIGNED
}

async function fetchSeals() {
  if (!queryName.value || !queryMobile.value) {
    message.warning('请先确认测试签署人信息')
    return
  }
  fetchingSeals.value = true
  try {
    const signerRes = await getUserUniqueId({
      name: queryName.value,
      mobile: queryMobile.value,
    })
    if (!isSuccessResponse(signerRes) || !getResult(signerRes)?.signerId) {
      message.error(signerRes?.message || signerRes?.msg || '获取签署人身份失败')
      return
    }
    currentSignerId.value = getResult(signerRes).signerId
    const sealRes = await getSealBySignerId({
      signerId: currentSignerId.value,
      sealTypes: querySealTypes.value,
      entName: queryEntName.value,
    })
    if (!isSuccessResponse(sealRes)) {
      message.error(sealRes?.message || sealRes?.msg || '获取印章失败')
      return
    }
    const result = getResult(sealRes)
    sealList.value = Array.isArray(result) ? result : []
    if (sealList.value.length) {
      message.success(`成功获取 ${sealList.value.length} 个印章`)
    } else {
      message.warning('未获取到可用印章')
    }
  } catch (error) {
    message.error(error?.message || '获取印章失败')
  } finally {
    fetchingSeals.value = false
  }
}

function validateBeforePresign() {
  if (!selectedPartyId.value) {
    return '请先选择当前代表的签署方'
  }
  if (!myBoxes.value.length) {
    return '当前签署方没有可签章位'
  }
  const signingBox = myBoxes.value.find((item) => Number(item.signedStatus) === STATUS_SIGNING)
  if (signingBox) {
    focusBox(signingBox.id, signingBox.pageNum)
    return '当前还有待扫码确认的签章位，请先完成或关闭当前二维码流程'
  }
  const firstMissing = myBoxes.value.find((item) => isBoxMissing(item))
  if (firstMissing) {
    focusBox(firstMissing.id, firstMissing.pageNum)
    return '当前签署方还有必签位置未放章，请先完成所有必签框'
  }
  if (!myPendingSubmitBoxes.value.length) {
    return '请先拖动印章到待签框'
  }
  if (!pageState.sealContractId) {
    return '签章平台合同ID为空，请先刷新签署文件'
  }
  return null
}

function buildSealConfigs() {
  return myPendingSubmitBoxes.value.map((box) => ({
    sealId: selectedSealMap[box.id]?.sealId || box.sealId,
    x: getSealPlacement(box).x,
    y: getSealPlacement(box).y,
    scale: getSealPlacement(box).scale,
    pageNum: Number(box.pageNum || 1),
  }))
}

function startQrCountdown() {
  stopQrCountdown()
  qrCount.value = qrExpiresIn.value || 300
  qrTimer = setInterval(() => {
    qrCount.value -= 1
    if (qrCount.value <= 0) {
      stopQrCountdown()
      if (qrVisible.value) {
        handleCloseQr()
      }
    }
  }, 1000)
}

function stopQrCountdown() {
  if (qrTimer) {
    clearInterval(qrTimer)
    qrTimer = null
  }
}

function stopPolling() {
  if (pollTimer) {
    clearInterval(pollTimer)
    pollTimer = null
  }
}

function startPolling(requestId) {
  stopPolling()
  const maxAttempts = Math.ceil((qrExpiresIn.value || 300) / 2) + 10
  let attempts = 0
  pollTimer = setInterval(async () => {
    attempts += 1
    if (attempts > maxAttempts || !qrVisible.value) {
      stopPolling()
      return
    }
    try {
      const res = await pollCallback(requestId)
      if (isSuccessResponse(res) && getResult(res)) {
        stopPolling()
        await handlePresignCallbackResult(getResult(res), requestId)
      }
    } catch {
      // ignore poll errors
    }
  }, 2000)
}

function isPresignFailed(data) {
  const contractStatus = String(data?.contractStatus || '').trim()
  if (contractStatus) {
    if (contractStatus === '20' || contractStatus === '2000') {
      return false
    }
    if (contractStatus === '0' || contractStatus.startsWith('-')) {
      return true
    }
  }
  const successValue = data?.isSuccess ?? data?.success
  if (successValue !== undefined && successValue !== null) {
    return String(successValue).toLowerCase() !== 'true'
  }
  const statusText = `${data?.status || ''} ${data?.msg || ''}`.toLowerCase()
  return ['fail', 'cancel', 'timeout', 'reject'].some((keyword) => statusText.includes(keyword))
}

async function handlePreSignSubmit() {
  const validationMessage = validateBeforePresign()
  if (validationMessage) {
    message.warning(validationMessage)
    return
  }
  preSigning.value = true
  try {
    const submitBoxes = [...myPendingSubmitBoxes.value]
    const sealConfigs = buildSealConfigs()
    const res = await getPreSignQrCode({
      contractId: pageState.sealContractId,
      notifyUrl: `${CALLBACK_HOST}/seal/callback/presign`,
      isNeedUrlLink: true,
      sealConfigs,
      pagingSealConfigs: null,
      timestampSealConfigs: null,
    })
    if (!isSuccessResponse(res) || !getResult(res)) {
      message.error(res?.message || res?.msg || '获取二维码失败')
      return
    }
    const data = getResult(res)

    await batchUpdateLaunchSealPositionStatus(contractDetail.value.id, {
      partyId: selectedPartyId.value,
      signedStatus: STATUS_SIGNING,
      requestId: data.requestId || '',
      items: submitBoxes.map((box) => ({
        id: box.id,
        sealId: selectedSealMap[box.id]?.sealId || box.sealId,
        sealName: selectedSealMap[box.id]?.sealName || box.sealName,
      })),
    })
    markLocalBoxesAsSigning(submitBoxes, data.requestId || '')
    qrCode.value = data.qrCode || ''
    qrRequestId.value = data.requestId || ''
    qrExpiresIn.value = Number(data.expiresIn || 300)
    qrVisible.value = true
    startQrCountdown()
    startPolling(qrRequestId.value)
    await loadPageState(false)
  } catch (error) {
    message.error(error?.message || '提交预盖章失败')
  } finally {
    preSigning.value = false
  }
}

async function handlePresignCallbackResult(data, requestId = '') {
  const currentRequestId = requestId || qrRequestId.value || String(data?.requestId || '')
  clearQrState()
  await refreshPageStateAfterCallback(currentRequestId)
  if (isPresignFailed(data)) {
    message.warning(data?.msg || '预盖章未完成，签章位已恢复为可重试状态')
    return
  }
  const nextUnfinishedBox = myBoxes.value.find((item) => isRequiredBoxUnfinished(item))
  if (nextUnfinishedBox) {
    focusBox(nextUnfinishedBox.id, nextUnfinishedBox.pageNum)
  }
  message.success(`预盖章确认成功，签署人：${data?.signer || queryName.value || '未知'}`)
}

async function handleMockSuccess() {
  if (!qrRequestId.value || !pageState.sealContractId) {
    message.warning('当前没有待模拟的扫码请求')
    return
  }
  stopPolling()
  mockingCallback.value = true
  try {
    const res = await mockPreSignSuccessCallback({
      requestId: qrRequestId.value,
      contractId: pageState.sealContractId,
      signer: queryName.value,
      signerId: currentSignerId.value || '',
      contractStatus: 20,
    })
    if (!isSuccessResponse(res)) {
      message.error(res?.message || res?.msg || '模拟扫码成功失败')
      return
    }
    await handlePresignCallbackResult({ signer: queryName.value, contractStatus: 20, requestId: qrRequestId.value }, qrRequestId.value)
  } catch (error) {
    message.error(error?.message || '模拟扫码成功失败')
  } finally {
    mockingCallback.value = false
  }
}

async function handleCloseQr() {
  const currentRequestId = qrRequestId.value
  const rollbackItems = pageState.positions
    .filter((item) => Number(item.partyId) === Number(selectedPartyId.value))
    .filter((item) => Number(item.signedStatus) === STATUS_SIGNING && item.requestId === currentRequestId)
    .map((item) => ({ id: item.id, sealId: item.sealId, sealName: item.sealName }))
  clearQrState()
  if (!rollbackItems.length) {
    return
  }
  try {
    await batchUpdateLaunchSealPositionStatus(contractDetail.value.id, {
      partyId: selectedPartyId.value,
      signedStatus: STATUS_SELECTED,
      items: rollbackItems,
    })
    await loadPageState(false)
    message.info('已关闭扫码窗口，本次预盖章已恢复为待提交状态')
  } catch {
    // ignore rollback failures in UI close action
  }
}

function buildSignedFileInfo(downloadData = {}) {
  const signedUrl = downloadData.downloadUrl || downloadData.url || ''
  const signedName = downloadData.fileName || `${contractDetail.value?.contractName || '已签署合同'}.pdf`
  return {
    signedFileName: signedName,
    signedFilePath: signedUrl || '',
    signedFileType: signedName.split('.').pop()?.toLowerCase() || 'pdf',
    signedFileSize: downloadData.fileSize || downloadData.size || null,
  }
}

function todayString() {
  const now = new Date()
  const month = String(now.getMonth() + 1).padStart(2, '0')
  const day = String(now.getDate()).padStart(2, '0')
  return `${now.getFullYear()}-${month}-${day}`
}

function formatDateTimeValue(value) {
  return value ? `${value} 00:00:00` : null
}

async function writeBackLaunchSealFinished() {
  let signedFileInfo = {}
  try {
    const downloadRes = await downloadContract(pageState.sealContractId)
    if (isSuccessResponse(downloadRes) && getResult(downloadRes)) {
      signedFileInfo = buildSignedFileInfo(getResult(downloadRes))
    }
  } catch {
    // ignore download errors here
  }
  const res = await finishLaunchSeal(contractDetail.value.id, {
    sealContractId: pageState.sealContractId,
    sealContractStatus: 2000,
    signDate: formatDateTimeValue(todayString()),
    sealDate: formatDateTimeValue(todayString()),
    activeDate: formatDateTimeValue(todayString()),
    ...signedFileInfo,
  })
  if (!isSuccessResponse(res) || !getResult(res)) {
    throw new Error(res?.message || res?.msg || '回写签署结果失败')
  }
  emit('done', getResult(res))
}

async function handleCompleteSigning() {
  if (!selectedPartyId.value) {
    message.warning('请先选择当前代表的签署方')
    return
  }
  checkingComplete.value = true
  try {
    const res = await checkLaunchSealPositions(contractDetail.value.id, {})
    if (!isSuccessResponse(res) || !getResult(res)) {
      message.error(res?.message || res?.msg || '校验签章位失败')
      return
    }
    const result = getResult(res)
    if (!result.passed) {
      const firstMissing = Array.isArray(result.missingPositions) ? result.missingPositions[0] : null
      if (firstMissing) {
        focusBox(firstMissing.id, firstMissing.pageNum)
      }
      message.warning(`还有 ${result.missingCount || 0} 个必签位置未完成签署，请先处理后再点签署完成`)
      return
    }
    const completeRes = await completeContract(pageState.sealContractId)
    if (!isSuccessResponse(completeRes)) {
      message.error(completeRes?.message || completeRes?.msg || '完成签署失败')
      return
    }
    await writeBackLaunchSealFinished()
    await loadPageState(false)
    message.success('合同签署已完成')
    visible.value = false
  } catch (error) {
    message.error(error?.message || '完成签署失败')
  } finally {
    checkingComplete.value = false
  }
}

async function showModal(detail) {
  resetState()
  contractDetail.value = detail ? { ...detail } : null
  visible.value = true
  await nextTick()
  refreshPreviewSize()
  await loadPageState(false)
}

function handleWindowResize() {
  if (visible.value) {
    refreshPreviewSize()
  }
}

onMounted(() => {
  window.addEventListener('resize', handleWindowResize)
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', handleWindowResize)
  clearQrState()
})

defineExpose({ showModal })
</script>

<style scoped>
.seal-position-sign-layout {
  height: 100%;
  display: flex;
  background: #f4f7fb;
}

.preview-pane {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  border-right: 1px solid #e5e7eb;
}

.preview-toolbar {
  padding: 14px 18px;
  background: #fff;
  border-bottom: 1px solid #e5e7eb;
}

.preview-scroll {
  flex: 1 1 auto;
  overflow: auto;
  padding: 18px;
}

.pdf-page-card {
  margin: 0 auto 18px;
  padding: 14px;
  border-radius: 18px;
  border: 1px solid #dbe4f0;
  background: #ffffff;
  width: fit-content;
  box-shadow: 0 12px 28px rgba(15, 23, 42, 0.06);
}

.pdf-page-card.is-active {
  border-color: #60a5fa;
  box-shadow: 0 16px 30px rgba(37, 99, 235, 0.12);
}

.page-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
  font-size: 13px;
  color: #475569;
  font-weight: 600;
}

.pdf-page-shell {
  position: relative;
  overflow: hidden;
  border-radius: 12px;
  background: #f8fafc;
}

.pdf-page-image {
  display: block;
  user-select: none;
}

.seal-position-box {
  position: absolute;
  box-sizing: border-box;
  border-radius: 12px;
  padding: 8px;
  overflow: hidden;
  border: 2px dashed #94a3b8;
  background: rgba(148, 163, 184, 0.12);
  color: #334155;
  transition: all 0.2s ease;
}

.seal-position-box.is-mine {
  border-color: #2563eb;
  background: rgba(37, 99, 235, 0.12);
  color: #1d4ed8;
}

.seal-position-box.is-other {
  border-color: #cbd5e1;
  background: rgba(203, 213, 225, 0.25);
}

.seal-position-box.is-selected-box {
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.14);
}

.seal-position-box.is-missing {
  border-color: #ef4444;
  background: rgba(239, 68, 68, 0.14);
  color: #b91c1c;
}

.seal-position-box.is-signed {
  border-style: solid;
}

.box-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.box-title {
  font-size: 12px;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.box-party,
.box-status,
.box-seal-name,
.box-signed-by {
  margin-top: 4px;
  font-size: 11px;
  line-height: 1.4;
}

.box-seal-preview {
  position: absolute;
  pointer-events: none;
  opacity: 0.92;
}

.box-seal-image {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.box-focus-ring {
  position: absolute;
  inset: 0;
  border-radius: 10px;
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.85);
}

.operation-pane {
  width: 380px;
  flex: 0 0 380px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  background: linear-gradient(180deg, #fbfdff 0%, #f5f8fc 100%);
}

.side-card {
  border-radius: 16px;
  border: 1px solid #e2e8f0;
  background: rgba(255, 255, 255, 0.96);
  padding: 14px;
}

.side-card-grow {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.side-card-title {
  margin-bottom: 12px;
  font-size: 14px;
  font-weight: 700;
  color: #0f172a;
}

.meta-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
  font-size: 13px;
  color: #475569;
}

.meta-item strong {
  flex: 1;
  text-align: right;
  color: #0f172a;
  word-break: break-word;
}

.meta-note {
  margin-top: 10px;
  padding: 10px 12px;
  border-radius: 12px;
  background: #eff6ff;
  font-size: 12px;
  line-height: 1.6;
  color: #1d4ed8;
}

.party-selector-item {
  margin-top: 10px;
  margin-bottom: 0;
}

.query-grid :deep(.ant-form-item) {
  margin-bottom: 10px;
}

.seal-type-wrap {
  margin: 8px 0 12px;
}

.seal-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.seal-card {
  border: 1px solid #dbe4f0;
  border-radius: 14px;
  padding: 10px;
  background: #fff;
  cursor: grab;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.seal-card:active {
  cursor: grabbing;
}

.seal-thumb {
  max-width: 100%;
  height: 72px;
  object-fit: contain;
}

.seal-name {
  font-size: 12px;
  text-align: center;
  color: #334155;
  word-break: break-word;
}

.pending-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 150px;
  overflow: auto;
}

.pending-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid #dbe4f0;
  cursor: pointer;
}

.pending-title {
  font-size: 13px;
  font-weight: 700;
  color: #0f172a;
}

.pending-sub {
  margin-top: 4px;
  font-size: 12px;
  color: #64748b;
}

.footer-actions {
  margin-top: auto;
  position: sticky;
  bottom: 0;
  padding-top: 12px;
  padding-bottom: 4px;
  background: linear-gradient(180deg, rgba(245, 248, 252, 0) 0%, rgba(245, 248, 252, 0.96) 24%, rgba(245, 248, 252, 1) 100%);
}

.qr-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 12px 0 4px;
}

.qr-image {
  width: 260px;
  height: 260px;
  object-fit: contain;
  border-radius: 18px;
  border: 1px solid #e2e8f0;
  background: #fff;
}

.qr-meta {
  font-size: 13px;
  color: #475569;
}

.empty-preview {
  margin-top: 48px;
}

@media (max-width: 1100px) {
  .seal-position-sign-layout {
    flex-direction: column;
  }

  .operation-pane {
    width: 100%;
    flex-basis: auto;
  }
}
</style>
