<template>
  <a-modal
    v-model:open="visible"
    :title="modalTitle"
    width="92vw"
    :footer="null"
    :mask-closable="false"
    :style="{ top: '24px' }"
    :body-style="{
      padding: 0,
      height: 'calc(100vh - 160px)',
      overflow: 'hidden',
    }"
  >
    <div class="contract-template-layout">
      <section class="config-panel">
        <div class="panel-header">
          <h3>合同正文配置</h3>
        </div>

        <a-form
          ref="formRef"
          layout="vertical"
          :model="contractDetailState"
          :disabled="isViewMode"
          class="config-form"
        >
          <a-card size="small" title="创建方式与模板选择" class="panel-card">
            <a-form-item label="创建方式">
              <a-radio-group v-model:value="creationMode" button-style="solid">
                <a-radio-button
                  v-for="item in contractCreateModeOptions"
                  :key="item.value"
                  :value="item.value"
                >
                  {{ item.label }}
                </a-radio-button>
              </a-radio-group>
            </a-form-item>

            <a-form-item
              v-if="creationMode === 'template'"
              label="合同模板"
              required
            >
              <a-select
                v-model:value="templateKey"
                :options="templateSelectOptions"
                placeholder="请选择已发布模板"
              />
            </a-form-item>

            <div v-if="creationMode === 'template'" class="template-tip">
              <div class="template-tip-title">
                {{ activeTemplate?.templateName || "未选择模板" }}
              </div>
              <div class="template-tip-desc">
                {{ activeTemplate?.description || "请选择已发布模板" }}
              </div>
            </div>
          </a-card>

          <a-card
            v-if="creationMode === 'template'"
            size="small"
            title="模板信息录入"
            class="panel-card"
          >
            <div class="grid-two">
              <a-form-item
                v-for="field in launchFieldList"
                :key="field.key"
                :label="field.label"
                :required="!field.readonly"
              >
                <a-input
                  :value="getLaunchFieldValue(field.key)"
                  :placeholder="`请输入${field.label}`"
                  :readonly="field.readonly"
                  @update:value="setLaunchFieldValue(field.key, $event)"
                />
              </a-form-item>
            </div>
          </a-card>
          <a-card
            v-if="creationMode === 'manual'"
            size="small"
            title="合同正文上传"
            class="panel-card"
          >
            <a-upload-dragger
              :file-list="manualBodyFileList"
              accept=".docx"
              :before-upload="handleManualContractBodyUpload"
              :max-count="1"
              :multiple="false"
              :disabled="isViewMode"
              :show-upload-list="{ showRemoveIcon: !isViewMode }"
              :remove="handleRemoveManualContractBody"
            >
              <p class="ant-upload-drag-icon">
                <FileWordOutlined />
              </p>
              <p class="ant-upload-text">上传合同正文 Word 文件</p>
              <p class="ant-upload-hint">
                手动创建模式下，右侧将直接渲染当前上传的 `.docx` 文件。
              </p>
            </a-upload-dragger>
          </a-card>
        </a-form>

        <div class="panel-footer">
          <a-space wrap>
            <a-button @click="visible = false">
              {{ isViewMode ? "关闭" : "取消" }}
            </a-button>
            <a-button v-if="!isViewMode" @click="resetForm">
              <ReloadOutlined />
              重置
            </a-button>
            <a-button @click="refreshPreview" :loading="rendering">
              <SettingOutlined />
              刷新预览
            </a-button>
            <a-button
              type="primary"
              @click="exportFilledDocument"
              :loading="exporting"
            >
              <FileWordOutlined />
              导出 Word
            </a-button>
            <a-button
              v-if="!isViewMode"
              type="primary"
              @click="handleSave"
            >
              保存
            </a-button>
          </a-space>
        </div>
      </section>

      <section class="preview-panel">
        <div class="preview-header">
          <div>
            <h3>Word 渲染预览</h3>
            <p>{{ previewDescription }}</p>
          </div>
          <a-space size="small">
            <a-tag :color="creationMode === 'manual' ? 'orange' : 'geekblue'">
              {{
                creationMode === "manual"
                  ? "手动创建"
                  : activeTemplate?.templateName || "未选择模板"
              }}
            </a-tag>
            <a-tag color="green">可导出</a-tag>
          </a-space>
        </div>

        <div class="preview-shell">
          <div ref="previewRef" class="preview-stage"></div>
          <div v-if="!hasPreviewSource && !rendering" class="preview-state">
            <FileWordOutlined style="font-size: 40px; color: #94a3b8" />
            <p>{{ emptyPreviewText }}</p>
          </div>
          <div v-if="rendering" class="preview-state preview-mask">
            <a-spin size="large" />
            <p>正在生成并渲染 Word 文档...</p>
          </div>
          <div v-else-if="previewError" class="preview-state preview-error">
            <h4>文档预览失败</h4>
            <p>{{ previewError }}</p>
          </div>
        </div>
      </section>
    </div>

  </a-modal>
</template>

<script setup>
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
import Docxtemplater from "docxtemplater";
import dayjs from "dayjs";
import {
  FileWordOutlined,
  ReloadOutlined,
  SettingOutlined,
} from "@ant-design/icons-vue";
import { message } from "ant-design-vue";
import PizZip from "pizzip";
import { renderAsync } from "docx-preview";
import { saveAs } from "file-saver";
import {
  downloadTemplateFile,
  queryPublishedManifestList,
} from "@/api/manifest.js";
import { uploadLaunchFile } from "@/api/launch.js";
import {
  contractSourceOptions,
  contractTypeOptions,
  currencyOptions,
  dateTypeOptions,
  extraAmountFieldOptions,
  getOptionLabel,
  isElectronOptions,
  paymentDirectionOptions,
  sealTypeOptions,
  textSourceOptions,
  valuationModeOptions,
} from "../contractLaunchOptions.js";

// ========================================================================
// 以下为表单模型 + 字典 + 工具函数，直接内联在此文件中。
// 接入后端时，可保留这些函数，将入参来源/出参提交替换为真实接口即可。
// ========================================================================

// ========== 创建方式选项 ==========
const contractCreateModeOptions = [
  { label: '模板创建', value: 'template' },
  { label: '手动创建', value: 'manual' },
]

