<template>
  <a-modal
    v-model:open="visible"
    title="维护合同方信息"
    width="96vw"
    :footer="null"
    :mask-closable="false"
    :style="{ top: '12px' }"
    :body-style="{
      padding: 0,
      height: 'calc(100vh - 120px)',
      overflow: 'hidden',
    }"
  >
    <div class="contract-main-editor">
      <div class="editor-header">
        <div><h3>合同主信息</h3></div>
        <a-space>
          <a-tag :color="creationMode === 'manual' ? 'orange' : 'blue'">
            {{ creationMode === "manual" ? "手动创建" : "模板创建" }}
          </a-tag>
          <a-tag v-if="templateName" color="geekblue">
            {{ templateName }}
          </a-tag>
        </a-space>
      </div>

      <div class="editor-body">
        <a-form layout="vertical" :disabled="isViewMode" class="editor-form">
          <section class="main-grid">
            <div class="left-column">
              <div class="section-card">
                <div class="section-title-row">
                  <div class="section-title">合同主信息</div>
                </div>

                <div class="section-title-row counterparty-toolbar">
                  <div class="section-title">我方主体</div>
                  <a-button
                    v-if="!isViewMode && !isOurPartyLocked"
                    type="link"
                    class="text-action-btn"
                    @click="handleAddOurParty"
                  >
                    添加
                  </a-button>
                </div>
                <div class="party-empty-state" style="margin-bottom: 12px">
                  {{
                    isSingleAgreementChecked
                      ? "单方协议默认使用当前登录主体，且不可修改"
                      : "当前登录主体必须包含在我方主体中"
                  }}
                </div>

                <div
                  v-for="party in ourParties"
                  :key="party.localId"
                  class="party-block"
                >
                  <div class="party-block-header">
                    <div class="field-label required">
                      {{ party.displayName }}
                    </div>
                    <a-button
                      v-if="
                        !isViewMode &&
                        !isOurPartyLocked &&
                        ourParties.length > 1
                      "
                      type="link"
                      danger
                      class="text-action-btn"
                      @click="handleRemoveOurParty(party.localId)"
                    >
                      删除
                    </a-button>
                  </div>
                  <div class="search-input-row">
                    <a-select
                      v-model:value="party.companyName"
                      :options="getPartyOptions(party.roleCode)"
                      show-search
                      :disabled="isViewMode || isOurPartyLocked"
                      :placeholder="`请选择${party.displayName}`"
                      :filter-option="filterPartnerOption"
                      @change="
                        (value, option) =>
                          handleCompanySelect(party, value, option)
                      "
                    />
                    <a-button
                      class="icon-btn"
                      :disabled="isViewMode || isOurPartyLocked"
                      @click="showSearchTip(`${party.displayName}`)"
                    >
                      <SearchOutlined />
                    </a-button>
                  </div>
                  <div class="party-info-box">
                    <div class="info-line">
                      <span>{{ party.displayName }}</span
                      ><strong>{{ party.companyName || "/" }}</strong>
                    </div>
                    <div class="info-line">
                      <span>主体编码</span
                      ><strong>{{ party.subjectCode || "/" }}</strong>
                    </div>
                    <div class="info-line">
                      <span>电话</span
                      ><strong>{{ party.contactPhone || "/" }}</strong>
                    </div>
                    <div class="info-line">
                      <span>公司地址</span
                      ><strong>{{ party.registeredAddress || "/" }}</strong>
                    </div>
                    <div class="info-line">
                      <span>银行/账号</span
                      ><strong>{{ party.bankAccount || "/" }}</strong>
                    </div>
                    <a-button
                      class="mini-search-btn"
                      type="text"
                      @click="showSearchTip(`${party.displayName}详情`)"
                    >
                      <SearchOutlined />
                    </a-button>
                  </div>
                </div>

                <div class="section-title-row counterparty-toolbar">
                  <div class="section-title">相对方</div>
                  <a-button
                    v-if="!isViewMode && !isSingleAgreementChecked"
                    type="link"
                    class="text-action-btn"
                    @click="handleAddCounterparty"
                  >
                    添加
                  </a-button>
                </div>

                <template v-if="!isSingleAgreementChecked">
                  <div
                    v-for="party in counterpartyParties"
                    :key="party.localId"
                    class="party-block"
                  >
                    <div class="party-block-header">
                      <div class="field-label required">
                        {{ party.displayName }}
                      </div>
                      <a-button
                        v-if="!isViewMode"
                        type="link"
                        danger
                        class="text-action-btn"
                        @click="handleRemoveCounterparty(party.localId)"
                      >
                        删除
                      </a-button>
                    </div>
                    <div class="search-input-row">
                      <a-select
                        v-model:value="party.companyName"
                        :options="getPartyOptions(party.roleCode)"
                        show-search
                        :placeholder="`请选择${party.displayName}`"
                        :filter-option="filterPartnerOption"
                        @change="
                          (value, option) =>
                            handleCompanySelect(party, value, option)
                        "
                      />
                      <a-button
                        class="icon-btn"
                        @click="showSearchTip(`${party.displayName}`)"
                      >
                        <SearchOutlined />
                      </a-button>
                    </div>
                    <div class="party-info-box">
                      <div class="info-line">
                        <span>{{ party.displayName }}</span
                        ><strong>{{ party.companyName || "/" }}</strong>
                      </div>
                      <div class="info-line">
                        <span>电话</span
                        ><strong>{{ party.contactPhone || "/" }}</strong>
                      </div>
                      <div class="info-line">
                        <span>公司地址</span
                        ><strong>{{ party.registeredAddress || "/" }}</strong>
                      </div>
                      <div class="info-line">
                        <span>银行/账号</span
                        ><strong>{{ party.bankAccount || "/" }}</strong>
                      </div>
                      <a-button
                        class="mini-search-btn"
                        type="text"
                        @click="showSearchTip(`${party.displayName}详情`)"
                      >
                        <SearchOutlined />
                      </a-button>
                    </div>
                  </div>

                  <div
                    v-if="!counterpartyParties.length"
                    class="party-empty-state"
                  >
                    暂无相对方，请点击右上角“添加”
                  </div>
                </template>
                <div v-else class="party-empty-state">
                  当前为单方协议，无需维护相对方
                </div>

                <div class="field-grid field-grid-2">
                  <a-form-item class="tight-item" label="合同名称" required>
                    <a-input
                      v-model:value="detailState.contractName"
                      placeholder="请输入"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="合同号">
                    <a-input
                      v-model:value="detailState.contractCode"
                      placeholder="不填则系统自动生成"
                      :disabled="!!detailState.id"
                    />
                  </a-form-item>
                  <a-form-item
                    class="tight-item field-span-2"
                    label="合同类型"
                    required
                  >
                    <a-select
                      v-model:value="detailState.contractType"
                      :options="contractTypeOptions"
                      placeholder="请选择合同类型"
                      @change="handleContractTypeChange"
                    />
                  </a-form-item>
                </div>

                <div class="period-block">
                  <div class="field-label required">合同起止时间</div>
                  <a-radio-group v-model:value="detailState.contractPeriodType">
                    <a-radio value="0">固定期限</a-radio>
                    <a-radio value="1">无固定期限</a-radio>
                  </a-radio-group>
                  <div
                    class="period-row"
                    v-if="detailState.contractPeriodType === '0'"
                  >
                    <a-date-picker
                      v-model:value="detailState.effectiveStart"
                      value-format="YYYY-MM-DD"
                      placeholder="开始日期"
                      style="width: 48%"
                    />
                    <a-date-picker
                      v-model:value="detailState.effectiveEnd"
                      value-format="YYYY-MM-DD"
                      placeholder="结束日期"
                      style="width: 48%"
                    />
                  </div>
                  <a-input
                    v-else
                    v-model:value="detailState.periodExplain"
                    placeholder="期限说明"
                    class="period-explain-input"
                  />
                </div>

                <div class="field-grid field-grid-2">
                  <a-form-item class="tight-item" label="计价方式" required>
                    <a-select
                      v-model:value="detailState.valuationMode"
                      :options="valuationModeOptions"
                      placeholder="请选择计价方式"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="币种" required>
                    <a-select
                      v-model:value="detailState.currencyName"
                      :options="currencyOptions"
                      placeholder="请选择币种"
                    />
                  </a-form-item>
                  <a-form-item
                    class="tight-item field-span-2"
                    label="签订方式"
                    required
                  >
                    <a-select
                      v-model:value="detailState.sealType"
                      :options="sealTypeOptions"
                      placeholder="请选择签订方式"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item field-span-2" label="委托类型">
                    <a-select
                      v-model:value="detailState.mandateType"
                      :options="mandateTypeOptions"
                      placeholder="请选择委托类型"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item field-span-2" label="货种">
                    <a-select
                      v-model:value="detailState.cargoType"
                      :options="cargoTypeOptions"
                      placeholder="请选择货种"
                    />
                  </a-form-item>
                  <a-form-item
                    class="tight-item field-span-2"
                    label="付款方式"
                    required
                  >
                    <a-select
                      v-model:value="detailState.paymentMethod"
                      :options="paymentMethodOptions"
                      placeholder="请选择付款方式"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="收支方向" required>
                    <a-select
                      v-model:value="detailState.paymentDirection"
                      :options="paymentDirectionOptions"
                      placeholder="请选择收支方向"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="签署方式" required>
                    <a-select
                      v-model:value="detailState.isElectron"
                      :options="isElectronOptions"
                      placeholder="请选择签署方式"
                    />
                  </a-form-item>
                </div>
              </div>
            </div>

            <div class="right-column">
              <div class="section-card">
                <div class="basic-header">
                  <div class="basic-title">
                    <span class="dot"></span>
                    基本信息
                  </div>
                </div>

                <div class="summary-grid">
                  <div class="summary-line">
                    <span>合同名称</span
                    ><strong>{{ detailState.contractName || "未填写" }}</strong>
                  </div>
                  <div class="summary-line">
                    <span>我方</span><strong>{{ ourPartySummaryText }}</strong>
                  </div>
                  <div class="summary-line">
                    <span>合同编号</span
                    ><strong>{{ detailState.contractCode || "未填写" }}</strong>
                  </div>
                  <div class="summary-line">
                    <span>相对方</span
                    ><strong>{{
                      isSingleAgreementChecked
                        ? "单方协议，无相对方"
                        : counterpartySummaryText
                    }}</strong>
                  </div>
                </div>

                <div class="upload-block">
                  <div class="upload-label">合同正文</div>
                  <div
                    class="readonly-upload-box contract-body-entry"
                    :class="{ 'is-clickable': !isViewMode }"
                    @click="openBodyConfigModal"
                  >
                    <div class="contract-body-entry-name">
                      {{ contractBodyDisplayText }}
                    </div>
                    <div class="contract-body-entry-hint">
                      {{ contractBodyHint }}
                    </div>
                  </div>
                  <a-space
                    v-if="detailState.contractBodyFilePath"
                    style="margin-top: 8px"
                  >
                    <a-button
                      size="small"
                      :disabled="false"
                      @click="
                        downloadFile(
                          detailState.contractBodyFilePath,
                          detailState.contractBodyFileName || '合同正文.docx',
                        )
                      "
                    >
                      下载
                    </a-button>
                  </a-space>
                </div>

                <div v-if="!isSingleAgreementChecked" class="upload-block">
                  <div class="upload-label">签订依据附件</div>
                  <a-upload-dragger
                    :file-list="gistAttachmentList"
                    :before-upload="handleGistAttachmentUpload"
                    @remove="handleRemoveGistAttachment"
                    :disabled="isViewMode"
                    :show-upload-list="{ showRemoveIcon: !isViewMode }"
                    multiple
                  >
                    <div class="other-upload-text">
                      签订依据文件，对应法务 contractGistFile
                    </div>
                  </a-upload-dragger>
                  <div
                    v-if="detailState.contractGistFileList?.length"
                    class="download-list"
                  >
                    <a-space wrap>
                      <a-button
                        v-for="item in detailState.contractGistFileList"
                        :key="item.uid || item.filePath"
                        size="small"
                        :disabled="false"
                        @click="
                          downloadFile(
                            item.filePath,
                            item.name || item.fileName,
                          )
                        "
                      >
                        下载 {{ item.name || item.fileName }}
                      </a-button>
                    </a-space>
                  </div>
                </div>

                <div
                  v-if="!isSingleAgreementChecked && requiresAuthAttachment"
                  class="upload-block"
                >
                  <div class="upload-label">授权委托书附件</div>
                  <a-upload-dragger
                    :file-list="authAttachmentUploadList"
                    :before-upload="handleAuthAttachmentUpload"
                    @remove="handleRemoveAuthAttachment"
                    :disabled="isViewMode"
                    :show-upload-list="{ showRemoveIcon: !isViewMode }"
                    multiple
                  >
                    <div class="other-upload-text">
                      当前签订方式需要上传授权委托书附件
                    </div>
                  </a-upload-dragger>
                  <div
                    v-if="detailState.authAttachmentList?.length"
                    class="download-list"
                  >
                    <a-space wrap>
                      <a-button
                        v-for="item in detailState.authAttachmentList"
                        :key="item.uid || item.filePath"
                        size="small"
                        :disabled="false"
                        @click="
                          downloadFile(
                            item.filePath,
                            item.name || item.fileName,
                          )
                        "
                      >
                        下载 {{ item.name || item.fileName }}
                      </a-button>
                    </a-space>
                  </div>
                </div>

                <div class="upload-block">
                  <div class="upload-label">其他附件</div>
                  <a-upload-dragger
                    :file-list="otherAttachmentList"
                    :before-upload="handleOtherAttachmentUpload"
                    @remove="handleRemoveOtherAttachment"
                    :disabled="isViewMode"
                    :show-upload-list="{ showRemoveIcon: !isViewMode }"
                    multiple
                  >
                    <div class="other-upload-text">
                      其他附件(可上传多个文件)
                    </div>
                  </a-upload-dragger>
                  <div
                    v-if="detailState.otherAttachmentList?.length"
                    class="download-list"
                  >
                    <a-space wrap>
                      <a-button
                        v-for="item in detailState.otherAttachmentList"
                        :key="item.uid || item.filePath"
                        size="small"
                        :disabled="false"
                        @click="
                          downloadFile(
                            item.filePath,
                            item.name || item.fileName,
                          )
                        "
                      >
                        下载 {{ item.name || item.fileName }}
                      </a-button>
                    </a-space>
                  </div>
                </div>

                <div v-if="detailState.signedFilePath" class="upload-block">
                  <div class="upload-label">已签署文件</div>
                  <div class="readonly-upload-box">
                    <div class="contract-body-entry-name">
                      {{ detailState.signedFileName || "已签署文件" }}
                    </div>
                    <div class="contract-body-entry-hint">
                      当前合同的签署完成版本
                    </div>
                  </div>
                  <a-space style="margin-top: 8px">
                    <a-button
                      size="small"
                      :disabled="false"
                      @click="
                        downloadFile(
                          detailState.signedFilePath,
                          detailState.signedFileName || '已签署合同.pdf',
                        )
                      "
                    >
                      下载
                    </a-button>
                  </a-space>
                </div>

                <a-form-item class="tight-item" label="主要内容">
                  <a-textarea
                    v-model:value="detailState.primaryContent"
                    :rows="2"
                    placeholder="选填"
                  />
                </a-form-item>

                <a-form-item class="tight-item" label="正文/摘要">
                  <a-textarea
                    v-model:value="detailState.bodySummary"
                    :rows="2"
                    placeholder="选填，如不填则自动取主要内容"
                  />
                </a-form-item>

                <a-form-item
                  class="tight-item"
                  label="金额说明"
                  :required="['0', '3'].includes(detailState.valuationMode)"
                >
                  <a-textarea
                    v-model:value="detailState.amountExplain"
                    :rows="2"
                    placeholder='计价方式为"无金额"或"无法预估总价"时必填，说明金额相关情况'
                  />
                </a-form-item>

                <a-form-item class="tight-item" label="签订依据说明">
                  <a-textarea
                    v-model:value="detailState.contractGistRemark"
                    :rows="2"
                    placeholder="选填，签订本合同的依据说明（如：根据XXXX号文件）"
                  />
                </a-form-item>

                <div class="basic-title contract-attr-title">
                  <span class="dot"></span>
                  合同属性
                </div>

                <div class="field-grid field-grid-2">
                  <a-form-item class="tight-item" label="计价方式">
                    <a-input
                      :value="
                        getOptionLabel(
                          valuationModeOptions,
                          detailState.valuationMode,
                        )
                      "
                      readonly
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="标的额">
                    <a-input-number
                      v-model:value="detailState.contractAmount"
                      :min="0"
                      :precision="2"
                      :controls="false"
                      style="width: 100%"
                    />
                  </a-form-item>
                  <a-form-item
                    v-if="detailState.valuationMode === '2'"
                    class="tight-item"
                    label="预估金额"
                    required
                  >
                    <a-input-number
                      v-model:value="detailState.estimatedAmount"
                      :min="0"
                      :precision="2"
                      :controls="false"
                      style="width: 100%"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="币种">
                    <a-input
                      :value="
                        getOptionLabel(
                          currencyOptions,
                          detailState.currencyName,
                        )
                      "
                      readonly
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="签订方式">
                    <a-input
                      :value="
                        getOptionLabel(sealTypeOptions, detailState.sealType)
                      "
                      readonly
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="文本来源" required>
                    <a-select
                      v-model:value="detailState.textSource"
                      :options="textSourceOptions"
                      placeholder="请选择文本来源"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="合同来源" required>
                    <a-select
                      v-model:value="detailState.contractSource"
                      :options="contractSourceOptions"
                      placeholder="请选择合同来源"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="协助部门">
                    <a-input
                      v-model:value="detailState.assistDept"
                      placeholder="协助部门"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="合同类型" required>
                    <a-select
                      v-model:value="detailState.contractTypeCode"
                      :options="contractTypeOptions"
                      placeholder="请选择合同类型编码"
                      @change="handleContractTypeCodeChange"
                    />
                  </a-form-item>
                  <a-form-item
                    class="tight-item field-span-2"
                    label="合同执行人"
                    required
                  >
                    <a-input
                      v-model:value="detailState.executorAccount"
                      placeholder="合同执行人手机号"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="二级单位合同编码">
                    <a-input
                      v-model:value="detailState.selfCode"
                      placeholder="选填：二级单位内部合同编码，对应法务 selfCode"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="主合同编码">
                    <a-input
                      v-model:value="detailState.mainContractCode"
                      placeholder="选填：补录/变更时关联主合同编码，对应法务 mainContractCode"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="固定期限日期类型">
                    <a-select
                      v-model:value="detailState.dateType"
                      :options="dateTypeOptions"
                      placeholder="请选择"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="签约主体编码">
                    <a-input
                      :value="signingSubjectCodeText"
                      placeholder="根据我方主体自动汇总主体编码"
                      readonly
                    />
                  </a-form-item>
                  <a-form-item
                    v-if="detailState.isPublicTender"
                    class="tight-item"
                    label="关联招标号"
                    required
                  >
                    <a-input
                      v-model:value="detailState.biddingNo"
                      placeholder="公开招标时必填"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="开票时间">
                    <a-date-picker
                      v-model:value="detailState.invoiceDate"
                      value-format="YYYY-MM-DD"
                      style="width: 100%"
                      placeholder="选填，用于账单开票提醒"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="收款时间">
                    <a-date-picker
                      v-model:value="detailState.receiveDate"
                      value-format="YYYY-MM-DD"
                      style="width: 100%"
                      placeholder="选填，用于应收账款提醒"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="签订日期">
                    <a-date-picker
                      v-model:value="detailState.signDate"
                      value-format="YYYY-MM-DD"
                      style="width: 100%"
                      placeholder="签署完成前需补齐"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="用印时间">
                    <a-date-picker
                      v-model:value="detailState.sealDate"
                      value-format="YYYY-MM-DD"
                      style="width: 100%"
                      placeholder="签署完成前需补齐"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="合同生效日期">
                    <a-date-picker
                      v-model:value="detailState.activeDate"
                      value-format="YYYY-MM-DD"
                      style="width: 100%"
                      placeholder="必填，且必须在合同期限范围内"
                    />
                  </a-form-item>
                  <a-form-item class="tight-item" label="用印数量">
                    <a-input-number
                      v-model:value="detailState.sealQuantity"
                      :min="1"
                      :precision="0"
                      style="width: 100%"
                      placeholder="默认1份"
                    />
                  </a-form-item>
                </div>

                <div class="boolean-grid">
                  <div class="boolean-row">
                    <span>是否需修订条款</span>
                    <a-radio-group v-model:value="detailState.needAmendments">
                      <a-radio value="1">是</a-radio>
                      <a-radio value="0">否</a-radio>
                    </a-radio-group>
                  </div>
                  <div class="boolean-row">
                    <span>是否涉及港澳台</span>
                    <a-radio-group
                      v-model:value="detailState.isInvolveHongKongMacaoTaiwan"
                    >
                      <a-radio :value="true">是</a-radio>
                      <a-radio :value="false">否</a-radio>
                    </a-radio-group>
                  </div>
                  <div class="boolean-row">
                    <span>是否公开招标</span>
                    <a-radio-group v-model:value="detailState.isPublicTender">
                      <a-radio :value="true">是</a-radio>
                      <a-radio :value="false">否</a-radio>
                    </a-radio-group>
                  </div>
                  <div class="boolean-row">
                    <span>签约主体是否涉外</span>
                    <a-radio-group
                      v-model:value="detailState.isForeignRelatedSubject"
                    >
                      <a-radio :value="true">是</a-radio>
                      <a-radio :value="false">否</a-radio>
                    </a-radio-group>
                  </div>
                  <div class="boolean-row">
                    <span>是否列入当年预算</span>
                    <a-radio-group
                      v-model:value="detailState.isIncludedInCurrentYearBudget"
                    >
                      <a-radio :value="true">是</a-radio>
                      <a-radio :value="false">否</a-radio>
                    </a-radio-group>
                  </div>
                  <div class="boolean-row">
                    <span>是否共享</span>
                    <a-radio-group v-model:value="detailState.isShared">
                      <a-radio :value="true">是</a-radio>
                      <a-radio :value="false">否</a-radio>
                    </a-radio-group>
                  </div>
                  <div class="boolean-row">
                    <span>是否重大合同</span>
                    <a-radio-group v-model:value="detailState.isMajorContract">
                      <a-radio :value="true">是</a-radio>
                      <a-radio :value="false">否</a-radio>
                    </a-radio-group>
                  </div>
                  <div class="boolean-row">
                    <span>是否单方协议</span>
                    <a-radio-group
                      v-model:value="detailState.isSingleAgreement"
                    >
                      <a-radio value="1">是</a-radio>
                      <a-radio value="0">否</a-radio>
                    </a-radio-group>
                  </div>
                  <div class="boolean-row">
                    <span>是否用印</span>
                    <a-radio-group v-model:value="detailState.sealIsSeal">
                      <a-radio value="1">是</a-radio>
                      <a-radio value="0">否</a-radio>
                    </a-radio-group>
                  </div>
                  <div class="boolean-row">
                    <span>审批是否一致</span>
                    <a-radio-group v-model:value="detailState.sealApproveSame">
                      <a-radio value="1">是</a-radio>
                      <a-radio value="0">否</a-radio>
                    </a-radio-group>
                  </div>
                </div>

                <div class="extra-amount-section">
                  <div class="extra-label">额外金额</div>
                  <div class="extra-check-row">
                    <label
                      v-for="item in extraAmountFieldOptions"
                      :key="item.key"
                      class="extra-check-item"
                    >
                      <a-checkbox
                        :disabled="isViewMode"
                        v-model:checked="
                          detailState.extraAmounts[`${item.key}Enabled`]
                        "
                      >
                        {{ item.label }}
                      </a-checkbox>
                    </label>
                  </div>
                  <div class="extra-input-row">
                    <a-input-number
                      v-for="item in extraAmountFieldOptions"
                      :key="`${item.key}_input`"
                      v-model:value="detailState.extraAmounts[item.key]"
                      :disabled="
                        isViewMode ||
                        !detailState.extraAmounts[`${item.key}Enabled`]
                      "
                      :min="0"
                      :controls="false"
                      :precision="2"
                      :placeholder="`${item.label}金额`"
                      style="width: 24%"
                    />
                  </div>
                </div>

                <div class="plan-section">
                  <div class="basic-title contract-attr-title">
                    <span class="dot"></span>
                    履行计划
                  </div>
                  <div class="plan-toolbar">
                    <a-button
                      v-if="!isViewMode"
                      type="dashed"
                      size="small"
                      @click="addPerformancePlan"
                    >
                      <PlusOutlined />
                      新增履行计划
                    </a-button>
                  </div>
                  <a-table
                    :data-source="detailState.performancePlans"
                    :pagination="false"
                    size="small"
                    :scroll="{ x: 820 }"
                    row-key="id"
                    class="plan-table"
                  >
                    <a-table-column title="履行事项" key="planMatter">
                      <template #default="{ record }">
                        <a-input
                          v-model:value="record.planMatter"
                          placeholder="如：第一笔付款"
                        />
                      </template>
                    </a-table-column>
                    <a-table-column title="履约条件" key="planWhere">
                      <template #default="{ record }">
                        <a-input
                          v-model:value="record.planWhere"
                          placeholder="如：达到xx条件"
                        />
                      </template>
                    </a-table-column>
                    <a-table-column title="节点日期" key="planTime" width="140">
                      <template #default="{ record }">
                        <a-date-picker
                          v-model:value="record.planTime"
                          value-format="YYYY-MM-DD"
                          style="width: 100%"
                          placeholder="选择日期"
                        />
                      </template>
                    </a-table-column>
                    <a-table-column
                      title="提醒提前天数"
                      key="planDay"
                      width="130"
                    >
                      <template #default="{ record }">
                        <a-input-number
                          v-model:value="record.planDay"
                          :min="0"
                          :controls="false"
                          style="width: 100%"
                          placeholder="提前几天提醒"
                        />
                      </template>
                    </a-table-column>
                    <a-table-column
                      title="款项金额"
                      key="planMoney"
                      width="130"
                    >
                      <template #default="{ record }">
                        <a-input-number
                          v-model:value="record.planMoney"
                          :min="0"
                          :controls="false"
                          style="width: 100%"
                          placeholder="请输入金额"
                        />
                      </template>
                    </a-table-column>
                    <a-table-column
                      title="所属主体编码"
                      key="planSubjectCode"
                      width="160"
                    >
                      <template #default="{ record }">
                        <a-input
                          v-model:value="record.planSubjectCode"
                          placeholder="所属主体机构编码"
                        />
                      </template>
                    </a-table-column>
                    <a-table-column
                      title="所属主体"
                      key="planSubjectName"
                      width="180"
                    >
                      <template #default="{ record }">
                        <a-input
                          v-model:value="record.planSubjectName"
                          placeholder="所属主体名称"
                        />
                      </template>
                    </a-table-column>
                    <a-table-column title="备注" key="planDetail">
                      <template #default="{ record }">
                        <a-input
                          v-model:value="record.planDetail"
                          placeholder="选填备注"
                        />
                      </template>
                    </a-table-column>
                    <a-table-column
                      title="操作"
                      key="action"
                      width="80"
                      fixed="right"
                    >
                      <template #default="{ record }">
                        <a-button
                          type="link"
                          danger
                          size="small"
                          @click="removePerformancePlan(record.id)"
                        >
                          删除
                        </a-button>
                      </template>
                    </a-table-column>
                    <template #emptyText>
                      <div class="empty-plan-text">暂无数据</div>
                    </template>
                  </a-table>
                </div>
              </div>
            </div>
          </section>
        </a-form>
      </div>

      <div class="editor-footer">
        <a-space>
          <a-button v-if="!isViewMode" @click="resetCurrentState"
            >重置</a-button
          >
          <a-button @click="visible = false">{{
            isViewMode ? "关闭" : "取消"
          }}</a-button>
          <a-button v-if="!isViewMode" type="primary" @click="handleSave"
            >保存</a-button
          >
        </a-space>
      </div>
    </div>

    <ContractTemplateFillDemoModal
      ref="bodyConfigModalRef"
      @save="handleBodyConfigSave"
    />
  </a-modal>