// ========== 可绑定业务字段列表（用于映射下拉和展示） ==========
const templateFieldList = [
  { value: 'contractName', label: '合同名称' },
  { value: 'contractCode', label: '合同编码' },
  { value: 'contractType', label: '合同类型' },
  { value: 'contractTypeCode', label: '合同类型编码' },
  { value: 'contractAmountDisplay', label: '合同金额' },
  { value: 'contractAmount', label: '合同金额数字' },
  { value: 'effectiveStart', label: '有效期起' },
  { value: 'effectiveEnd', label: '有效期止' },
  { value: 'effectivePeriod', label: '有效期区间' },
  { value: 'contractPeriodTypeLabel', label: '合同期限类型' },
  { value: 'periodExplain', label: '期限说明' },
  { value: 'paymentMethod', label: '付款方式' },
  { value: 'paymentDirectionLabel', label: '收支方向' },
  { value: 'valuationModeLabel', label: '计价方式' },
  { value: 'currencyNameLabel', label: '币种' },
  { value: 'sealTypeLabel', label: '签订方式' },
  { value: 'textSourceLabel', label: '文本来源' },
  { value: 'isElectronLabel', label: '签署方式' },
  { value: 'contractSourceLabel', label: '合同来源' },
  { value: 'dateTypeLabel', label: '固定期限日期类型' },
  { value: 'mandateType', label: '委托类型' },
  { value: 'cargoType', label: '货种' },
  { value: 'primaryContent', label: '合同标的说明' },
  { value: 'amountExplain', label: '金额说明' },
  { value: 'contractGistRemark', label: '签订依据说明' },
  { value: 'bodySummary', label: '正文摘要' },
  { value: 'assistDept', label: '协助部门' },
  { value: 'executorAccount', label: '合同执行人' },
  { value: 'selfCode', label: '二级单位合同编码' },
  { value: 'mainContractCode', label: '主合同编码' },
  { value: 'signingSubjectCode', label: '签约主体编码' },
  { value: 'invoiceDate', label: '开票时间' },
  { value: 'receiveDate', label: '收款时间' },
  { value: 'partyAName', label: '甲方名称' },
  { value: 'partyBName', label: '乙方名称' },
  { value: 'partyCName', label: '丙方名称' },
  { value: 'partyAContactName', label: '甲方联系人' },
  { value: 'partyBContactName', label: '乙方联系人' },
  { value: 'partyCContactName', label: '丙方联系人' },
  { value: 'partyAContactPhone', label: '甲方电话' },
  { value: 'partyBContactPhone', label: '乙方电话' },
  { value: 'partyCContactPhone', label: '丙方电话' },
  { value: 'partyAAddress', label: '甲方地址' },
  { value: 'partyBAddress', label: '乙方地址' },
  { value: 'partyCAddress', label: '丙方地址' },
  { value: 'partyACreditCode', label: '甲方统一社会信用代码' },
  { value: 'partyBCreditCode', label: '乙方统一社会信用代码' },
  { value: 'partyCCreditCode', label: '丙方统一社会信用代码' },
  { value: 'partyABankName', label: '甲方开户行' },
  { value: 'partyBBankName', label: '乙方开户行' },
  { value: 'partyCBankName', label: '丙方开户行' },
  { value: 'partyABankAccount', label: '甲方银行账号' },
  { value: 'partyBBankAccount', label: '乙方银行账号' },
  { value: 'partyCBankAccount', label: '丙方银行账号' },
  { value: 'counterpartySummary', label: '合同方摘要' },
  { value: 'attachmentSummary', label: '附件情况' },
  { value: 'contractGistFileSummary', label: '签订依据附件' },
  { value: 'contractBodyFileName', label: '合同正文文件名' },
  { value: 'extraAmountSummary', label: '额外金额' },
  { value: 'performancePlanSummary', label: '履行计划' },
  { value: 'templateLabel', label: '模板名称' },
  { value: 'signDate', label: '签署日期' },
]

// ========== 格式化合同金额（如 368,000.00 元） ==========
function formatContractAmount(value) {
  if (value === undefined || value === null || value === '') return ''
  return `${new Intl.NumberFormat('zh-CN', {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }).format(Number(value))} 元`
}

function getContractPeriodTypeLabel(value) {
  if (value === '1') return '无固定期限'
  return '固定期限'
}

function normalizeFileItem(item = {}) {
  return {
    uid: item.uid || `file_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`,
    name: item.name || item.fileName || '',
    size: item.size ?? item.fileSize ?? 0,
    status: item.status || 'done',
    filePath: item.filePath || '',
    fileType: item.fileType || '',
    fileCategory: item.fileCategory || '',
  }
}

function splitFilesByCategory(source = {}) {
  const sourceFiles = source.fileList || []
  const bodyFile = sourceFiles.find((item) => item.fileCategory === 'body') || {}
  return {
    contractBodyFileName:
      source.contractBodyFileName ||
      source.contractTextFileName ||
      bodyFile.fileName ||
      '',
    contractBodyFilePath:
      source.contractBodyFilePath ||
      source.contractTextFilePath ||
      bodyFile.filePath ||
      '',
    contractBodyFileType:
      source.contractBodyFileType ||
      source.contractTextFileType ||
      bodyFile.fileType ||
      '',
    contractBodyFileSize:
      source.contractBodyFileSize ??
      source.contractTextFileSize ??
      bodyFile.fileSize ??
      null,
    otherAttachmentList: source.otherAttachmentList?.length
      ? source.otherAttachmentList.map(normalizeFileItem)
      : sourceFiles
          .filter((item) => item.fileCategory === 'other')
          .map(normalizeFileItem),
    contractGistFileList: source.contractGistFileList?.length
      ? source.contractGistFileList.map(normalizeFileItem)
      : sourceFiles
          .filter((item) => item.fileCategory === 'gist')
          .map(normalizeFileItem),
  }
}

function formatFileSummary(fileList = []) {
  return fileList.length
    ? fileList.map((item) => item.name || item.fileName).filter(Boolean).join('；')
    : '暂无附件'
}

function buildExtraAmountSummary(extraAmounts = {}) {
  const parts = extraAmountFieldOptions
    .filter((item) => extraAmounts[`${item.key}Enabled`])
    .map((item) => `${item.label}${formatContractAmount(extraAmounts[item.key]) || '0.00 元'}`)
  return parts.length ? parts.join('；') : '无'
}

function restoreExtraAmounts(source = {}, current = {}) {
  const result = { ...current, ...(source.extraAmounts || {}) }
  const selectedCodes = String(source.ewAmountType || '')
    .split(',')
    .map((item) => item.trim())
    .filter(Boolean)

  extraAmountFieldOptions.forEach((item) => {
    const amount = source[item.amountField]
    if (selectedCodes.includes(item.code) || amount || amount === 0) {
      result[`${item.key}Enabled`] = selectedCodes.includes(item.code) || amount > 0
      if (amount !== undefined && amount !== null) {
        result[item.key] = amount
      }
    }
  })

  return result
}

function buildPerformancePlanSummary(list = []) {
  if (!list.length) return '暂无履行计划'
  return list
    .map((item, index) => {
      const money = item.planMoney || item.planMoney === 0 ? `，金额${formatContractAmount(item.planMoney)}` : ''
      return `${index + 1}. ${item.planMatter || '未填写事项'}，${item.planWhere || '未填写条件'}，${item.planTime || '未填写日期'}${money}`
    })
    .join('\n')
}

// ========== 合同方相关函数 ==========

/** 深拷贝合同方状态 */
function copyPartyState(input) {
  const source = input || {}
  const listState = buildPartyStateFromList(source.partyList)
  return {
    isThreePartyContract: Boolean(source.isThreePartyContract),
    partyA: { roleKey: 'A', roleName: '甲方', companyName: '', ...(source.partyA || source.parties?.A || {}), ...(listState?.partyA || {}) },
    partyB: { roleKey: 'B', roleName: '乙方', companyName: '', ...(source.partyB || source.parties?.B || {}), ...(listState?.partyB || {}) },
    partyC: { roleKey: 'C', roleName: '丙方', companyName: '', ...(source.partyC || source.parties?.C || {}), ...(listState?.partyC || {}) },
    ...(listState ? { isThreePartyContract: listState.isThreePartyContract } : {}),
  }
}

function mapLaunchPartyToFormParty(item = {}) {
  return {
    roleKey: item.roleCode || 'B',
    roleName: item.roleCode === 'A' ? '甲方' : item.roleCode === 'C' ? '丙方' : '乙方',
    partnerId: item.partnerId || null,
    oppositeId: item.oppositeId || '',
    companyName: item.companyName || '',
    companyType: item.companyType || '',
    oppCharacter: item.oppositeCharacter || '',
    contactName: item.contactName || '',
    contactPhone: item.contactPhone || '',
    contactEmail: item.contactEmail || '',
    contactAddress: item.contactAddress || '',
    registeredAddress: item.registeredAddress || '',
    legalRepresentative: item.legalRepresentative || '',
    creditCode: item.creditCode || '',
    bankName: item.bankName || '',
    bankAccount: item.bankAccount || '',
    naturalPersonIdType: item.naturalPersonIdType || '',
    naturalPersonIdNumber: item.naturalPersonIdNumber || '',
    isGroupInternal: item.isGroupInternal === '1' || item.isGroupInternal === true,
    isHongKongMacaoTaiwan: item.isHongKongMacaoTaiwan === '1' || item.isHongKongMacaoTaiwan === true,
  }
}

function buildPartyStateFromList(list = []) {
  if (!Array.isArray(list) || !list.length) return null
  const state = { partyA: {}, partyB: {}, partyC: {}, isThreePartyContract: false }
  list.forEach((item) => {
    const key = item.roleCode === 'A' ? 'partyA' : item.roleCode === 'C' ? 'partyC' : 'partyB'
    state[key] = mapLaunchPartyToFormParty(item)
  })
  state.isThreePartyContract = Boolean(state.partyB.companyName && state.partyC.companyName)
  return state
}

/** 获取有效的合同方列表 */
function getPartyList(partyState) {
  const state = copyPartyState(partyState)
  const list = [state.partyA, state.partyB]
  if (state.isThreePartyContract) list.push(state.partyC)
  return list
}

/** 获取合同方摘要（如 "甲方：xxx；乙方：xxx"） */
function getPartySummary(partyState) {
  return getPartyList(partyState)
    .map((item) => `${item.roleName || '未知'}：${item.companyName || '未填写'}`)
    .join('；')
}

/** 构建推送法务系统的相对方信息列表（排除甲方） */
function buildContractOpposites(partyState) {
  return getPartyList(partyState)
    .filter((item) => item.roleKey !== 'A')
    .map((item) => ({
      roleName: item.roleName,
      oppositeName: item.companyName,
      oppCharacter: item.oppCharacter || 'QY',
      credirCode: item.oppCharacter === 'QY' ? item.creditCode : '',
      idCard: item.oppCharacter === 'ZZR' ? item.naturalPersonIdNumber : '',
      companyType: item.companyType,
      companyNature: item.companyNature,
      contactName: item.contactName,
      contactPhone: item.contactPhone,
      contactEmail: item.contactEmail,
    }))
}

/** 构建合同方同步列表（用于推送法务） */
function buildPartySyncList(partyState) {
  return getPartyList(partyState).map((item) => ({
    roleKey: item.roleKey,
    roleName: item.roleName,
    companyName: item.companyName,
    oppCharacter: item.oppCharacter,
    companyType: item.companyType,
    companyNature: item.companyNature,
    organizationNature: item.organizationNature,
    enterpriseNatureLevel2: item.enterpriseNatureLevel2,
    isHongKongMacaoTaiwan: item.isHongKongMacaoTaiwan ? 1 : 0,
    isGroupInternal: item.isGroupInternal ? 1 : 0,
    creditCode: item.creditCode,
    naturalPersonIdType: item.naturalPersonIdType,
    naturalPersonIdNumber: item.naturalPersonIdNumber,
    legalRepresentative: item.legalRepresentative,
    registeredAddress: item.registeredAddress,
    contactName: item.contactName,
    contactPhone: item.contactPhone,
    contactEmail: item.contactEmail,
    bankName: item.bankName,
    bankAccount: item.bankAccount,
  }))
}

// ========== 履行计划 ==========

/** 创建一条空的履行计划 */
function createPerformancePlan() {
  return {
    id: `plan_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`,
    planMatter: '',
    planWhere: '',
    planTime: '',
    planDay: 0,
    planMoney: '',
    planSubjectCode: '',
    planSubjectName: '',
    planDetail: '',
  }
}

// ========== 合同发起表单数据模型 ==========