</template>

<script setup>
import { computed, ref } from "vue";
import { message } from "ant-design-vue";
import { PlusOutlined, SearchOutlined } from "@ant-design/icons-vue";
import ContractTemplateFillDemoModal from "./ContractTemplateFillDemoModal.vue";
import {
  downloadLaunchFile,
  getLaunchSelfProfile,
  queryPartnerOptions,
  uploadLaunchFile,
} from "@/api/launch.js";
import {
  contractSourceOptions,
  contractTypeOptions,
  currencyOptions,
  dateTypeOptions,
  extraAmountFieldOptions,
  isElectronOptions,
  paymentDirectionOptions,
  sealTypeOptions,
  textSourceOptions,
  valuationModeOptions,
  getOptionLabel,
} from "../contractLaunchOptions.js";

const contractCompanyOptions = ref([]);

// ========== 合同相关字典 ==========

const mandateTypeOptions = [
  { label: "海铁联运委托", value: "海铁联运委托" },
  { label: "一站式物流委托", value: "一站式物流委托" },
  { label: "采购执行委托", value: "采购执行委托" },
  { label: "销售执行委托", value: "销售执行委托" },
];

const cargoTypeOptions = [
  { label: "燃料油", value: "燃料油" },
  { label: "港口设备", value: "港口设备" },
  { label: "软件服务", value: "软件服务" },
  { label: "综合物流", value: "综合物流" },
];