/** 创建空白的合同表单数据 */
function createContractFormData() {
  return {
    // 合同基本信息
    contractName: '',           // 合同名称
    contractCode: '',           // 合同编码
    contractType: '',           // 合同类型
    contractTypeCode: '',       // 合同类型编码
    contractAmount: null,       // 合同金额（元）
    effectiveStart: '',         // 有效期起 YYYY-MM-DD
    effectiveEnd: '',           // 有效期止 YYYY-MM-DD
    contractPeriodType: '0',    // 合同期限类型: 0-固定期限 1-无固定期限
    periodExplain: '',          // 期限说明（无固定期限时填写）
    paymentMethod: '',          // 付款方式
    paymentDirection: '0',      // 收支方向
    isElectron: '1',            // 签署方式: 0-纸质 1-电子
    textSource: 'standardText', // 文本来源
    contractSource: 'ECP',      // 合同来源
    dateType: '0',              // 固定期间日期类型：0-年 1-月 2-日
    valuationMode: '',          // 计价方式: 0-无金额 1-固定总价 2-预估总价 3-无法预估总价 4-固定单价
    currencyName: '',           // 币种: RMB/USD/HKD/EUR/IDR
    sealType: '',               // 签订方式: GZ/HTZYZ/...
    mandateType: '',            // 委托类型
    cargoType: '',              // 货种
    primaryContent: '',         // 合同主要内容
    amountExplain: '',          // 金额说明
    contractGistRemark: '',     // 签订依据说明
    bodySummary: '',            // 正文摘要
    assistDept: '',             // 协助部门
    executorAccount: '',        // 合同执行人手机号
    selfCode: '',               // 二级单位合同编码
    mainContractCode: '',       // 主合同编码
    signingSubjectCode: '',     // 签约主体编码
    invoiceDate: '',            // 开票时间
    receiveDate: '',            // 收款时间

    // 文件
    contractBodyFileName: '',   // 合同正文文件名
    contractBodyFilePath: '',
    contractBodyFileType: '',
    contractBodyFileSize: null,
    contractBodyArrayBuffer: null, // 合同正文文件二进制
    otherAttachmentList: [],    // 其他附件列表
    contractGistFileList: [],   // 签订依据附件列表

    // 合同方
    partyState: {
      isThreePartyContract: false,
      partyA: { companyName: '' },
      partyB: { companyName: '' },
      partyC: { companyName: '' },
    },

    // 合同属性
    isInvolveHongKongMacaoTaiwan: false,  // 是否涉及港澳台
    isPublicTender: false,                 // 是否公开招标
    isForeignRelatedSubject: false,        // 签约主体是否涉外
    isIncludedInCurrentYearBudget: false,  // 是否列入当年预算
    isShared: false,                       // 是否共享
    isMajorContract: false,                // 是否重大合同

    // 额外金额
    extraAmounts: {
      depositEnabled: false, deposit: 0,
      pledgeEnabled: false, pledge: 0,
      advanceEnabled: false, advance: 0,
      guaranteeEnabled: false, guarantee: 0,
    },
    ewAmountType: '',
    djAmount: 0,
    yjAmount: 0,
    yfkAmount: 0,
    bzjAmount: 0,

    // 履行计划
    performancePlans: [],

    // 签署日期
    signDate: dayjs().format('YYYY-MM-DD'),
  }
}

/** 深拷贝合同表单数据（用 source 覆盖默认值） */
function copyContractFormData(data = {}) {
  const form = createContractFormData()
  const source = data || {}
  const fileState = splitFilesByCategory(source)

  form.contractName = source.contractName ?? form.contractName
  form.contractCode = source.contractCode ?? form.contractCode
  form.contractType = source.contractType ?? form.contractType
  form.contractTypeCode = source.contractTypeCode ?? source.contractType ?? form.contractTypeCode
  form.contractAmount = source.contractAmount ?? form.contractAmount
  form.effectiveStart = source.effectiveStart ? String(source.effectiveStart).slice(0, 10) : form.effectiveStart
  form.effectiveEnd = source.effectiveEnd ? String(source.effectiveEnd).slice(0, 10) : form.effectiveEnd
  form.contractPeriodType = source.contractPeriodType ?? form.contractPeriodType
  form.periodExplain = source.periodExplain ?? form.periodExplain
  form.paymentMethod = source.paymentMethod ?? form.paymentMethod
  form.paymentDirection = source.paymentDirection ?? form.paymentDirection
  form.isElectron = source.isElectron ?? form.isElectron
  form.textSource = source.textSource ?? form.textSource
  form.contractSource = source.contractSource ?? form.contractSource
  form.dateType = source.dateType ?? form.dateType
  form.valuationMode = source.valuationMode ?? form.valuationMode
  form.currencyName = source.currencyName ?? form.currencyName
  form.sealType = source.sealType ?? form.sealType
  form.mandateType = source.mandateType ?? form.mandateType
  form.cargoType = source.cargoType ?? form.cargoType
  form.primaryContent = source.primaryContent ?? form.primaryContent
  form.amountExplain = source.amountExplain ?? form.amountExplain
  form.contractGistRemark = source.contractGistRemark ?? form.contractGistRemark
  form.bodySummary = source.bodySummary ?? form.bodySummary
  form.assistDept = source.assistDept ?? source.assistDepartmentName ?? form.assistDept
  form.executorAccount = source.executorAccount ?? form.executorAccount
  form.selfCode = source.selfCode ?? form.selfCode
  form.mainContractCode = source.mainContractCode ?? form.mainContractCode
  form.signingSubjectCode = source.signingSubjectCode ?? source.orgId ?? form.signingSubjectCode
  form.invoiceDate = source.invoiceDate ? String(source.invoiceDate).slice(0, 10) : form.invoiceDate
  form.receiveDate = source.receiveDate ? String(source.receiveDate).slice(0, 10) : form.receiveDate
  form.contractBodyFileName = fileState.contractBodyFileName
  form.contractBodyFilePath = fileState.contractBodyFilePath
  form.contractBodyFileType = fileState.contractBodyFileType
  form.contractBodyFileSize = fileState.contractBodyFileSize
  form.contractBodyArrayBuffer = source.contractBodyArrayBuffer
    ? (source.contractBodyArrayBuffer instanceof ArrayBuffer ? source.contractBodyArrayBuffer.slice(0) : null)
    : null
  form.otherAttachmentList = fileState.otherAttachmentList
  form.contractGistFileList = fileState.contractGistFileList
  form.partyState = copyPartyState(source.partyState || source)
  form.isInvolveHongKongMacaoTaiwan = source.isInvolveHongKongMacaoTaiwan ?? form.isInvolveHongKongMacaoTaiwan
  form.isPublicTender = source.isPublicTender ?? form.isPublicTender
  form.isForeignRelatedSubject = source.isForeignRelatedSubject ?? form.isForeignRelatedSubject
  form.isIncludedInCurrentYearBudget = source.isIncludedInCurrentYearBudget ?? form.isIncludedInCurrentYearBudget
  form.isShared = source.isShared ?? form.isShared
  form.isMajorContract = source.isMajorContract ?? form.isMajorContract
  form.signDate = source.signDate ? String(source.signDate).slice(0, 10) : form.signDate
  form.extraAmounts = {
    depositEnabled: false, deposit: 0,
    pledgeEnabled: false, pledge: 0,
    advanceEnabled: false, advance: 0,
    guaranteeEnabled: false, guarantee: 0,
  }
  form.extraAmounts = restoreExtraAmounts(source, form.extraAmounts)
  form.ewAmountType = source.ewAmountType ?? form.ewAmountType
  form.djAmount = source.djAmount ?? form.djAmount
  form.yjAmount = source.yjAmount ?? form.yjAmount
  form.yfkAmount = source.yfkAmount ?? form.yfkAmount
  form.bzjAmount = source.bzjAmount ?? form.bzjAmount
  form.performancePlans = (source.performancePlans || []).map((item) => ({
    ...createPerformancePlan(),
    ...item,
    planTime: item.planTime ? String(item.planTime).slice(0, 10) : '',
  }))

  return form
}

// ========== 模板变量预览数据构建 ==========

/** 将合同表单数据转换为模板变量字典（用于 docxtemplater 渲染） */
function buildTemplatePreviewData(formData, templateLabel = '', creationMode = 'template') {
  const form = copyContractFormData(formData)
  const attachmentSummary = formatFileSummary(form.otherAttachmentList)
  const contractGistFileSummary = formatFileSummary(form.contractGistFileList)
  const partyA = form.partyState.partyA || {}
  const partyB = form.partyState.partyB || {}
  const partyC = form.partyState.partyC || {}

  return {
    contractName: form.contractName || '未填写',
    contractCode: form.contractCode || '未填写',
    contractType: getOptionLabel(contractTypeOptions, form.contractType) || form.contractType || '未填写',
    contractTypeCode: form.contractTypeCode || '',
    contractAmountDisplay: formatContractAmount(form.contractAmount),
    contractAmount: form.contractAmount ?? '',
    effectiveStart: form.effectiveStart || '--',
    effectiveEnd: form.effectiveEnd || '--',
    effectivePeriod: form.contractPeriodType === '1'
      ? form.periodExplain || '无固定期限'
      : `${form.effectiveStart || '--'} 至 ${form.effectiveEnd || '--'}`,
    contractPeriodTypeLabel: getContractPeriodTypeLabel(form.contractPeriodType),
    periodExplain: form.periodExplain || '',
    paymentMethod: form.paymentMethod || '未填写',
    paymentDirectionLabel: getOptionLabel(paymentDirectionOptions, form.paymentDirection),
    valuationModeLabel: getOptionLabel(valuationModeOptions, form.valuationMode),
    currencyNameLabel: getOptionLabel(currencyOptions, form.currencyName),
    sealTypeLabel: getOptionLabel(sealTypeOptions, form.sealType),
    textSourceLabel: getOptionLabel(textSourceOptions, form.textSource),
    isElectronLabel: getOptionLabel(isElectronOptions, form.isElectron),
    contractSourceLabel: getOptionLabel(contractSourceOptions, form.contractSource),
    dateTypeLabel: getOptionLabel(dateTypeOptions, form.dateType),
    mandateType: form.mandateType || '',
    cargoType: form.cargoType || '',
    primaryContent: form.primaryContent || form.cargoType || '',
    amountExplain: form.amountExplain || '',
    contractGistRemark: form.contractGistRemark || '',
    bodySummary: form.bodySummary || form.primaryContent || '',
    assistDept: form.assistDept || '',
    executorAccount: form.executorAccount || '',
    selfCode: form.selfCode || '',
    mainContractCode: form.mainContractCode || '',
    signingSubjectCode: form.signingSubjectCode || '',
    invoiceDate: form.invoiceDate || '',
    receiveDate: form.receiveDate || '',
    counterpartySummary: getPartySummary(form.partyState),
    attachmentSummary,
    contractGistFileSummary,
    contractBodyFileName: form.contractBodyFileName || '',
    extraAmountSummary: buildExtraAmountSummary(form.extraAmounts),
    performancePlanSummary: buildPerformancePlanSummary(form.performancePlans),
    templateLabel: templateLabel || (creationMode === 'manual' ? '手动创建' : ''),
    signDate: form.signDate || dayjs().format('YYYY-MM-DD'),
    partyAName: partyA.companyName || '',
    partyBName: partyB.companyName || '',
    partyCName: form.partyState.isThreePartyContract ? partyC.companyName || '' : '',
    partyAContactName: partyA.contactName || '',
    partyBContactName: partyB.contactName || '',
    partyCContactName: partyC.contactName || '',
    partyAContactPhone: partyA.contactPhone || '',
    partyBContactPhone: partyB.contactPhone || '',
    partyCContactPhone: partyC.contactPhone || '',
    partyAAddress: partyA.registeredAddress || partyA.contactAddress || '',
    partyBAddress: partyB.registeredAddress || partyB.contactAddress || '',
    partyCAddress: partyC.registeredAddress || partyC.contactAddress || '',
    partyACreditCode: partyA.creditCode || '',
    partyBCreditCode: partyB.creditCode || '',
    partyCCreditCode: partyC.creditCode || '',
    partyABankName: partyA.bankName || '',
    partyBBankName: partyB.bankName || '',
    partyCBankName: partyC.bankName || '',
    partyABankAccount: partyA.bankAccount || '',
    partyBBankAccount: partyB.bankAccount || '',
    partyCBankAccount: partyC.bankAccount || '',
  }
}

// ========== 提交报文构建 ==========

/** 构建推送法务系统的合同报文 */
function buildContractSubmitPayload(formData, extra = {}) {
  const form = copyContractFormData(formData)

  return {
    creationMode: extra.creationMode || 'template',
    templateKey: extra.templateKey || '',
    templateName: extra.templateName || '',
    basicInfo: {
      contractName: form.contractName,
      contractCode: form.contractCode,
      contractType: form.contractType,
      contractTypeCode: form.contractTypeCode,
      contractAmount: form.contractAmount,
      effectiveStart: form.effectiveStart,
      effectiveEnd: form.effectiveEnd,
      contractPeriodType: form.contractPeriodType,
      periodExplain: form.periodExplain,
      paymentMethod: form.paymentMethod,
      paymentDirection: form.paymentDirection,
      isElectron: form.isElectron,
      textSource: form.textSource,
      contractSource: form.contractSource,
      dateType: form.dateType,
      valuationMode: form.valuationMode,
      currencyName: form.currencyName,
      sealType: form.sealType,
      mandateType: form.mandateType,
      cargoType: form.cargoType,
      primaryContent: form.primaryContent,
      amountExplain: form.amountExplain,
      contractGistRemark: form.contractGistRemark,
      bodySummary: form.bodySummary,
      assistDept: form.assistDept,
      executorAccount: form.executorAccount,
      selfCode: form.selfCode,
      mainContractCode: form.mainContractCode,
      signingSubjectCode: form.signingSubjectCode,
      invoiceDate: form.invoiceDate,
      receiveDate: form.receiveDate,
    },
    partyMaintenance: {
      isThreePartyContract: form.partyState.isThreePartyContract ? 1 : 0,
      parties: buildPartySyncList(form.partyState),
    },
    pushContractPayloadPreview: {
      contractName: form.contractName,
      contractTypeCode: form.contractTypeCode,
      contractPeriod: form.contractPeriodType,
      startTime: form.effectiveStart,
      endTime: form.effectiveEnd,
      periodExplain: form.periodExplain,
      dateType: form.dateType,
      valuationMode: form.valuationMode,
      currencyName: form.currencyName,
      sealType: form.sealType,
      isText: form.textSource,
      isElectron: form.isElectron,
      paymentDirection: form.paymentDirection,
      primaryContent: form.primaryContent,
      amountExplain: form.amountExplain,
      contractGistRemark: form.contractGistRemark,
      isMajorContract: form.isMajorContract ? 1 : 0,
      contractOpposites: buildContractOpposites(form.partyState),
      otherAttach: form.otherAttachmentList.map((item) => ({
        fileName: item.name || item.fileName,
        filePath: item.filePath,
        fileType: item.fileType,
        fileSize: item.size ?? item.fileSize,
      })),
      contractGistFile: form.contractGistFileList.map((item) => ({
        fileName: item.name || item.fileName,
        filePath: item.filePath,
        fileType: item.fileType,
        fileSize: item.size ?? item.fileSize,
      })),
      contractBodyFileName: form.contractBodyFileName,
      performancePlans: form.performancePlans,
    },
  }
}