const paymentMethodOptions = [
  {
    label: "银行转账（30%预付款，70%验收后支付）",
    value: "银行转账（30%预付款，70%验收后支付）",
  },
  { label: "银行转账（按月结算）", value: "银行转账（按月结算）" },
  { label: "银行转账（一次性结清）", value: "银行转账（一次性结清）" },
];

const PARTY_ROLE_OUR = "OUR";
const PARTY_ROLE_OPPOSITE = "OPPOSITE";

function formatDateTimeValue(value, endOfDay = false) {
  if (!value) return null;
  if (String(value).includes(" ")) return value;
  return `${value} ${endOfDay ? "23:59:59" : "00:00:00"}`;
}

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 normalizeBooleanValue(value) {
  if (value === true || value === false) return value;
  if (value === 1 || value === "1" || value === "Y" || value === "true")
    return true;
  if (value === 0 || value === "0" || value === "N" || value === "false")
    return false;
  return false;
}

function pickBooleanSource(source = {}, key) {
  const mapping = {
    // needAmendments 改为 String 1/0，不再走 boolean 映射
    isInvolveHongKongMacaoTaiwan: [
      source.isInvolveHongKongMacaoTaiwan,
      source.isSgat,
    ],
    isPublicTender: [source.isPublicTender, source.isOpenBidding],
    isForeignRelatedSubject: [
      source.isForeignRelatedSubject,
      source.contractNature,
    ],
    isIncludedInCurrentYearBudget: [
      source.isIncludedInCurrentYearBudget,
      source.isYearBudget,
    ],
    isShared: [source.isShared, source.isShare],
    isMajorContract: [source.isMajorContract],
  };
  const values = mapping[key] || [];
  for (const value of values) {
    if (value !== undefined && value !== null && value !== "") {
      return value;
    }
  }
  return undefined;
}