const emit = defineEmits(["save"]);

const visible = ref(false);
const previewRef = ref(null);
const formRef = ref(null);
const modalMode = ref("create");
const creationMode = ref("template");
const templateKey = ref("");
const contractDetailState = ref(createContractFormData());
const rendering = ref(false);
const exporting = ref(false);
const previewError = ref("");
const previewBlob = ref(null);
const manualBodyFileList = ref([]);
const publishedTemplates = ref([]);

const templateBufferCache = new Map();
let previewTimer = null;
let renderTaskId = 0;

const templateSelectOptions = computed(() =>
  publishedTemplates.value.map((item) => ({
    label: item.templateName,
    value: item.id,
  })),
);

// TODO: 这里后续替换为“根据模板ID查询模板详情接口”
// TODO: 这里后续如果“已发布模板列表接口”不返回完整详情，再改成调用“模板详情接口”
//相当于详情接口返回了 映射 然后下面做匹配
const activeTemplate = computed(() =>
  templateKey.value ? getManifestDetailLocal(templateKey.value) : null,
);

const isViewMode = computed(() => modalMode.value === "view");

const modalTitle = computed(() => "合同正文配置");

const templateFieldLabelMap = computed(() => {
  const map = {};
  //这个就是字典数据，包含所有定义的映射字段
  templateFieldList.forEach((item) => {
    map[item.value] = item.label;
  });
  return map;
});

const launchFieldList = computed(() => {
  if (creationMode.value === "manual") {
    return [];
  }
  // 模板模式：从 variableMappings 中取出 bindField
  const mappings = activeTemplate.value?.variableMappings || [];
  const fieldKeys = [];

  mappings.forEach((item) => {
    if (!item.bindField) return;
    if (!fieldKeys.includes(item.bindField)) {
      fieldKeys.push(item.bindField);
    }
  });

  return fieldKeys.map((key) => ({
    key,
    label: templateFieldLabelMap.value[key] || key,
    readonly: Object.prototype.hasOwnProperty.call(templateVariables.value, key),
  }));
});

function getLaunchFieldValue(fieldKey) {
  if (Object.prototype.hasOwnProperty.call(templateVariables.value, fieldKey)) {
    return templateVariables.value[fieldKey] ?? "";
  }
  return contractDetailState.value[fieldKey] ?? "";
}

function setLaunchFieldValue(fieldKey, value) {
  if (Object.prototype.hasOwnProperty.call(templateVariables.value, fieldKey)) {
    return;
  }
  contractDetailState.value[fieldKey] = value;
}

const templateVariables = computed(() =>
  buildTemplatePreviewData(
    contractDetailState.value,
    activeTemplate.value?.templateName || "",
    creationMode.value,
  ),
);

const hasPreviewSource = computed(() => {
  if (creationMode.value === "manual") {
    return !!contractDetailState.value.contractBodyArrayBuffer;
  }
  return !!activeTemplate.value;
});

const previewDescription = computed(() =>
  creationMode.value === "manual"
    ? "手动创建模式下，右侧直接渲染当前窗口上传的合同正文 Word 文件。"
    : "模板创建模式下，系统会根据维护后的合同信息自动填充模板变量并生成合同正文。",
);

const emptyPreviewText = computed(() =>
  creationMode.value === "manual"
    ? "请先在当前窗口上传合同正文 Word 文件。"
    : "请选择模板并完善合同信息后再预览。",
);

watch(
  () => visible.value,
  (open) => {
    if (open) {
      nextTick(() => {
        refreshPreview();
      });
    }
  },
);

watch(
  () => JSON.stringify(templateVariables.value),
  () => {
    if (visible.value) {
      schedulePreview();
    }
  },
);

watch(
  () => JSON.stringify(contractDetailState.value.otherAttachmentList),
  () => {
    if (visible.value) {
      schedulePreview();
    }
  },
);

watch(
  () => creationMode.value,
  (mode, prevMode) => {
    if (mode === prevMode) return;

    if (mode === "template" && prevMode === "manual") {
      contractDetailState.value.contractBodyFileName = "";
      contractDetailState.value.contractBodyFilePath = "";
      contractDetailState.value.contractBodyFileType = "";
      contractDetailState.value.contractBodyFileSize = null;
      contractDetailState.value.contractBodyArrayBuffer = null;
      contractDetailState.value.textSource = "standardText";
      syncManualBodyFileList();
    }

    previewBlob.value = null;
    previewError.value = "";
    if (visible.value) {
      schedulePreview();
    }
  },
);

function syncManualBodyFileList() {
  manualBodyFileList.value = contractDetailState.value.contractBodyFileName
    ? [
        {
          uid: "manual_contract_body",
          name: contractDetailState.value.contractBodyFileName,
          status: "done",
        },
      ]
    : [];
}

function loadPublishedTemplates() {
  // TODO: 这里后续替换为“已发布模板列表接口”
  return queryPublishedManifestList().then((list) => {
    publishedTemplates.value = list;
  });
}

function getManifestDetailLocal(templateId) {
  return (
    publishedTemplates.value.find((item) => item.id === templateId) || null
  );
}

function schedulePreview() {
  clearPreviewTimer();
  previewTimer = window.setTimeout(() => {
    refreshPreview();
  }, 240);
}

function clearPreviewTimer() {
  if (previewTimer) {
    window.clearTimeout(previewTimer);
    previewTimer = null;
  }
}

async function fetchTemplateBuffer(templateFilePath) {
  const cached = templateBufferCache.get(templateFilePath);
  if (cached) {
    return cached.slice(0);
  }

  const arrayBuffer = await downloadTemplateFile(templateFilePath);
  const buffer =
    arrayBuffer instanceof ArrayBuffer ? arrayBuffer : arrayBuffer?.buffer;
  templateBufferCache.set(templateFilePath, buffer);
  return buffer.slice(0);
}

async function readFileAsArrayBuffer(fileLike) {
  const rawFile = fileLike?.originFileObj || fileLike?.file || fileLike;
  if (!rawFile) {
    throw new Error("未获取到上传文件");
  }

  if (typeof rawFile.arrayBuffer === "function") {
    const buffer = await rawFile.arrayBuffer();
    return buffer instanceof ArrayBuffer ? buffer : buffer?.buffer;
  }

  if (rawFile instanceof Blob) {
    return await rawFile.arrayBuffer();
  }

  return await new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = () => reject(new Error("读取 Word 文件失败"));
    reader.readAsArrayBuffer(rawFile);
  });
}

async function buildFilledDocumentBlob() {
  if (creationMode.value === "manual") {
    if (!contractDetailState.value.contractBodyArrayBuffer) {
      throw new Error("请先上传合同正文 Word 文件");
    }

    return new Blob(
      [contractDetailState.value.contractBodyArrayBuffer.slice(0)],
      {
        type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      },
    );
  }

  if (!activeTemplate.value) {
    throw new Error("请选择合同模板");
  }

  const buffer = activeTemplate.value.templateArrayBuffer
    ? activeTemplate.value.templateArrayBuffer.slice(0)
    : await fetchTemplateBuffer(
        activeTemplate.value.templateFilePath ||
          activeTemplate.value.templateFileUrl ||
          activeTemplate.value.fileUrl,
      );
  const zip = new PizZip(buffer);
  const doc = new Docxtemplater(zip, {
    paragraphLoop: true,
    linebreaks: true,
    nullGetter(part) {
      return part?.raw ? `[${part.raw}]` : "";
    },
  });
  //这里是生成的测试数据
  const sourceValues = buildTemplatePreviewData(
    contractDetailState.value,
    activeTemplate.value?.templateName || "",
    creationMode.value,
  );
  const mappings = activeTemplate.value.variableMappings || [];

  const renderData = {};
  //这部分是匹配的关键
  if (mappings.length) {
    mappings.forEach((item) => {
      if (!item.variableKey) return;
      renderData[item.variableKey] =
        sourceValues[item.bindField] ??
        contractDetailState.value[item.bindField] ??
        "";
    });
  } else {
    Object.assign(renderData, sourceValues);
  }

  doc.render(renderData);

  return doc.getZip().generate({
    type: "blob",
    mimeType:
      "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  });
}

async function refreshPreview() {
  if (!visible.value || !previewRef.value || !hasPreviewSource.value) {
    if (previewRef.value) {
      previewRef.value.innerHTML = "";
    }
    return;
  }

  const currentTaskId = ++renderTaskId;
  rendering.value = true;
  previewError.value = "";

  try {
    const blob = await buildFilledDocumentBlob();
    if (currentTaskId !== renderTaskId || !previewRef.value) {
      return;
    }

    previewBlob.value = blob;
    previewRef.value.innerHTML = "";

    await renderAsync(blob, previewRef.value, previewRef.value, {
      className: "contract-docx-preview",
      inWrapper: true,
      ignoreWidth: false,
      ignoreHeight: false,
    });
  } catch (error) {
    if (currentTaskId !== renderTaskId) {
      return;
    }

    previewBlob.value = null;
    previewError.value = error?.message || "文档渲染失败";
    if (previewRef.value) {
      previewRef.value.innerHTML = "";
    }
  } finally {
    if (currentTaskId === renderTaskId) {
      rendering.value = false;
    }
  }
}

async function exportFilledDocument() {
  exporting.value = true;
  try {
    const blob = previewBlob.value || (await buildFilledDocumentBlob());
    previewBlob.value = blob;
    const fileName =
      creationMode.value === "manual"
        ? contractDetailState.value.contractBodyFileName ||
          `${contractDetailState.value.contractName || "合同正文"}.docx`
        : `${contractDetailState.value.contractName || "合同正文"}.docx`;
    saveAs(blob, fileName);
    message.success("已导出 Word 文档");
  } catch (error) {
    message.warning(error?.message || "导出 Word 失败");
  } finally {
    exporting.value = false;
  }
}

async function handleManualContractBodyUpload(file) {
  if (!/\.docx$/i.test(file.name)) {
    message.warning("仅支持上传 .docx 格式的合同正文");
    return false;
  }

  const formData = new FormData();
  formData.append("file", file);
  const uploadRes = await uploadLaunchFile(formData);
  if (String(uploadRes?.code || "") !== "0000" || !uploadRes?.result) {
    message.error(uploadRes?.message || uploadRes?.msg || "合同正文上传失败");
    return false;
  }

  contractDetailState.value.contractBodyFileName = file.name;
  contractDetailState.value.contractBodyFilePath = uploadRes.result;
  contractDetailState.value.contractBodyFileType =
    file.name.split(".").pop()?.toLowerCase() || "docx";
  contractDetailState.value.contractBodyFileSize = file.size;
  contractDetailState.value.textSource = "draftBySelf";
  contractDetailState.value.contractBodyArrayBuffer = await readFileAsArrayBuffer(
    file,
  );
  syncManualBodyFileList();
  previewBlob.value = null;
  previewError.value = "";
  schedulePreview();
  message.success("合同正文已上传");
  return false;
}

function handleRemoveManualContractBody() {
  contractDetailState.value.contractBodyFileName = "";
  contractDetailState.value.contractBodyFilePath = "";
  contractDetailState.value.contractBodyFileType = "";
  contractDetailState.value.contractBodyFileSize = null;
  contractDetailState.value.contractBodyArrayBuffer = null;
  syncManualBodyFileList();
  previewBlob.value = null;
  previewError.value = "";
  schedulePreview();
  return true;
}

async function uploadBlobAsLaunchFile(blob, fileName) {
  const file = new File([blob], fileName, {
    type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  });
  const formData = new FormData();
  formData.append("file", file);
  const uploadRes = await uploadLaunchFile(formData);
  if (String(uploadRes?.code || "") !== "0000" || !uploadRes?.result) {
    throw new Error(uploadRes?.message || uploadRes?.msg || "合同正文上传失败");
  }
  contractDetailState.value.contractBodyFileName = fileName;
  contractDetailState.value.contractBodyFilePath = uploadRes.result;
  contractDetailState.value.contractBodyFileType = "docx";
  contractDetailState.value.contractBodyFileSize = file.size;
}

async function ensureTemplateBodyFileUploaded() {
  if (creationMode.value !== "template") return;
  const blob = previewBlob.value || (await buildFilledDocumentBlob());
  previewBlob.value = blob;
  const fileName = `${contractDetailState.value.contractName || "合同正文"}.docx`;
  await uploadBlobAsLaunchFile(blob, fileName);
}

function validateMainForm() {
  if (creationMode.value === "template" && !templateKey.value)
    return "请选择合同模板";

  for (const field of launchFieldList.value) {
    if (field.readonly) {
      continue;
    }
    const value = getLaunchFieldValue(field.key);
    if (value === null || value === undefined || value === "") {
      return `请输入${field.label}`;
    }
  }

  if (
    creationMode.value === "manual" &&
    !contractDetailState.value.contractBodyArrayBuffer
  )
    return "请上传合同正文 Word 文件";
  return "";
}

async function handleSave() {
  const errorMessage = validateMainForm();
  if (errorMessage) {
    message.warning(errorMessage);
    return;
  }

  try {
    await ensureTemplateBodyFileUploaded();
    emit("save", {
      creationMode: creationMode.value,
      templateKey: templateKey.value,
      templateName: activeTemplate.value?.templateName || "",
      detailState: copyContractFormData(contractDetailState.value),
    });
    visible.value = false;
    message.success("合同正文配置已保存");
  } catch (error) {
    message.warning(error?.message || "合同正文保存失败");
  }
}

function resetForm() {
  creationMode.value = "template";
  templateKey.value = publishedTemplates.value[0]?.id || "";
  contractDetailState.value = createContractFormData();
  syncManualBodyFileList();
  previewError.value = "";
  previewBlob.value = null;
  nextTick(() => {
    if (visible.value) {
      refreshPreview();
    }
  });
}

async function showModal(initialValues = {}, mode = "create") {
  modalMode.value = mode || "create";
  const isCreate = modalMode.value === "create";
  await loadPublishedTemplates();

  creationMode.value = isCreate
    ? "template"
    : initialValues.creationMode ||
      (initialValues.detailState?.contractBodyArrayBuffer ||
      initialValues.contractBodyArrayBuffer
        ? "manual"
        : "template");
  templateKey.value = isCreate
    ? publishedTemplates.value[0]?.id || ""
    : initialValues.templateKey || "";
  contractDetailState.value = isCreate
    ? createContractFormData()
    : initialValues.detailState
      ? copyContractFormData(initialValues.detailState)
      : copyContractFormData(initialValues);

  syncManualBodyFileList();
  previewBlob.value = null;
  previewError.value = "";
  visible.value = true;
}

defineExpose({
  showModal,
});

onBeforeUnmount(() => {
  clearPreviewTimer();
});
</script>

<style scoped>
.contract-template-layout {
  display: grid;
  grid-template-columns: 460px minmax(0, 1fr);
  height: 100%;
  min-height: 0;
}

.config-panel {
  border-right: 1px solid #e5e7eb;
  background: linear-gradient(180deg, #fbfdff 0%, #f6f8fc 100%);
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.panel-header,
.preview-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 24px 16px;
  border-bottom: 1px solid rgba(148, 163, 184, 0.16);
}

.panel-header h3,
.preview-header h3 {
  font-size: 18px;
  font-weight: 700;
  color: #0f172a;
  margin-bottom: 6px;
}

.preview-header p {
  font-size: 13px;
  color: #64748b;
  line-height: 1.7;
}

.config-form {
  flex: 1;
  overflow-y: auto;
  padding: 20px;
}

.panel-card {
  margin-bottom: 16px;
  border-radius: 14px;
  border-color: #e8edf5;
  box-shadow: 0 6px 20px rgba(15, 23, 42, 0.04);
}

.grid-two {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.template-tip {
  padding: 12px 14px;
  border-radius: 12px;
  background: linear-gradient(135deg, #eff6ff 0%, #f8fbff 100%);
  border: 1px solid #dbeafe;
}

.template-tip-title {
  color: #1d4ed8;
  font-weight: 600;
  margin-bottom: 4px;
}

.template-tip-desc {
  font-size: 12px;
  color: #5b6b81;
  line-height: 1.7;
}

.party-action-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 16px;
}

.minor-label {
  font-size: 14px;
  font-weight: 600;
  color: #0f172a;
  margin-bottom: 4px;
}

.minor-desc {
  font-size: 12px;
  color: #64748b;
  line-height: 1.7;
}

.summary-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.summary-item {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  padding: 10px 12px;
  border-radius: 12px;
  background: #f8fbff;
  border: 1px solid #e4edf8;
}

.summary-item span {
  font-size: 12px;
  color: #64748b;
}

.summary-item strong {
  flex: 1;
  text-align: right;
  font-size: 13px;
  color: #0f172a;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.summary-meta-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-top: 14px;
}

.meta-box {
  border-radius: 12px;
  padding: 12px;
  border: 1px solid #e5edf7;
  background: linear-gradient(180deg, #ffffff 0%, #f9fbff 100%);
}

.meta-box-label {
  font-size: 12px;
  color: #64748b;
  margin-bottom: 4px;
}

.meta-box-value {
  font-size: 13px;
  color: #0f172a;
  line-height: 1.7;
}

.panel-footer {
  padding: 16px 20px 20px;
  border-top: 1px solid rgba(148, 163, 184, 0.16);
  background: rgba(255, 255, 255, 0.9);
}

.preview-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background:
    radial-gradient(
      circle at top left,
      rgba(59, 130, 246, 0.12),
      transparent 28%
    ),
    linear-gradient(180deg, #f3f6fb 0%, #eef2f7 100%);
}

.preview-shell {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 28px 24px 32px;
}

.preview-stage {
  min-height: 100%;
}

.preview-state {
  position: absolute;
  inset: 28px 24px 32px;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  color: #64748b;
  text-align: center;
  border-radius: 20px;
}

.preview-mask {
  background: rgba(243, 246, 251, 0.88);
  backdrop-filter: blur(4px);
}

.preview-error {
  background: rgba(255, 255, 255, 0.92);
  border: 1px dashed #fca5a5;
}

.preview-error h4 {
  color: #b91c1c;
  font-size: 18px;
}

.preview-error p {
  max-width: 520px;
  line-height: 1.8;
}

.preview-stage :deep(.docx-wrapper) {
  background: transparent;
  padding: 0;
}

.preview-stage :deep(.docx) {
  margin: 0 auto 24px;
  box-shadow:
    0 22px 50px rgba(15, 23, 42, 0.12),
    0 2px 10px rgba(15, 23, 42, 0.08);
}

@media (max-width: 1200px) {
  .contract-template-layout {
    grid-template-columns: 1fr;
  }

  .config-panel {
    border-right: none;
    border-bottom: 1px solid #e5e7eb;
  }
}

@media (max-width: 760px) {
  .grid-two,
  .summary-meta-grid {
    grid-template-columns: 1fr;
  }

  .panel-header,
  .preview-header,
  .party-action-row {
    flex-direction: column;
  }
}
</style>