function getFileNameFromDetail(source, category) {
  if (category === "body") {
    return source.contractBodyFileName || source.contractTextFileName || "";
  }
  return "";
}

function splitFilesByCategory(source = {}) {
  const sourceFiles = source.fileList || [];
  const bodyFile =
    sourceFiles.find((item) => item.fileCategory === "body") || {};

  return {
    contractBodyFileName:
      getFileNameFromDetail(source, "body") || 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),
    authAttachmentList: source.authAttachmentList?.length
      ? source.authAttachmentList.map(normalizeFileItem)
      : sourceFiles
          .filter((item) => item.fileCategory === "auth")
          .map(normalizeFileItem),
  };
}

function buildExtraAmountPayload(extraAmounts = {}) {
  const enabledItems = extraAmountFieldOptions.filter(
    (item) => extraAmounts[`${item.key}Enabled`],
  );
  const result = {
    ewAmountType: enabledItems.map((item) => item.code).join(","),
    djAmount: 0,
    yjAmount: 0,
    yfkAmount: 0,
    bzjAmount: 0,
  };

  enabledItems.forEach((item) => {
    result[item.amountField] = Number(extraAmounts[item.key] || 0);
  });

  return result;
}

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 createLocalId(prefix = "party") {
  return `${prefix}_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;
}

function createEmptyParty(roleCode, index = 0) {
  const isOur = roleCode === PARTY_ROLE_OUR;
  return {
    localId: createLocalId(isOur ? "our" : "opposite"),
    roleCode,
    roleName: isOur ? "我方" : "相对方",
    displayName: isOur ? `我方主体${index + 1}` : `相对方${index + 1}`,
    subjectCode: "",
    partnerId: null,
    customerSysid: null,
    oppositeId: "",
    companyName: "",
    companyType: "",
    oppositeCharacter: "",
    oppCharacter: "",
    contactName: "",
    contactPhone: "",
    contactEmail: "",
    contactAddress: "",
    registeredAddress: "",
    legalRepresentative: "",
    creditCode: "",
    bankName: "",
    bankAccount: "",
    naturalPersonIdType: "",
    naturalPersonIdNumber: "",
    isGroupInternal: false,
    isHongKongMacaoTaiwan: false,
  };
}

function mapLaunchPartyToFormParty(item = {}) {
  const roleCode =
    String(item.roleCode || PARTY_ROLE_OPPOSITE).toUpperCase() ===
    PARTY_ROLE_OUR
      ? PARTY_ROLE_OUR
      : PARTY_ROLE_OPPOSITE;
  return {
    ...createEmptyParty(roleCode),
    ...item,
    localId:
      item.localId ||
      createLocalId(roleCode === PARTY_ROLE_OUR ? "our" : "opposite"),
    roleCode,
    roleName: roleCode === PARTY_ROLE_OUR ? "我方" : "相对方",
    subjectCode: item.subjectCode || "",
    oppCharacter: item.oppositeCharacter || item.oppCharacter || "",
    oppositeCharacter: item.oppositeCharacter || item.oppCharacter || "",
    displayName: "",
    customerSysid: item.customerSysid || item.partnerId || null,
  };
}

function buildPartyStateFromList(list = []) {
  if (!Array.isArray(list) || !list.length) return null;
  const result = {
    ourPartyList: [],
    counterpartyList: [],
  };
  list.forEach((item) => {
    if (!item?.roleCode) return;
    const mapped = mapLaunchPartyToFormParty(item);
    if (mapped.roleCode === PARTY_ROLE_OUR) {
      result.ourPartyList.push(mapped);
    } else {
      result.counterpartyList.push(mapped);
    }
  });
  return normalizePartyState(result);
}

function isPartyFilled(party = {}) {
  const ignoreKeys = new Set([
    "localId",
    "roleCode",
    "roleName",
    "displayName",
  ]);
  return Object.entries(party).some(([key, value]) => {
    if (ignoreKeys.has(key)) return false;
    if (Array.isArray(value)) return value.length > 0;
    if (typeof value === "number") return !Number.isNaN(value);
    return String(value ?? "").trim() !== "";
  });
}

function normalizePartyArray(list = [], roleCode) {
  return (Array.isArray(list) ? list : [])
    .filter((party) => isPartyFilled(party))
    .map((party, index) => ({
      ...createEmptyParty(roleCode, index),
      ...party,
      localId:
        party.localId ||
        createLocalId(roleCode === PARTY_ROLE_OUR ? "our" : "opposite"),
      roleCode,
      roleName: roleCode === PARTY_ROLE_OUR ? "我方" : "相对方",
      displayName:
        roleCode === PARTY_ROLE_OUR
          ? `我方主体${index + 1}`
          : `相对方${index + 1}`,
    }));
}

function normalizePartyState(partyState) {
  const state = partyState || {};
  const ourPartyList = normalizePartyArray(
    Array.isArray(state.ourPartyList)
      ? state.ourPartyList
      : state.partyA
        ? [state.partyA]
        : [],
    PARTY_ROLE_OUR,
  );
  const normalizedOurList = ourPartyList.length
    ? ourPartyList
    : [
        {
          ...createEmptyParty(PARTY_ROLE_OUR, 0),
        },
      ];
  const counterpartyList = normalizePartyArray(
    state.counterpartyList,
    PARTY_ROLE_OPPOSITE,
  );
  return {
    partyA: { ...normalizedOurList[0] },
    ourPartyList: normalizedOurList,
    counterpartyList,
    isThreePartyContract: counterpartyList.length > 1,
  };
}

function applySubjectCodesToPartyState(partyState, signingSubjectCode) {
  const state = normalizePartyState(partyState);
  const codes = String(signingSubjectCode || "")
    .split(",")
    .map((item) => item.trim())
    .filter(Boolean);
  state.ourPartyList = state.ourPartyList.map((party, index) => ({
    ...party,
    subjectCode:
      party.subjectCode || (codes.length === 1 ? codes[0] : codes[index] || ""),
  }));
  state.partyA = state.ourPartyList[0] || createEmptyParty(PARTY_ROLE_OUR, 0);
  return state;
}

function copyPartyState(input) {
  const source = input || {};
  const listState = buildPartyStateFromList(source.partyList);
  if (listState) {
    return applySubjectCodesToPartyState(
      listState,
      source.signingSubjectCode ?? "",
    );
  }
  return normalizePartyState({
    ourPartyList: source.ourPartyList || (source.partyA ? [source.partyA] : []),
    counterpartyList: source.counterpartyList || [],
    isThreePartyContract: source.isThreePartyContract,
  });
}
/** 创建一条空的履行计划 */
function createPerformancePlan() {
  return {
    id: `plan_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`,
    planMatter: "",
    planWhere: "",
    planTime: "",
    planDay: 0,
    planMoney: "",
    planSubjectCode: "",
    planSubjectName: "",
    executorUnitId: null,
    planDetail: "",
  };
}

/** 创建空白的合同表单数据 */
function createContractFormData() {
  return {
    id: null,
    contractName: "",
    contractCode: "",
    contractType: undefined,
    contractTypeCode: undefined,
    contractAmount: null,
    estimatedAmount: null,
    effectiveStart: "",
    effectiveEnd: "",
    contractPeriodType: "0",
    periodExplain: "",
    paymentMethod: undefined,
    paymentDirection: "0",
    isElectron: "1",
    textSource: "standardText",
    contractSource: "ECP",
    valuationMode: undefined,
    currencyName: undefined,
    sealType: undefined,
    mandateType: undefined,
    cargoType: undefined,
    primaryContent: "",
    amountExplain: "",
    assistDept: "",
    executorAccount: "",
    selfCode: "",
    mainContractCode: "",
    dateType: "0",
    signingSubjectCode: "",
    contractGistRemark: "",
    bodySummary: "",
    invoiceDate: "",
    receiveDate: "",
    biddingNo: "",
    contractBodyFileName: "",
    contractBodyFilePath: "",
    contractBodyFileType: "",
    contractBodyFileSize: null,
    contractBodyArrayBuffer: null,
    otherAttachmentList: [],
    contractGistFileList: [],
    authAttachmentList: [],
    // 正文模板字段值统一放这里，后续新增模板字段时尽量只改 templateFieldList
    templateFieldValues: {},
    partyState: {
      isThreePartyContract: false,
      ourPartyList: [
        {
          ...createEmptyParty(PARTY_ROLE_OUR, 0),
        },
      ],
      counterpartyList: [],
    },
    isSingleAgreement: "0",
    isInvolveHongKongMacaoTaiwan: false,
    isPublicTender: false,
    isForeignRelatedSubject: false,
    isIncludedInCurrentYearBudget: false,
    isShared: false,
    isMajorContract: false,
    needAmendments: "0",
    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: "",
    sealDate: "",
    activeDate: "",
    sealQuantity: 1,
    sealIsSeal: "1",
    sealApproveSame: "1",
  };
}

/** 深拷贝合同表单数据 */
function copyContractFormData(data = {}) {
  const form = createContractFormData();
  const source = data || {};
  const fileState = splitFilesByCategory(source);
  Object.keys(form).forEach((key) => {
    if (key === "extraAmounts") {
      form.extraAmounts = restoreExtraAmounts(source, form.extraAmounts);
    } else if (key === "templateFieldValues") {
      if (
        source.templateFieldValues &&
        typeof source.templateFieldValues === "object"
      ) {
        form.templateFieldValues = { ...source.templateFieldValues };
      } else if (source.templateFieldValuesJson) {
        try {
          form.templateFieldValues = JSON.parse(source.templateFieldValuesJson);
        } catch (error) {
          form.templateFieldValues = {};
        }
      } else {
        form.templateFieldValues = {};
      }
    } else if (key === "performancePlans") {
      form.performancePlans = (source.performancePlans || []).map((item) => ({
        ...createPerformancePlan(),
        ...item,
      }));
    } else if (key === "partyState") {
      form.partyState = copyPartyState(source.partyState || source);
    } else if (key === "otherAttachmentList") {
      form.otherAttachmentList = fileState.otherAttachmentList;
    } else if (key === "contractGistFileList") {
      form.contractGistFileList = fileState.contractGistFileList;
    } else if (key === "authAttachmentList") {
      form.authAttachmentList = fileState.authAttachmentList;
    } else if (
      [
        "contractBodyFileName",
        "contractBodyFilePath",
        "contractBodyFileType",
        "contractBodyFileSize",
      ].includes(key)
    ) {
      form[key] = fileState[key] ?? form[key];
    } else if (key === "contractBodyArrayBuffer") {
      form.contractBodyArrayBuffer = source.contractBodyArrayBuffer
        ? source.contractBodyArrayBuffer instanceof ArrayBuffer
          ? source.contractBodyArrayBuffer.slice(0)
          : null
        : null;
    } else if (
      key === "signDate" ||
      key === "sealDate" ||
      key === "invoiceDate" ||
      key === "receiveDate" ||
      key === "activeDate"
    ) {
      form[key] = source[key] ? String(source[key]).slice(0, 10) : form[key];
    } else if (key === "assistDept") {
      form.assistDept =
        source.assistDept ?? source.assistDepartmentName ?? form.assistDept;
    } else if (key === "signingSubjectCode") {
      form.signingSubjectCode =
        source.signingSubjectCode ?? source.orgId ?? form.signingSubjectCode;
    } else if (key === "isSingleAgreement") {
      form.isSingleAgreement =
        source.isSingleAgreement ?? form.isSingleAgreement;
    } else if (
      [
        "isInvolveHongKongMacaoTaiwan",
        "isPublicTender",
        "isForeignRelatedSubject",
        "isIncludedInCurrentYearBudget",
        "isShared",
        "isMajorContract",
      ].includes(key)
    ) {
      form[key] = normalizeBooleanValue(pickBooleanSource(source, key));
    } else {
      form[key] = source[key] ?? form[key];
    }
  });
  if (source.textSource) {
    form.textSource = source.textSource;
  }
  if (source.contractTextFileName && !form.contractBodyFileName) {
    form.contractBodyFileName = source.contractTextFileName;
  }
  return form;
}

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

const visible = ref(false);
const modalMode = ref("create");
const creationMode = ref("template");
const templateKey = ref("");
const templateName = ref("");
const detailState = ref(createContractFormData());
const otherAttachmentList = ref([]);
const gistAttachmentList = ref([]);
const authAttachmentUploadList = ref([]);
const bodyConfigModalRef = ref(null);
const currentSelfParty = ref(null);

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

const isSingleAgreementChecked = computed(
  () => String(detailState.value.isSingleAgreement || "0") === "1",
);

const isOurPartyLocked = computed(() => isSingleAgreementChecked.value);

const ourParties = computed(
  () => detailState.value.partyState.ourPartyList || [],
);

const counterpartyParties = computed(
  () => detailState.value.partyState.counterpartyList || [],
);

const counterpartySummaryText = computed(() =>
  counterpartyParties.value.length
    ? counterpartyParties.value
        .map((party) => party.companyName || party.displayName)
        .join("；")
    : "未填写",
);

const ourPartySummaryText = computed(() =>
  ourParties.value.length
    ? ourParties.value
        .map((party) => party.companyName || party.displayName)
        .join("；")
    : "未填写",
);

const signingSubjectCodeText = computed(() =>
  ourParties.value
    .map((party) => String(party.subjectCode || "").trim())
    .filter(Boolean)
    .join(","),
);

const currentSelfSubjectCode = computed(() =>
  String(currentSelfParty.value?.subjectCode || "").trim(),
);

const contractBodyDisplayText = computed(
  () =>
    detailState.value.contractBodyFileName ||
    (creationMode.value === "manual"
      ? "点击上传合同正文 Word 文件"
      : templateName.value || "点击配置合同正文"),
);

const contractBodyHint = computed(() =>
  creationMode.value === "manual"
    ? "当前为手动创建，支持上传并预览 .docx 正文"
    : templateName.value
      ? `当前模板：${templateName.value}，点击继续维护模板信息和预览`
      : "当前为模板创建，点击维护模板信息和正文预览",
);

const requiresAuthAttachment = computed(() =>
  ["GZBSQRQZ", "HTZYZBSQRQZ"].includes(detailState.value.sealType),
);

function forceSingleAgreementDefaults() {
  if (!isSingleAgreementChecked.value || !currentSelfParty.value) return;
  detailState.value.isElectron = "0";
  detailState.value.sealType = "YWZ";
  detailState.value.partyState.ourPartyList = [
    {
      ...createEmptyParty(PARTY_ROLE_OUR, 0),
      ...currentSelfParty.value,
      localId:
        detailState.value.partyState.ourPartyList?.[0]?.localId ||
        createLocalId("our"),
    },
  ];
  detailState.value.partyState.counterpartyList = [];
  refreshPartyDisplayMeta();
}

function syncUploadLists() {
  otherAttachmentList.value = (detailState.value.otherAttachmentList || []).map(
    (item, index) => ({
      uid: item.uid || `other_${index + 1}`,
      name: item.name || item.fileName,
      size: item.size ?? item.fileSize,
      status: item.status || "done",
    }),
  );
  gistAttachmentList.value = (detailState.value.contractGistFileList || []).map(
    (item, index) => ({
      uid: item.uid || `gist_${index + 1}`,
      name: item.name || item.fileName,
      size: item.size ?? item.fileSize,
      status: item.status || "done",
    }),
  );
  authAttachmentUploadList.value = (
    detailState.value.authAttachmentList || []
  ).map((item, index) => ({
    uid: item.uid || `auth_${index + 1}`,
    name: item.name || item.fileName,
    size: item.size ?? item.fileSize,
    status: item.status || "done",
  }));
}

function syncCounterpartyState() {
  detailState.value.partyState = normalizePartyState(
    detailState.value.partyState,
  );
  refreshPartyDisplayMeta();
}

function refreshPartyDisplayMeta() {
  detailState.value.partyState.ourPartyList = (
    detailState.value.partyState.ourPartyList || []
  ).map((party, index) => ({
    ...party,
    roleCode: PARTY_ROLE_OUR,
    roleName: "我方",
    displayName: `我方主体${index + 1}`,
  }));
  detailState.value.partyState.counterpartyList = (
    detailState.value.partyState.counterpartyList || []
  ).map((party, index) => ({
    ...party,
    roleCode: PARTY_ROLE_OPPOSITE,
    roleName: "相对方",
    displayName: `相对方${index + 1}`,
  }));
  detailState.value.partyState.partyA =
    detailState.value.partyState.ourPartyList[0] ||
    createEmptyParty(PARTY_ROLE_OUR, 0);
  detailState.value.partyState.isThreePartyContract =
    detailState.value.partyState.counterpartyList.length > 1;
  detailState.value.signingSubjectCode = signingSubjectCodeText.value;
}

function resetCurrentState() {
  creationMode.value = "template";
  templateKey.value = "";
  templateName.value = "";
  detailState.value = createContractFormData();
  syncCounterpartyState();
  syncUploadLists();
}

function showSearchTip(title) {
  loadPartnerOptions();
  message.info(
    `${title}数据已从客户/主体库加载；相对方下拉仅展示已同步到法务的记录。`,
  );
}

function filterPartnerOption(input, option) {
  const keyword = String(input || "")
    .trim()
    .toLowerCase();
  if (!keyword) return true;
  return [
    option?.label,
    option?.value,
    option?.raw?.oppositeCode,
    option?.raw?.oppositeId,
  ]
    .filter(Boolean)
    .some((item) => String(item).toLowerCase().includes(keyword));
}

function getRelativeTypeLabel(value) {
  const map = {
    client: "客户",
    supplier: "供应商",
    Partner: "合作伙伴",
    clientAndSup: "客户及供应商",
    other: "其他",
  };
  return map[value] || value || "";
}

function isSyncedCounterpartyOption(option) {
  const raw = option?.raw || {};
  return raw.syncStatus === "synced" && !!String(raw.oppositeId || "").trim();
}

function mapPartnerToParty(raw = {}, roleKey, companyName) {
  const firstContact = raw.contacts?.[0] || {};
  const roleCode =
    roleKey === PARTY_ROLE_OUR ? PARTY_ROLE_OUR : PARTY_ROLE_OPPOSITE;
  return {
    ...createEmptyParty(roleCode),
    partnerId: raw.customerSysid || null,
    customerSysid: raw.customerSysid || null,
    subjectCode: raw.orgId == null ? "" : String(raw.orgId),
    oppositeId: raw.oppositeId || "",
    companyName: raw.customerNameC || companyName || "",
    companyType: getRelativeTypeLabel(raw.relativeType),
    oppositeCharacter: raw.oppositeCharacter || "",
    oppCharacter: raw.oppositeCharacter || "",
    creditCode: raw.customerTaxno || "",
    legalRepresentative: raw.legalPersonName || "",
    registeredAddress: raw.customerRegaddr || "",
    contactName: firstContact.contactName || "",
    contactPhone: firstContact.contactPhone || raw.officephone || "",
    contactEmail: firstContact.email || raw.emailAddress || "",
    contactAddress: firstContact.postalAddress || raw.customerAddress || "",
    bankName: raw.customerBank || "",
    bankAccount: raw.customerBankaccount || "",
    naturalPersonIdType: raw.certificateType || "",
    naturalPersonIdNumber: raw.certificateNum || "",
    isGroupInternal: raw.groupFlag === "Y" || raw.isJtn === "1",
    isHongKongMacaoTaiwan: raw.gatFlag === "Y" || raw.isSgat === "1",
    roleCode,
    roleName: roleCode === PARTY_ROLE_OUR ? "我方" : "相对方",
  };
}

function getPartyOptions(roleCode) {
  const baseOptions = contractCompanyOptions.value || [];
  if (roleCode !== PARTY_ROLE_OUR) {
    return baseOptions.filter(isSyncedCounterpartyOption);
  }
  const selfParty = currentSelfParty.value;
  if (!selfParty?.companyName) {
    return baseOptions;
  }
  const exists = baseOptions.some(
    (item) => item.value === selfParty.companyName,
  );
  if (exists) {
    return baseOptions;
  }
  return [
    {
      label: `${selfParty.companyName}（当前主体）`,
      value: selfParty.companyName,
      raw: {
        customerNameC: selfParty.companyName,
        orgId: selfParty.subjectCode,
        officephone: selfParty.contactPhone,
        emailAddress: selfParty.contactEmail,
        customerAddress: selfParty.contactAddress,
        customerRegaddr: selfParty.registeredAddress,
        customerBank: selfParty.bankName,
        customerBankaccount: selfParty.bankAccount,
        legalPersonName: selfParty.legalRepresentative,
        customerTaxno: selfParty.creditCode,
      },
    },
    ...baseOptions,
  ];
}

function handleCompanySelect(party, companyName, option = {}) {
  if (!party) return;

  const rawPartner =
    option?.raw ||
    contractCompanyOptions.value.find((item) => item.value === companyName)
      ?.raw;
  if (rawPartner) {
    Object.assign(
      party,
      mapPartnerToParty(rawPartner, party.roleCode, companyName),
    );
    refreshPartyDisplayMeta();
    return;
  }

  party.companyName = companyName;
  refreshPartyDisplayMeta();
}

function handleAddOurParty() {
  if (isOurPartyLocked.value) return;
  detailState.value.partyState.ourPartyList =
    detailState.value.partyState.ourPartyList || [];
  detailState.value.partyState.ourPartyList.push(
    createEmptyParty(
      PARTY_ROLE_OUR,
      detailState.value.partyState.ourPartyList.length,
    ),
  );
  refreshPartyDisplayMeta();
}

function handleContractTypeChange(value) {
  detailState.value.contractTypeCode = value;
}

function handleContractTypeCodeChange(value) {
  detailState.value.contractType = value;
}

function handleAddCounterparty() {
  if (isSingleAgreementChecked.value) return;
  detailState.value.partyState.counterpartyList =
    detailState.value.partyState.counterpartyList || [];
  detailState.value.partyState.counterpartyList.push(
    createEmptyParty(
      PARTY_ROLE_OPPOSITE,
      detailState.value.partyState.counterpartyList.length,
    ),
  );
  refreshPartyDisplayMeta();
}

function handleRemoveOurParty(localId) {
  if (isOurPartyLocked.value) return;
  detailState.value.partyState.ourPartyList = (
    detailState.value.partyState.ourPartyList || []
  ).filter((item) => item.localId !== localId);
  if (!detailState.value.partyState.ourPartyList.length) {
    detailState.value.partyState.ourPartyList = [
      createEmptyParty(PARTY_ROLE_OUR, 0),
    ];
  }
  refreshPartyDisplayMeta();
}

function handleRemoveCounterparty(localId) {
  if (isSingleAgreementChecked.value) return;
  detailState.value.partyState.counterpartyList = (
    detailState.value.partyState.counterpartyList || []
  ).filter((item) => item.localId !== localId);
  refreshPartyDisplayMeta();
}

function handleOtherAttachmentUpload(file) {
  return uploadAttachmentFile(file, "other");
}

function handleGistAttachmentUpload(file) {
  return uploadAttachmentFile(file, "gist");
}

function handleAuthAttachmentUpload(file) {
  return uploadAttachmentFile(file, "auth");
}

function uploadAttachmentFile(file, category) {
  const formData = new FormData();
  formData.append("file", file);
  uploadLaunchFile(formData).then((res) => {
    if (String(res?.code || "") !== "0000") {
      message.error(res?.message || res?.msg || "附件上传失败");
      return;
    }
    const item = {
      uid: `other_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`,
      name: file.name,
      size: file.size,
      status: "done",
      filePath: res?.result || "",
      fileType: file.name.split(".").pop()?.toLowerCase() || "",
      fileCategory: category,
    };
    if (category === "gist") {
      detailState.value.contractGistFileList.push(item);
      gistAttachmentList.value.push(item);
    } else if (category === "auth") {
      detailState.value.authAttachmentList.push(item);
      authAttachmentUploadList.value.push(item);
    } else {
      detailState.value.otherAttachmentList.push(item);
      otherAttachmentList.value.push(item);
    }
    message.success(`已添加附件：${file.name}`);
  });
  return false;
}

function handleRemoveOtherAttachment(file) {
  detailState.value.otherAttachmentList =
    detailState.value.otherAttachmentList.filter(
      (item) => item.uid !== file.uid && item.name !== file.name,
    );
  otherAttachmentList.value = otherAttachmentList.value.filter(
    (item) => item.uid !== file.uid && item.name !== file.name,
  );
}

function handleRemoveGistAttachment(file) {
  detailState.value.contractGistFileList =
    detailState.value.contractGistFileList.filter(
      (item) => item.uid !== file.uid && item.name !== file.name,
    );
  gistAttachmentList.value = gistAttachmentList.value.filter(
    (item) => item.uid !== file.uid && item.name !== file.name,
  );
}

function handleRemoveAuthAttachment(file) {
  detailState.value.authAttachmentList =
    detailState.value.authAttachmentList.filter(
      (item) => item.uid !== file.uid && item.name !== file.name,
    );
  authAttachmentUploadList.value = authAttachmentUploadList.value.filter(
    (item) => item.uid !== file.uid && item.name !== file.name,
  );
}

async function downloadFile(filePath, fileName = "附件") {
  if (!filePath) {
    message.warning("文件路径为空，无法下载");
    return;
  }
  try {
    const resp = await downloadLaunchFile(filePath);
    const arrayBuffer = resp?.data ?? resp;
    const blob = new Blob([arrayBuffer]);
    const url = window.URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.href = url;
    link.download = fileName || "附件";
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    window.URL.revokeObjectURL(url);
  } catch (error) {
    message.error("文件下载失败");
  }
}

function openBodyConfigModal() {
  bodyConfigModalRef.value?.showModal(
    {
      detailState: copyContractFormData(detailState.value),
      creationMode: creationMode.value,
      templateKey: templateKey.value,
      templateName: templateName.value,
    },
    isViewMode.value ? "view" : "edit",
  );
}

function handleBodyConfigSave(payload = {}) {
  creationMode.value = payload.creationMode || "template";
  templateKey.value = payload.templateKey || "";
  templateName.value = payload.templateName || "";
  detailState.value = copyContractFormData(payload.detailState);
  syncCounterpartyState();
  syncUploadLists();

  if (modalMode.value === "edit" && detailState.value.id) {
    const errorMessage = validateState();
    if (errorMessage) {
      message.success(
        "合同正文配置已保存到当前页面，请继续完善主信息后再保存合同草稿。",
      );
      return;
    }

    syncCounterpartyState();
    detailState.value.contractAmount =
      detailState.value.contractAmount === null ||
      detailState.value.contractAmount === undefined ||
      detailState.value.contractAmount === ""
        ? null
        : Number(detailState.value.contractAmount);

    emit("save", {
      mode: modalMode.value,
      record: buildLaunchRecord(),
      successMessage: "合同正文配置已保存",
    });
    return;
  }

  message.success(
    "合同正文配置已保存到当前页面，请继续点击底部“保存”提交合同草稿。",
  );
}

function addPerformancePlan() {
  detailState.value.performancePlans.push(createPerformancePlan());
}

function removePerformancePlan(id) {
  detailState.value.performancePlans =
    detailState.value.performancePlans.filter((item) => item.id !== id);
}

function validateState() {
  if (
    !ourParties.value.length ||
    ourParties.value.some((party) => !party.companyName?.trim())
  )
    return "请确认我方信息";
  if (
    currentSelfSubjectCode.value &&
    !ourParties.value.some(
      (party) =>
        String(party.subjectCode || "").trim() === currentSelfSubjectCode.value,
    )
  ) {
    return "当前登录主体必须包含在我方主体中";
  }
  if (
    isSingleAgreementChecked.value &&
    currentSelfSubjectCode.value &&
    ourParties.value.some(
      (party) =>
        String(party.subjectCode || "").trim() !== currentSelfSubjectCode.value,
    )
  ) {
    return "单方协议仅允许使用当前登录主体";
  }
  if (!isSingleAgreementChecked.value && !counterpartyParties.value.length)
    return "请至少添加一个相对方";
  if (
    !isSingleAgreementChecked.value &&
    counterpartyParties.value.some((party) => !party.companyName?.trim())
  )
    return "请选择相对方";
  if (!detailState.value.contractName?.trim()) return "请填写合同名称";
  if (!detailState.value.contractType) return "请选择合同类型";
  if (
    detailState.value.contractPeriodType === "0" &&
    (!detailState.value.effectiveStart || !detailState.value.effectiveEnd)
  )
    return "请选择固定期限起止日期";
  if (
    detailState.value.contractPeriodType === "1" &&
    !detailState.value.periodExplain?.trim()
  )
    return "请填写期限说明";
  if (!detailState.value.activeDate) return "请选择合同生效日期";
  if (
    detailState.value.contractPeriodType === "0" &&
    detailState.value.effectiveStart &&
    detailState.value.effectiveEnd &&
    (detailState.value.activeDate < detailState.value.effectiveStart ||
      detailState.value.activeDate > detailState.value.effectiveEnd)
  )
    return "合同生效日期必须在合同期限范围内";
  if (!detailState.value.valuationMode) return "请选择计价方式";
  // 固定总价时标的额必填
  if (
    detailState.value.valuationMode === "1" &&
    (detailState.value.contractAmount === null ||
      detailState.value.contractAmount === undefined ||
      detailState.value.contractAmount === "")
  )
    return "请填写合同标的额";
  // 预估总价时预估金额必填
  if (
    detailState.value.valuationMode === "2" &&
    (detailState.value.estimatedAmount === null ||
      detailState.value.estimatedAmount === undefined ||
      detailState.value.estimatedAmount === "")
  )
    return "请填写预估金额";
  // 无金额或无法预估总价时金额说明必填
  if (
    (detailState.value.valuationMode === "0" ||
      detailState.value.valuationMode === "3") &&
    !detailState.value.amountExplain?.trim()
  )
    return "请填写金额说明";
  if (!detailState.value.currencyName) return "请选择币种";
  if (!detailState.value.sealType) return "请选择签订方式";
  if (!detailState.value.textSource) return "请选择文本来源";
  // needAmendments 必填（文档要求）
  if (
    !detailState.value.needAmendments ||
    detailState.value.needAmendments === ""
  )
    return "请选择是否需修订条款";
  if (!detailState.value.paymentDirection) return "请选择收支方向";
  if (!detailState.value.contractTypeCode) return "请选择合同类型编码";
  if (!detailState.value.executorAccount?.trim()) return "请填写合同执行人";
  if (detailState.value.isPublicTender && !detailState.value.biddingNo?.trim())
    return "请填写关联招标号";
  if (!detailState.value.contractBodyFileName?.trim())
    return "请先配置合同正文";
  if (
    !isSingleAgreementChecked.value &&
    requiresAuthAttachment.value &&
    !(detailState.value.authAttachmentList || []).length
  )
    return "请上传授权委托书附件";
  if (!(detailState.value.performancePlans || []).length)
    return "请至少添加一条履行计划";
  for (const plan of detailState.value.performancePlans || []) {
    if (!plan.planMatter?.trim()) return "请填写履行计划的履行事项";
    if (!plan.planWhere?.trim()) return "请填写履行计划的履约条件";
    if (!plan.planTime) return "请选择履行计划的节点日期";
    if (
      plan.planDay === null ||
      plan.planDay === undefined ||
      plan.planDay === ""
    )
      return "请填写履行计划的提醒提前天数";
    if (
      !["0", "3"].includes(detailState.value.valuationMode) &&
      (plan.planMoney === null ||
        plan.planMoney === undefined ||
        plan.planMoney === "")
    )
      return "请填写履行计划的款项金额";
  }
  return "";
}

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

  syncCounterpartyState();
  detailState.value.contractAmount =
    detailState.value.contractAmount === null ||
    detailState.value.contractAmount === undefined ||
    detailState.value.contractAmount === ""
      ? null
      : Number(detailState.value.contractAmount);
  detailState.value.estimatedAmount =
    detailState.value.estimatedAmount === null ||
    detailState.value.estimatedAmount === undefined ||
    detailState.value.estimatedAmount === ""
      ? null
      : Number(detailState.value.estimatedAmount);

  const record = buildLaunchRecord();
  emit("save", {
    mode: modalMode.value,
    record,
  });
  visible.value = false;
  message.success("合同方信息已保存");
}

function buildLaunchRecord() {
  const partyList = [];
  ourParties.value.forEach((party) => {
    partyList.push({
      roleCode: PARTY_ROLE_OUR,
      roleName: "我方",
      subjectCode: party.subjectCode || "",
      partnerId: party.partnerId || party.customerSysid || null,
      oppositeId: party.oppositeId,
      companyName: party.companyName,
      companyType: party.companyType,
      oppositeCharacter: party.oppCharacter || "1",
      contactName: party.contactName,
      contactPhone: party.contactPhone,
      contactEmail: party.contactEmail,
      contactAddress: party.contactAddress,
      registeredAddress: party.registeredAddress,
      legalRepresentative: party.legalRepresentative,
      creditCode: party.creditCode,
      bankName: party.bankName,
      bankAccount: party.bankAccount,
      naturalPersonIdType: party.naturalPersonIdType,
      naturalPersonIdNumber: party.naturalPersonIdNumber,
      isGroupInternal: party.isGroupInternal ? "1" : "0",
      isHongKongMacaoTaiwan: party.isHongKongMacaoTaiwan ? "1" : "0",
    });
  });

  if (!isSingleAgreementChecked.value) {
    counterpartyParties.value.forEach((party) => {
      partyList.push({
        roleCode: PARTY_ROLE_OPPOSITE,
        roleName: "相对方",
        partnerId: party.partnerId || party.customerSysid || null,
        oppositeId: party.oppositeId,
        companyName: party.companyName,
        companyType: party.companyType,
        oppositeCharacter: party.oppCharacter || "1",
        contactName: party.contactName,
        contactPhone: party.contactPhone,
        contactEmail: party.contactEmail,
        contactAddress: party.contactAddress,
        registeredAddress: party.registeredAddress,
        legalRepresentative: party.legalRepresentative,
        creditCode: party.creditCode,
        bankName: party.bankName,
        bankAccount: party.bankAccount,
        naturalPersonIdType: party.naturalPersonIdType,
        naturalPersonIdNumber: party.naturalPersonIdNumber,
        isGroupInternal: party.isGroupInternal ? "1" : "0",
        isHongKongMacaoTaiwan: party.isHongKongMacaoTaiwan ? "1" : "0",
      });
    });
  }

  const fileList = [];
  if (detailState.value.contractBodyFileName) {
    fileList.push({
      fileCategory: "body",
      fileName: detailState.value.contractBodyFileName,
      filePath: detailState.value.contractBodyFilePath || "",
      fileType: detailState.value.contractBodyFileType || "docx",
      fileSize: detailState.value.contractBodyFileSize || null,
    });
  }
  (detailState.value.contractGistFileList || []).forEach((item) => {
    fileList.push({
      fileCategory: "gist",
      fileName: item.name || item.fileName,
      filePath: item.filePath || "",
      fileType: item.fileType || "",
      fileSize: item.size ?? item.fileSize ?? 0,
    });
  });
  (detailState.value.authAttachmentList || []).forEach((item) => {
    fileList.push({
      fileCategory: "auth",
      fileName: item.name || item.fileName,
      filePath: item.filePath || "",
      fileType: item.fileType || "",
      fileSize: item.size ?? item.fileSize ?? 0,
    });
  });
  (detailState.value.otherAttachmentList || []).forEach((item) => {
    fileList.push({
      fileCategory: "other",
      fileName: item.name || item.fileName,
      filePath: item.filePath || "",
      fileType: item.fileType || "",
      fileSize: item.size ?? item.fileSize ?? 0,
    });
  });

  const extraAmountPayload = buildExtraAmountPayload(
    detailState.value.extraAmounts,
  );

  return {
    id: detailState.value.id || null,
    contractCode: detailState.value.contractCode,
    contractName: detailState.value.contractName,
    contractType: getOptionLabel(
      contractTypeOptions,
      detailState.value.contractType,
    ),
    contractTypeCode:
      detailState.value.contractTypeCode || detailState.value.contractType,
    contractSource: detailState.value.contractSource || "ECP",
    creationMode: creationMode.value,
    templateId: templateKey.value || null,
    templateName: templateName.value || "",
    contractAmount: detailState.value.contractAmount,
    currencyName: detailState.value.currencyName,
    contractPeriodType: detailState.value.contractPeriodType,
    periodExplain: detailState.value.periodExplain,
    effectiveStart: formatDateTimeValue(detailState.value.effectiveStart),
    effectiveEnd: formatDateTimeValue(detailState.value.effectiveEnd, true),
    paymentMethod: detailState.value.paymentMethod,
    primaryContent: detailState.value.primaryContent,
    amountExplain: detailState.value.amountExplain,
    valuationMode: detailState.value.valuationMode,
    needAmendments: detailState.value.needAmendments,
    estimatedAmount:
      detailState.value.valuationMode === "2"
        ? detailState.value.estimatedAmount
        : null,
    sealType: detailState.value.sealType,
    textSource:
      detailState.value.textSource ||
      (creationMode.value === "manual" ? "draftBySelf" : "standardText"),
    mandateType: detailState.value.mandateType,
    cargoType: detailState.value.cargoType,
    assistDepartmentName: detailState.value.assistDept,
    executorAccount: detailState.value.executorAccount,
    creatorPhone: detailState.value.executorAccount,
    selfCode: detailState.value.selfCode || "",
    mainContractCode: detailState.value.mainContractCode || "",
    dateType: detailState.value.dateType || "0",
    isAddit: detailState.value.mainContractCode ? 1 : 2,
    isElectron: detailState.value.isElectron || "1",
    isSingleAgreement: detailState.value.isSingleAgreement || "0",
    paymentDirection: detailState.value.paymentDirection || "0",
    isMajorContract: detailState.value.isMajorContract ? 1 : 0,
    isOpenBidding: detailState.value.isPublicTender ? 1 : 0,
    isYearBudget: detailState.value.isIncludedInCurrentYearBudget ? 1 : 0,
    isShare: detailState.value.isShared ? "1" : "0",
    contractNature: detailState.value.isForeignRelatedSubject ? "1" : "0",
    isSgat: detailState.value.isInvolveHongKongMacaoTaiwan ? "1" : "0",
    biddingNo: detailState.value.isPublicTender
      ? detailState.value.biddingNo || ""
      : "",
    contractGistRemark: detailState.value.contractGistRemark || "",
    bodySummary:
      detailState.value.bodySummary || detailState.value.primaryContent || "",
    contractTextFileName: detailState.value.contractBodyFileName || "",
    contractTextFilePath: detailState.value.contractBodyFilePath || "",
    contractTextFileType: detailState.value.contractBodyFileType || "docx",
    contractTextFileSize: detailState.value.contractBodyFileSize || null,
    signDate: formatDateTimeValue(detailState.value.signDate),
    sealDate: formatDateTimeValue(detailState.value.sealDate),
    invoiceDate: formatDateTimeValue(detailState.value.invoiceDate),
    receiveDate: formatDateTimeValue(detailState.value.receiveDate),
    activeDate: formatDateTimeValue(detailState.value.activeDate),
    sealQuantity: detailState.value.sealQuantity,
    sealIsSeal: detailState.value.sealIsSeal || "1",
    sealApproveSame: detailState.value.sealApproveSame || "1",
    templateFieldValuesJson: JSON.stringify(
      detailState.value.templateFieldValues || {},
    ),
    remindDays: 3,
    signingSubjectCode:
      signingSubjectCodeText.value ||
      detailState.value.signingSubjectCode ||
      "",
    extraAmounts: { ...detailState.value.extraAmounts },
    ...extraAmountPayload,
    partyList,
    fileList,
    performancePlans: (detailState.value.performancePlans || []).map(
      (item) => ({
        planMatter: item.planMatter,
        planWhere: item.planWhere,
        planTime: formatDateTimeValue(item.planTime),
        planDay: item.planDay,
        planMoney: item.planMoney,
        planSubjectCode: item.planSubjectCode,
        planSubjectName: item.planSubjectName,
        executorUnitId: item.executorUnitId || null,
        planDetail: item.planDetail,
        planStatus: "0",
      }),
    ),
  };
}

function applySelfProfile(profile = {}) {
  const firstOurParty =
    detailState.value.partyState.ourPartyList?.[0] ||
    createEmptyParty(PARTY_ROLE_OUR, 0);
  currentSelfParty.value = {
    ...createEmptyParty(PARTY_ROLE_OUR, 0),
    companyName: profile.orgName || "",
    subjectCode: profile.id || "",
    contactPhone: profile.contactPhone || "",
    contactEmail: profile.contactEmail || "",
    contactAddress: profile.contactAddress || "",
    registeredAddress: profile.registeredAddress || "",
    bankName: profile.bankName || "",
    bankAccount: profile.bankAccount || "",
    legalRepresentative: profile.legalRepresentative || "",
    creditCode: profile.creditCode || "",
  };
  const shouldHydrateFirstParty =
    (!firstOurParty.companyName && !firstOurParty.subjectCode) ||
    firstOurParty.companyName === profile.orgName ||
    (profile.id && firstOurParty.subjectCode === profile.id);
  if (!shouldHydrateFirstParty) {
    return;
  }
  detailState.value.partyState.ourPartyList = [
    {
      ...firstOurParty,
      companyName: profile.orgName || firstOurParty.companyName,
      subjectCode: profile.id || firstOurParty.subjectCode,
      contactPhone: profile.contactPhone || firstOurParty.contactPhone,
      contactEmail: profile.contactEmail || firstOurParty.contactEmail,
      contactAddress: profile.contactAddress || firstOurParty.contactAddress,
      registeredAddress:
        profile.registeredAddress || firstOurParty.registeredAddress,
      bankName: profile.bankName || firstOurParty.bankName,
      bankAccount: profile.bankAccount || firstOurParty.bankAccount,
      legalRepresentative:
        profile.legalRepresentative || firstOurParty.legalRepresentative,
      creditCode: profile.creditCode || firstOurParty.creditCode,
    },
    ...(detailState.value.partyState.ourPartyList || []).slice(1),
  ];
  refreshPartyDisplayMeta();
  forceSingleAgreementDefaults();
}

async function showModal(context = {}, mode = "create") {
  modalMode.value = mode || "create";
  const source =
    context.detailState ||
    (Object.keys(context || {}).length ? context : createContractFormData());

  creationMode.value =
    context.creationMode ||
    source.creationMode ||
    (source.contractBodyArrayBuffer || source.contractBodyFilePath
      ? "manual"
      : "template");
  templateKey.value = context.templateKey || source.templateId || "";
  templateName.value = context.templateName || source.templateName || "";
  detailState.value = copyContractFormData(source);
  const selfProfile = await getLaunchSelfProfile();
  applySelfProfile(selfProfile || {});
  forceSingleAgreementDefaults();
  syncCounterpartyState();
  syncUploadLists();
  loadPartnerOptions();
  visible.value = true;
}

function loadPartnerOptions() {
  queryPartnerOptions().then((list) => {
    const options = (list || []).map((item) => ({
      label: `${item.customerNameC || "未命名相对方"}${item.oppositeId ? `（${item.oppositeId}）` : ""}`,
      value: item.customerNameC,
      raw: item,
    }));
    contractCompanyOptions.value = options;
  });
}

defineExpose({
  showModal,
});
</script>

<style scoped>
.contract-main-editor {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: #f6f8fb;
}

.editor-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 24px 14px;
  background: #fff;
  border-bottom: 1px solid #e5e7eb;
}

.editor-header h3 {
  font-size: 20px;
  font-weight: 700;
  color: #0f172a;
  margin-bottom: 6px;
}

.editor-body {
  flex: 1;
  overflow-y: auto;
  padding: 18px 20px;
}

.editor-form {
  height: 100%;
}

.main-grid {
  display: grid;
  grid-template-columns: 470px minmax(0, 1fr);
  gap: 18px;
}

.section-card {
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 16px 18px;
}

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

.counterparty-toolbar {
  margin-top: 18px;
  margin-bottom: 10px;
}

.section-title,
.basic-title {
  font-size: 16px;
  font-weight: 700;
  color: #111827;
}

.basic-header {
  border-bottom: 1px solid #eef2f7;
  padding-bottom: 10px;
  margin-bottom: 14px;
}

.basic-title {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.dot {
  width: 10px;
  height: 10px;
  background: #2563eb;
  border-radius: 2px;
}

.party-block + .party-block {
  margin-top: 14px;
}

.party-block-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.field-label {
  font-size: 13px;
  color: #334155;
  margin-bottom: 6px;
}

.required::before {
  content: "*";
  color: #ef4444;
  margin-right: 4px;
}

.search-input-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 34px;
  gap: 6px;
  margin-bottom: 8px;
}

.icon-btn,
.mini-search-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #2563eb;
}

.text-action-btn {
  padding-inline: 0;
}

.party-info-box {
  position: relative;
  background: #fafbff;
  border: 1px solid #edf2f7;
  border-radius: 6px;
  padding: 10px 38px 10px 12px;
  margin-bottom: 8px;
}

.info-line {
  display: flex;
  gap: 10px;
  font-size: 12px;
  line-height: 1.8;
}

.info-line span {
  width: 62px;
  flex-shrink: 0;
  color: #475569;
}

.info-line strong {
  color: #0f172a;
  font-weight: 500;
}

.mini-search-btn {
  position: absolute;
  top: 8px;
  right: 8px;
}

.invoice-btn {
  width: 100%;
  justify-content: flex-start;
}

.party-empty-state {
  padding: 10px 12px;
  border: 1px dashed #dbe3ee;
  border-radius: 8px;
  background: #f8fbff;
  color: #64748b;
  font-size: 13px;
}

.field-grid {
  display: grid;
  gap: 10px 14px;
}

.field-grid-2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.field-grid-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.field-span-2 {
  grid-column: span 2;
}

.field-span-3 {
  grid-column: span 3;
}

.tight-item {
  margin-bottom: 4px;
}

.period-block {
  margin-top: 10px;
  margin-bottom: 12px;
}

.period-row {
  display: flex;
  justify-content: space-between;
  margin-top: 8px;
}

.period-explain-input {
  margin-top: 8px;
}

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

.summary-line {
  display: flex;
  gap: 10px;
  font-size: 13px;
}

.summary-line span {
  width: 62px;
  flex-shrink: 0;
  color: #475569;
}

.summary-line strong {
  color: #0f172a;
  font-weight: 600;
}

.upload-block {
  margin-bottom: 14px;
}

.upload-label {
  font-size: 13px;
  color: #334155;
  margin-bottom: 6px;
}

.readonly-upload-box {
  min-height: 44px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  flex-direction: column;
  padding: 10px 12px;
  border: 1px dashed #dbe3ee;
  border-radius: 8px;
  background: #f8fbff;
  color: #475569;
  font-size: 13px;
}

.contract-body-entry {
  gap: 4px;
  transition: all 0.2s ease;
}

.contract-body-entry.is-clickable {
  cursor: pointer;
}

.contract-body-entry.is-clickable:hover {
  border-color: #91caff;
  background: #eef6ff;
}

.contract-body-entry-name {
  color: #0f172a;
  font-weight: 600;
  word-break: break-all;
}

.contract-body-entry-hint {
  color: #64748b;
  line-height: 1.6;
}

.other-upload-text {
  font-size: 13px;
  color: #475569;
}

.contract-attr-title {
  margin-top: 14px;
  margin-bottom: 10px;
}

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

.boolean-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 32px;
  font-size: 13px;
  color: #334155;
}

.extra-amount-section {
  margin-top: 16px;
}

.extra-label {
  font-size: 13px;
  font-weight: 600;
  color: #334155;
  margin-bottom: 8px;
}

.extra-check-row,
.extra-input-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
}

.extra-check-item {
  width: 24%;
  font-size: 13px;
}

.plan-section {
  margin-top: 18px;
}

.plan-toolbar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 8px;
}

.plan-table :deep(.ant-table-cell) {
  vertical-align: top;
}

.empty-plan-text {
  padding: 12px 0;
  color: #94a3b8;
}

.detail-collapse {
  margin-top: 16px;
  background: transparent;
}

.editor-footer {
  display: flex;
  justify-content: flex-end;
  padding: 14px 20px 18px;
  background: #fff;
  border-top: 1px solid #e5e7eb;
}

@media (max-width: 1280px) {
  .main-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 900px) {
  .field-grid-2,
  .field-grid-3,
  .summary-grid,
  .boolean-grid {
    grid-template-columns: 1fr;
  }

  .field-span-2,
  .field-span-3 {
    grid-column: span 1;
  }

  .extra-check-row,
  .extra-input-row {
    flex-direction: column;
  }

  .extra-check-item,
  .extra-input-row :deep(.ant-input-number) {
    width: 100% !important;
  }
}
</style>
