import recoveryCreditorsBaseHtml from '~/assets/templates/recovery-template-creditors.html?raw'
import recoveryAssetsBaseHtml from '~/assets/templates/recovery-template-assets.html?raw'
import recoveryTrusteeDebtBaseHtml from '~/assets/templates/recovery-template-trustee-debt.html?raw'
import { buildAssetDocumentModel, padAssetRows, renderAssetTableHtml } from '~/composables/useAssetData'
import { getRecoveryDummyDraftEntries } from '~/composables/useRecoveryDummyData'

type RecoveryDocumentKey =
  | 'all_application'
  | 'creditors_csv'
  | 'creditors'
  | 'assets'
  | 'income_expense'
  | 'statement'
  | 'repayment_plan'
  | 'prohibition'
  | 'suspension'
  | 'copy_submission'
  | 'filing_certificate'
  | 'power_of_attorney'
  | 'trustee_debt'
  | 'confirmation'

type RecoveryDocumentDefinition = {
  key: RecoveryDocumentKey
  label: string
  manualLabel: string
  fileBaseName: string
  pageIds: Array<number | string>
  kind: 'pdf' | 'csv'
}

type RecoveryDocumentContext = {
  caseInfo: Record<string, any>
  pages: Record<string, any>
  manualDrafts: Record<string, ManualDraftData>
}

type ManualDraftData = {
  html?: string
  updatedAt?: string
  documentOrder?: RecoveryDocumentKey[]
}

type CaseInfoResponse = {
  success: boolean
  case?: Record<string, any>
}

type PageDraftRecord = {
  pageId: string | number
  formData?: Record<string, any>
}

type PageDraftListResponse = {
  success: boolean
  drafts?: PageDraftRecord[]
}

type PageDraftSingleResponse = {
  success: boolean
  draft?: PageDraftRecord | null
}

const CREDITOR_SOURCE_PAGE_IDS: Array<number | string> = [4, '11_1', '11_2', '11_3', 12, '12_1', '13_1']
const CREDITOR_PROCEEDING_PAGE_IDS: Array<number | string> = [14, 15, '15_1', '15_2', '15_3', '15_4', '15_5']

const RECOVERY_DOCUMENT_DEFINITIONS: RecoveryDocumentDefinition[] = [
  {
    key: 'creditors_csv',
    label: '1. [CSV] 채권자목록',
    manualLabel: '채권자목록',
    fileBaseName: '채권자목록',
    pageIds: [4],
    kind: 'csv'
  },
  {
    key: 'creditors',
    label: '1. [PDF] 채권자목록',
    manualLabel: '채권자목록',
    fileBaseName: '채권자목록',
    pageIds: [1, ...CREDITOR_SOURCE_PAGE_IDS, ...CREDITOR_PROCEEDING_PAGE_IDS],
    kind: 'pdf'
  },
  {
    key: 'assets',
    label: '2. [PDF] 재산목록',
    manualLabel: '재산목록',
    fileBaseName: '재산목록',
    pageIds: [1, 2, 7, 10, 11, '11_1', '11_2', '11_3', 12, '12_1'],
    kind: 'pdf'
  },
  {
    key: 'income_expense',
    label: '3. [PDF] 수입 및 지출에 관한 목록',
    manualLabel: '수입 및 지출에 관한 목록',
    fileBaseName: '수입및지출에관한목록',
    pageIds: [2, 3, 5, 16],
    kind: 'pdf'
  },
  {
    key: 'statement',
    label: '4. [PDF] 진술서',
    manualLabel: '진술서',
    fileBaseName: '진술서',
    pageIds: [1, 2, '15_1', '15_2', '15_3', '15_4', '15_5', 16, 17],
    kind: 'pdf'
  },
  {
    key: 'repayment_plan',
    label: '5. [PDF] 변제계획안',
    manualLabel: '변제계획안',
    fileBaseName: '변제계획안',
    pageIds: [1, 2, 3, 4, 5, 16, '11_1', '11_2', '11_3', 12, '12_1', '13_1'],
    kind: 'pdf'
  },
  {
    key: 'prohibition',
    label: '6. [PDF] 금지명령',
    manualLabel: '금지명령',
    fileBaseName: '금지명령',
    pageIds: [1, 2, 16],
    kind: 'pdf'
  },
  {
    key: 'suspension',
    label: '7. [PDF] 중지명령',
    manualLabel: '중지명령',
    fileBaseName: '중지명령',
    pageIds: [1, 2, '15_1', '15_2', '15_3', '15_4', '15_5', 16],
    kind: 'pdf'
  },
  {
    key: 'copy_submission',
    label: '8. [PDF] 부본제출서',
    manualLabel: '부본제출서',
    fileBaseName: '부본제출서',
    pageIds: [1, 2],
    kind: 'pdf'
  },
  {
    key: 'filing_certificate',
    label: '9. [PDF] 접수증명원',
    manualLabel: '접수증명원',
    fileBaseName: '접수증명원',
    pageIds: [1, 2],
    kind: 'pdf'
  },
  {
    key: 'power_of_attorney',
    label: '10. [PDF] 위임장',
    manualLabel: '위임장',
    fileBaseName: '위임장',
    pageIds: [1, 2],
    kind: 'pdf'
  },
  {
    key: 'trustee_debt',
    label: '11. [PDF] 재단채권 변제계획서',
    manualLabel: '재단채권 변제계획서',
    fileBaseName: '재단채권변제계획서',
    pageIds: [4, 5],
    kind: 'pdf'
  },
  {
    key: 'confirmation',
    label: '12. [PDF] 확인서',
    manualLabel: '확인서',
    fileBaseName: '확인서',
    pageIds: [1, 2, ...CREDITOR_SOURCE_PAGE_IDS],
    kind: 'pdf'
  },
  {
    key: 'all_application',
    label: '13. [PDF] 신청서 전부',
    manualLabel: '전체문서',
    fileBaseName: '개인회생신청서_전부',
    pageIds: [1, 2, 3, 4, 5, 7, 10, 11, '11_1', '11_2', '11_3', 12, '12_1', 16, 17, '13_1', 14, 15, '15_1', '15_2', '15_3', '15_4', '15_5'],
    kind: 'pdf'
  }
]

const DEFAULT_DOCUMENT_NAV_ORDER: RecoveryDocumentKey[] = [
  'all_application',
  'creditors',
  'assets',
  'income_expense',
  'statement',
  'repayment_plan',
  'prohibition',
  'suspension',
  'copy_submission',
  'filing_certificate',
  'power_of_attorney',
  'trustee_debt',
  'confirmation'
]

const PAGE5_MONTH_KEYS = Array.from({ length: 12 }, (_, index) => `${index + 1}`)

const buildDefaultPage5MonthColumns = () => {
  const baseDate = new Date()
  baseDate.setDate(1)
  baseDate.setMonth(baseDate.getMonth() - 1)

  return PAGE5_MONTH_KEYS.map((key, index) => {
    const target = new Date(baseDate)
    target.setMonth(baseDate.getMonth() - (11 - index))

    return {
      key,
      year: String(target.getFullYear()),
      month: String(target.getMonth() + 1).padStart(2, '0'),
      enabled: true
    }
  })
}

const getPage5MonthColumns = (page5: Record<string, any>) => {
  const defaults = buildDefaultPage5MonthColumns()

  if (Array.isArray(page5?.monthColumns) && page5.monthColumns.length === 12) {
    return defaults.map((column, index) => ({
      key: column.key,
      year: String(page5.monthColumns[index]?.year || column.year),
      month: String(page5.monthColumns[index]?.month || column.month).padStart(2, '0'),
      enabled: page5.monthColumns[index]?.enabled !== false
    }))
  }

  const selectedMonths = page5?.selectedMonths || {}
  return defaults.map((column) => ({
    ...column,
    enabled: selectedMonths[column.key] !== false
  }))
}

const getEnabledPage5MonthColumns = (page5: Record<string, any>) =>
  getPage5MonthColumns(page5).filter((column) => column.enabled)

const getPage5MonthLabel = (column: { year?: string; month?: string }) =>
  `${String(column?.year || '').slice(-2)}.${String(column?.month || '').padStart(2, '0')}`

const manualPageIdPrefix = 'manual_'
const referenceDocumentKeys = new Set<RecoveryDocumentKey>([
  'all_application',
  'creditors',
  'assets',
  'income_expense',
  'statement',
  'repayment_plan',
  'prohibition',
  'suspension',
  'copy_submission',
  'filing_certificate',
  'power_of_attorney',
  'trustee_debt',
  'confirmation'
])
const PAGE_BREAK_SCREEN_LABEL_TEXT = '------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------'
const PAGE_BREAK_SCREEN_LABEL_HTML = `<span class="fincure-page-break-screen-only" style="position:absolute;top:0;left:50%;transform:translate(-50%, -55%);padding:0 12px;background:#ffffff;color:#dc2626;font-size:12px;font-weight:700;line-height:1.4;white-space:nowrap;">${PAGE_BREAK_SCREEN_LABEL_TEXT}</span>`
const PAGE_BREAK_SCREEN_STYLE = 'display:block;width:100%;height:1px;min-height:1px;margin:36px 0 48px;padding:0;border:0;border-top:1px dashed #ef4444;overflow:visible;visibility:visible;position:relative;pointer-events:none;break-before:auto;page-break-before:auto;'

const sanitizeFileName = (value: string) =>
  value.replace(/[\\/:*?"<>|]/g, '_').replace(/\s+/g, '_')

const stripHtml = (value: string) =>
  value
    .replace(/<style[\s\S]*?<\/style>/gi, '')
    .replace(/<script[\s\S]*?<\/script>/gi, '')
    .replace(/<\/(p|div|h1|h2|h3|li|tr)>/gi, '\n')
    .replace(/<br\s*\/?>/gi, '\n')
    .replace(/<[^>]+>/g, '')
    .replace(/&nbsp;/g, ' ')
    .replace(/&amp;/g, '&')
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/\n{3,}/g, '\n\n')
    .trim()

const escapeHtml = (value: unknown) =>
  String(value ?? '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;')

const PAGE_BREAK_MARKER_HTML = `<div class="fincure-page-break" data-page-break="manual" aria-hidden="true" style="${PAGE_BREAK_SCREEN_STYLE}">${PAGE_BREAK_SCREEN_LABEL_HTML}</div>`
const AUTO_PAGE_BREAK_MARKER_HTML = `<div class="fincure-page-break" data-page-break="auto" data-auto-page-break="true" aria-hidden="true" style="${PAGE_BREAK_SCREEN_STYLE}">${PAGE_BREAK_SCREEN_LABEL_HTML}</div>`
const LEGACY_VISIBLE_PAGE_BREAK_PATTERN = /<p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- \[수정하거나 지우지 마세요\] 이 부분은 인쇄할때 페이지를 구분합니다\. -------------------<\/b><br style='height:0; line-height:0'>/gi
const LEGACY_PLAIN_PAGE_BREAK_PATTERN = /<p style='page-break-before:always'>/gi
const EMPTY_AUTO_PAGE_BREAK_PATTERN = /<div(?=[^>]*class=(['"])[^'"]*fincure-page-break[^'"]*\1)(?=[^>]*data-auto-page-break=(['"])true\2)[^>]*><\/div>/gi
const EMPTY_MANUAL_PAGE_BREAK_PATTERN = /<div(?=[^>]*class=(['"])[^'"]*fincure-page-break[^'"]*\1)(?![^>]*data-auto-page-break=)[^>]*><\/div>/gi
const EDITOR_PREVIEW_PAGE_BREAK_LAYER_PATTERN = /<div[^>]*id=(['"])fincure-page-break-preview-layer\1[^>]*>[\s\S]*?<\/div>/gi
const PAGE_BREAK_BOUNDARY_PATTERN_SOURCE = `<div(?=[^>]*class=(['"])[^'"]*fincure-page-break[^'"]*\\1)[^>]*><\\/div>|<p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- \\[수정하거나 지우지 마세요\\] 이 부분은 인쇄할때 페이지를 구분합니다\\. -------------------<\\/b><br style='height:0; line-height:0'>|<p style='page-break-before:always'>`
const PAGE_BREAK_BOUNDARY_PATTERN = new RegExp(PAGE_BREAK_BOUNDARY_PATTERN_SOURCE, 'gi')
const AUTO_PAGE_BREAK_PATTERN = /<div(?=[^>]*class=(['"])[^'"]*fincure-page-break[^'"]*\1)(?=[^>]*data-auto-page-break=(['"])true\2)[^>]*><\/div>/gi
const ASSET_LAYOUT_VERSION = 'v6'
const ASSET_LAYOUT_VERSION_MARKER = `<div data-fincure-asset-layout="${ASSET_LAYOUT_VERSION}" aria-hidden="true" style="display:none !important;width:0 !important;height:0 !important;overflow:hidden !important;"></div>`
const ASSET_PRINT_SECTION_OPEN = `<div class="fincure-print-section fincure-asset-print-section">`
const ASSET_PRINT_SECTION_CLOSE = `</div>`
const DOCUMENT_FINGERPRINT_MARKER_PATTERN = /<div(?=[^>]*data-fincure-document-key=(['"])[^'"]+\1)(?=[^>]*data-fincure-document-fingerprint=(['"])[^'"]+\2)[^>]*><\/div>/gi
const DOCUMENT_LAYOUT_VERSIONS: Record<RecoveryDocumentKey, string> = {
  all_application: 'v3',
  creditors_csv: 'v1',
  creditors: 'v4',
  assets: 'v6',
  income_expense: 'v2',
  statement: 'v2',
  repayment_plan: 'v3',
  prohibition: 'v2',
  suspension: 'v2',
  copy_submission: 'v2',
  filing_certificate: 'v2',
  power_of_attorney: 'v2',
  trustee_debt: 'v2',
  confirmation: 'v3'
}

const normalizePageBreakMarkup = (html: string) =>
  String(html || '')
    .replace(LEGACY_VISIBLE_PAGE_BREAK_PATTERN, PAGE_BREAK_MARKER_HTML)
    .replace(LEGACY_PLAIN_PAGE_BREAK_PATTERN, PAGE_BREAK_MARKER_HTML)
    .replace(EMPTY_AUTO_PAGE_BREAK_PATTERN, AUTO_PAGE_BREAK_MARKER_HTML)
    .replace(EMPTY_MANUAL_PAGE_BREAK_PATTERN, PAGE_BREAK_MARKER_HTML)

const stripAutomaticPreviewPageBreaks = (html: string) =>
  String(html || '').replace(AUTO_PAGE_BREAK_PATTERN, '')

const normalizeVisibleWhitespaceEntities = (html: string) =>
  String(html || '')
    .replace(/&(amp|#38);nbsp;/gi, '&nbsp;')
    .replace(/&(amp|#38);#160;/gi, '&#160;')
    .replace(/&(amp|#38);ensp;/gi, '&ensp;')
    .replace(/&(amp|#38);emsp;/gi, '&emsp;')

const sanitizeManualDocumentHtml = (html: string) =>
  normalizePageBreakMarkup(
    stripAutomaticPreviewPageBreaks(
      normalizeVisibleWhitespaceEntities(
        String(html || '').replace(EDITOR_PREVIEW_PAGE_BREAK_LAYER_PATTERN, '')
      )
    )
  )

const withAssetLayoutVersionMarker = (html: string) => {
  const source = String(html || '')
  if (source.includes(`data-fincure-asset-layout="${ASSET_LAYOUT_VERSION}"`)) {
    return source
  }

  return `${ASSET_LAYOUT_VERSION_MARKER}${source}`
}

const stripDocumentFingerprintMarkers = (html: string) =>
  String(html || '').replace(DOCUMENT_FINGERPRINT_MARKER_PATTERN, '')

const getPageBreakStartIndices = (source: string) => {
  const indices: number[] = []
  const matcher = new RegExp(PAGE_BREAK_BOUNDARY_PATTERN.source, 'gi')
  let match: RegExpExecArray | null = null

  while ((match = matcher.exec(source)) !== null) {
    indices.push(match.index)
  }

  return indices
}

const extractBodyInnerHtml = (value: string) => {
  const trimmed = normalizeVisibleWhitespaceEntities(String(value || '')).trim()
  if (!trimmed) return ''

  const textareaMatch = trimmed.match(/<textarea[^>]+id=["']ir1["'][^>]*>([\s\S]*?)<\/textarea>/i)
  if (textareaMatch) {
    const textareaContent = textareaMatch[1].trim()
    if (textareaContent) {
      const nestedBodyMatch = textareaContent.match(/<body[^>]*>([\s\S]*?)<\/body>/i)
      if (nestedBodyMatch) {
        return nestedBodyMatch[1].trim()
      }

      return textareaContent
        .replace(/<!DOCTYPE[^>]*>/gi, '')
        .replace(/<\/?html[^>]*>/gi, '')
        .replace(/<\/?head[^>]*>/gi, '')
        .replace(/<\/?body[^>]*>/gi, '')
        .trim()
    }
  }

  const bodyMatch = trimmed.match(/<body[^>]*>([\s\S]*?)<\/body>/i)
  if (bodyMatch) {
    return bodyMatch[1].trim()
  }

  return trimmed
    .replace(/<!DOCTYPE[^>]*>/gi, '')
    .replace(/<\/?html[^>]*>/gi, '')
    .replace(/<\/?head[^>]*>/gi, '')
    .replace(/<\/?body[^>]*>/gi, '')
    .trim()
}

const buildReferenceDocumentShell = (title: string, bodyHtml: string) => `
  <!DOCTYPE html>
  <html lang="ko">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${escapeHtml(title)}</title>
    <link rel="stylesheet" href="/SmartEditor2/css/ko_KR/restoration.css">
    <link rel="stylesheet" href="/SmartEditor2/css/ko_KR/renewal_2019.css">
    <link rel="stylesheet" href="/SmartEditor2/css/ko_KR/smart_editor2_in.css">
    <link rel="stylesheet" href="/SmartEditor2/css/ko_KR/smart_editor2_font_fix.css">
    <style>
      html,
      body,
      body *,
      table,
      tbody,
      thead,
      tfoot,
      tr,
      td,
      th,
      p,
      span,
      div,
      strong,
      b,
      em,
      u,
      li {
        font-family: "Batang", "BatangChe", "Nanum Myeongjo", serif !important;
      }
      body { margin: 0; background: #ffffff; }
      table { border-collapse: collapse; }
      table, tr, td, th { border-spacing: 0; }
      p { margin: 0; }
      br { line-height: 0; }
      .border_o,
      .border_l,
      .border_r,
      .border_t,
      .border_b,
      .border_tb,
      .border_tl,
      .border_tlb,
      .border_tr,
      .border_tlr,
      .border_lr,
      .border_lrt,
      .border_lrtb,
      .border_lb,
      .border_lbr,
      .border_lbt {
        border-color: #000000 !important;
        border-style: solid !important;
        border-width: 1px !important;
      }
      .border_l,
      .border_tl,
      .border_tlb,
      .border_tlr,
      .border_lr,
      .border_lrt,
      .border_lrtb,
      .border_lb,
      .border_lbr,
      .border_lbt {
        border-left: none !important;
      }
      .border_r,
      .border_tr,
      .border_tlr,
      .border_lr,
      .border_lrt,
      .border_lrtb,
      .border_lbr {
        border-right: none !important;
      }
      .border_t,
      .border_tb,
      .border_tl,
      .border_tlb,
      .border_tr,
      .border_tlr,
      .border_lrt,
      .border_lrtb,
      .border_lbt {
        border-top: none !important;
      }
      .border_b,
      .border_tb,
      .border_tlb,
      .border_lrtb,
      .border_lb,
      .border_lbr,
      .border_lbt {
        border-bottom: none !important;
      }
      .fincure-print-section {
        display: block;
        width: 100%;
        box-sizing: border-box;
        break-inside: avoid-page;
        page-break-inside: avoid;
      }
      .fincure-page-break {
        display: block;
        width: 100%;
        height: 0;
        margin: 0;
        padding: 0;
        border: 0;
        overflow: hidden;
        visibility: hidden;
        break-before: page;
        page-break-before: always;
      }
      .fincure-page-break-screen-only {
        display: none !important;
      }
      .fincure-income-expense-compact .text01 {
        font-size: 7pt !important;
        line-height: 1.2 !important;
      }
    </style>
  </head>
  <body>${normalizeReferenceBodyHtml(bodyHtml)}</body>
</html>
`

const normalizeReferenceBodyHtml = (html: string) => {
  let result = extractBodyInnerHtml(html)

  result = result.replace(
    new RegExp(`(${PAGE_BREAK_BOUNDARY_PATTERN.source})(?:\\s|&nbsp;|<br\\s*\\/?>)+`, 'gi'),
    '$1'
  )
  result = result.replace(
    /(<div(?=[^>]*class=(['"])[^'"]*fincure-print-section[^'"]*\2)[^>]*>)(?:\s|&nbsp;|<br\s*\/?>)+/gi,
    '$1'
  )
  result = result.replace(/^(?:\s|&nbsp;|<br\s*\/?>)+/gi, '')

  return result.trim()
}

const buildBlankDocumentBody = (title: string, context: RecoveryDocumentContext) => {
  const page1 = context.pages['1'] || {}
  const applicantName = context.caseInfo?.applicant_name || page1?.applicantName || ''
  const residentNumber = formatResidentNumber(page1?.residentNumber || page1?.ssn || context.caseInfo?.resident_number || '')
  const caseNumber = context.caseInfo?.case_number || page1?.caseNumber || ''
  const dateText = formatDottedDate(page1?.filingDate || context.caseInfo?.registration_date)

  return `
  <br><br><br>
  <p align='left'><br>
    <span class='text01'>&nbsp;&nbsp;&nbsp;&nbsp;<u>${escapeHtml(caseNumber)} &nbsp;&nbsp;&nbsp;채무자&nbsp;${escapeHtml(applicantName)}  ( ${escapeHtml(residentNumber)} )</u>&nbsp;&nbsp;&nbsp;&nbsp;</span>
  </p>
  <p align='center' style='padding:0px;margin:0px;'>
    <b><span class='text07_b'>${escapeHtml(title)}</span></b>
    &nbsp;<center><div style='width:100%' align='right'><span class='printhidden'></span></div></center>
  </p>
  <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center'>
    <tr>
      <td><span class='text01' style='line-height:40px;'>작성일: ${dateText}</span></td>
    </tr>
  </table>
  <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center' class='docxline'>
    <tr bgcolor='#ffffff'>
      <td class='border_o' style='height:120px; padding:18px;'>
        <span class='text01'>저장된 데이터에 따라 이 문서 양식을 채워야 합니다.</span><br>
        <span class='text01'>현재 이 문서의 전용 레퍼런스 템플릿 연결 작업이 진행 중입니다.</span>
      </td>
    </tr>
  </table>`
}

const hasMeaningfulValue = (value: unknown): boolean => {
  if (value === null || value === undefined) return false
  if (typeof value === 'string') return value.trim().length > 0
  if (typeof value === 'number') return value !== 0
  if (typeof value === 'boolean') return value
  if (Array.isArray(value)) return value.some((item) => hasMeaningfulValue(item))
  if (typeof value === 'object') return Object.values(value as Record<string, unknown>).some((item) => hasMeaningfulValue(item))
  return false
}

const hasMeaningfulPages = (pages: Record<string, any>) =>
  Object.values(pages).some((page) => hasMeaningfulValue(page))

const isLikelyReferenceSeedHtml = (html?: string | null) => {
  const value = String(html || '')
  if (!value) return false

  const structuralMarkers = [
    /개인회생채권자목록/,
    /금지명령 포함/,
    /인지대[\s\S]{0,40}회생/,
    /송달료[\s\S]{0,40}회생/,
    /채무 원 금/,
    /휴대전화 번호\s*:/
  ]
  const structuralMatchCount = structuralMarkers.filter((pattern) => pattern.test(value)).length
  const sampleDateCount = (value.match(/2026\s*\.\s*(?:&nbsp;\s*)?\d{2}\s*\.\s*(?:&nbsp;\s*)?\d{2}\s*\./g) || []).length

  return structuralMatchCount >= 3 || sampleDateCount >= 4
}

const formatCurrency = (value: unknown) => {
  const amount = parseNumeric(value)
  return `${amount.toLocaleString('ko-KR')}원`
}

const formatResidentNumber = (value: unknown) => {
  const digits = String(value ?? '').replace(/[^0-9]/g, '')
  if (digits.length >= 13) {
    return `${digits.slice(0, 6)} - ${digits.slice(6, 7)}******`
  }
  if (digits.length >= 7) {
    return `${digits.slice(0, 6)} - ${digits.slice(6)}`
  }
  return String(value ?? '')
}

const formatDottedDate = (value?: string | null) => {
  const parsed = value ? new Date(value) : new Date()
  const target = Number.isNaN(parsed.getTime()) ? new Date() : parsed
  return `${target.getFullYear()} .&nbsp;&nbsp;&nbsp;${String(target.getMonth() + 1).padStart(2, '0')} .&nbsp;&nbsp;&nbsp;${String(target.getDate()).padStart(2, '0')} .`
}

const formatDateParts = (value?: string | null) => {
  if (!value) {
    return { year: '', month: '', day: '' }
  }

  const parsed = new Date(value)
  if (Number.isNaN(parsed.getTime())) {
    const match = String(value).match(/(\d{4})[-./ ]+(\d{1,2})[-./ ]+(\d{1,2})/)
    if (!match) return { year: '', month: '', day: '' }

    return {
      year: match[1],
      month: match[2].padStart(2, '0'),
      day: match[3].padStart(2, '0')
    }
  }

  return {
    year: String(parsed.getFullYear()),
    month: String(parsed.getMonth() + 1).padStart(2, '0'),
    day: String(parsed.getDate()).padStart(2, '0')
  }
}

const formatHtmlDottedDateFromParts = (year?: string | null, month?: string | null, day?: string | null) => {
  const safeYear = String(year || '').trim() || '&nbsp;&nbsp;&nbsp;&nbsp;'
  const safeMonth = String(month || '').trim() || '&nbsp;&nbsp;'
  const safeDay = String(day || '').trim() || '&nbsp;&nbsp;'
  return `${safeYear} .&nbsp;&nbsp;&nbsp;${safeMonth} .&nbsp;&nbsp;&nbsp;${safeDay} .`
}

const resolveRepaymentMonths = (page2: Record<string, any>) => {
  const customMonths = parseNumeric(page2?.customRepaymentMonths)
  if (page2?.customRepaymentPeriod && customMonths > 0) {
    return String(customMonths)
  }

  const periodCode = String(page2?.repaymentPeriod || '').trim().toUpperCase()
  switch (periodCode) {
    case 'W':
      return '36'
    case 'Y':
      return '35'
    case 'N':
      return '60'
    case 'V':
      return '60'
    case 'Z':
      return '60'
    default: {
      const numericPeriod = typeof page2?.repaymentPeriod === 'string'
        ? page2.repaymentPeriod.replace(/[^0-9]/g, '')
        : ''
      return numericPeriod || ''
    }
  }
}

const buildPhoneNumber = (...parts: Array<unknown>) => {
  const values = parts.map((item) => String(item ?? '').trim()).filter(Boolean)
  if (values.length === 3) return values.join(' - ')
  return values.join('')
}

const getContextBasics = (context: RecoveryDocumentContext) => {
  const page1 = context.pages['1'] || {}
  const page2 = context.pages['2'] || {}
  const page3 = context.pages['3'] || {}
  const page4 = context.pages['4'] || {}
  const page5 = context.pages['5'] || {}
  const residentAddress = [
    page1?.registeredZipCode,
    page1?.registeredAddress,
    page1?.registeredAddressDetail
  ]
    .filter(Boolean)
    .join(' ')
    .trim()
  const currentAddress = [
    page1?.currentZipCode,
    page1?.currentAddress,
    page1?.currentAddressDetail
  ]
    .filter(Boolean)
    .join(' ')
    .trim()
  const page2Address = [
    page2?.residentAddress,
    page2?.currentAddress,
    context.caseInfo?.applicant_address
  ]
    .filter(Boolean)
    .map((item: unknown) => String(item).trim())
    .find(Boolean) || ''
  const applicantAddress = currentAddress || residentAddress || page2Address
  const lawyerName = String(page1?.agentName || page2?.lawyerName || '').trim()
  const lawyerPhone = String(
    buildPhoneNumber(page1?.agentPhonePart1, page1?.agentPhonePart2, page1?.agentPhonePart3) ||
    page2?.lawyerPhone ||
    ''
  ).trim()
  const lawyerFax = String(
    buildPhoneNumber(page1?.agentFaxPart1, page1?.agentFaxPart2, page1?.agentFaxPart3) ||
    page2?.lawyerFax ||
    ''
  ).trim()
  const lawyerAddress = String(
    [page1?.agentZipCode, page1?.agentAddress].filter(Boolean).join(' ') ||
    page2?.lawyerAddress ||
    ''
  ).trim()
  const applicantName = context.caseInfo?.applicant_name || page1?.applicantName || ''
  const residentNumberRaw =
    page1?.residentNumber ||
    page1?.ssn ||
    `${page1?.juminFront || ''}${page1?.juminBack || ''}` ||
    context.caseInfo?.resident_number ||
    ''
  const residentNumber = formatResidentNumber(residentNumberRaw)
  const caseNumber = context.caseInfo?.case_number || page1?.caseNumber || ''
  const filingDateRaw = page1?.filingDate || context.caseInfo?.registration_date || ''
  const filingDate = formatDottedDate(filingDateRaw)
  const filingDateParts = formatDateParts(filingDateRaw)
  const firstRepaymentDateRaw = page1?.firstRepaymentDate || ''
  const firstRepaymentDateParts = formatDateParts(firstRepaymentDateRaw)
  const mobileNumber = buildPhoneNumber(page1?.mobilePart1, page1?.mobilePart2, page1?.mobilePart3)
  const receiveMobileNumber = buildPhoneNumber(page1?.receiveMobilePart1, page1?.receiveMobilePart2, page1?.receiveMobilePart3)
  const phoneNumber = buildPhoneNumber(page1?.phonePart1, page1?.phonePart2, page1?.phonePart3)
  const courtName = String(page2?.court || '').trim()
  const repaymentMonths = resolveRepaymentMonths(page2)
  const interestBaseDate =
    page4?.interestBaseDate?.year && page4?.interestBaseDate?.month && page4?.interestBaseDate?.day
      ? `${page4.interestBaseDate.year} .&nbsp;&nbsp;&nbsp;${String(page4.interestBaseDate.month).padStart(2, '0')} .&nbsp;&nbsp;&nbsp;${String(page4.interestBaseDate.day).padStart(2, '0')} .`
      : filingDate

  const enabledMonthColumns = getEnabledPage5MonthColumns(page5)
  const incomeRows = Array.isArray(page5?.incomeRows) ? page5.incomeRows : []
  const deductionRows = Array.isArray(page5?.deductionRows) ? page5.deductionRows : []
  const totalForRows = (rows: Array<Record<string, any>>) =>
    rows.reduce((sum, row) => {
      const amounts = row?.amounts || {}
      return (
        sum +
        enabledMonthColumns.reduce((monthSum, column) => monthSum + parseNumeric(amounts[column.key]), 0)
      )
    }, 0)

  const averageIncome = enabledMonthColumns.length ? Math.round(totalForRows(incomeRows) / enabledMonthColumns.length) : 0
  const averageDeduction = enabledMonthColumns.length ? Math.round(totalForRows(deductionRows) / enabledMonthColumns.length) : 0
  const averageNetIncome = Math.max(0, averageIncome - averageDeduction)

  const statementLines = [
    ...(Array.isArray(page3?.expenseReasons) ? page3.expenseReasons : []).filter((item: unknown) => String(item || '').trim()),
    String(page3?.supplementaryDescription || '').trim()
  ].filter(Boolean)

  return {
    applicantName,
    residentNumber,
    caseNumber,
    filingDate,
    filingDateParts,
    firstRepaymentDateParts,
    mobileNumber,
    receiveMobileNumber,
    phoneNumber,
    applicantAddress,
    lawyerName,
    lawyerPhone,
    lawyerFax,
    lawyerAddress,
    courtName,
    repaymentMonths,
    interestBaseDate,
    averageIncome,
    averageDeduction,
    averageNetIncome,
    statementText: statementLines.join('\n\n')
  }
}

const getEmploymentDataFromContext = (context: RecoveryDocumentContext) => {
  const page4 = context.pages['4'] || {}
  const page16 = context.pages['16'] || {}
  const employment = { ...(page16?.formData || {}) } as Record<string, any>
  const page4EmploymentType = String(page4?.employmentType || '').trim()

  if (!String(employment.employmentType || '').trim() && page4EmploymentType) {
    employment.employmentType = page4EmploymentType
  }

  if (!String(employment.employmentType || '').trim() && String(page4?.employmentDetailType || '').trim()) {
    employment.employmentType = ['D', 'E', 'F', 'G', 'H', 'K'].includes(String(page4.employmentDetailType))
      ? '자영'
      : '근무'
  }

  return employment
}

const scrubReferenceSeeds = (
  html: string,
  context: RecoveryDocumentContext,
  key: RecoveryDocumentKey
) => {
  const basics = getContextBasics(context)
  const filingYear = basics.filingDateParts.year || '&nbsp;&nbsp;&nbsp;&nbsp;'
  const filingMonth = basics.filingDateParts.month || '&nbsp;&nbsp;'
  const filingDay = basics.filingDateParts.day || '&nbsp;&nbsp;'
  let result = html

  result = result.replace(
    /<span class='text01'>&nbsp;&nbsp;&nbsp;&nbsp;<u>[\s\S]*?<\/u>&nbsp;&nbsp;&nbsp;&nbsp;<\/span>/,
    `<span class='text01'>&nbsp;&nbsp;&nbsp;&nbsp;<u>${escapeHtml(basics.caseNumber)} &nbsp;&nbsp;&nbsp;채무자&nbsp;${escapeHtml(basics.applicantName)}  ( ${escapeHtml(basics.residentNumber)} )    </u>&nbsp;&nbsp;&nbsp;&nbsp;</span>`
  )

  result = result.replace(
    /(신청인\s*\(채무자\)\s*&nbsp;|신청인\s*&nbsp;&nbsp;)(?:<u>)?[^<\n]+(?:<\/u>)?/g,
    (_, prefix: string) => `${prefix}${basics.applicantName ? `<u>${escapeHtml(basics.applicantName)}</u>` : '<u>&nbsp;</u>'}`
  )

  if (basics.mobileNumber) {
    result = result.replace(/010\s*-\s*\d{3,4}\s*-\s*\d{4}/g, basics.mobileNumber)
  } else {
    result = result.replace(/010\s*-\s*\d{3,4}\s*-\s*\d{4}/g, '&nbsp;')
  }

  if (basics.receiveMobileNumber) {
    result = result.replace(/휴대전화 번호 :[^<]*/g, `휴대전화 번호 : ${basics.receiveMobileNumber}`)
  } else {
    result = result.replace(/휴대전화 번호 :[^<]*/g, '휴대전화 번호 :')
  }

  result = result.replace(/\b\d{6}\s*-\s*\d\*{6}\b/g, basics.residentNumber || '&nbsp;')
  result = result.replace(/\b\d{6}\s*-\s*\d{7}\b/g, basics.residentNumber || '&nbsp;')
  result = result.replace(/\b20\d{2}\s*개회[^\s<]{0,20}\s*호\b/g, basics.caseNumber || '&nbsp;')
  result = result.replace(/\b20\d{2}\s*개회\s*&nbsp;*[^<]{0,30}호/g, basics.caseNumber || '&nbsp;')

  if (basics.courtName) {
    result = result.replace(/[가-힣]+\s*(지방법원|회생법원)\s*개인회생과&nbsp;&nbsp;&nbsp;&nbsp;귀&nbsp;중/g, `${escapeHtml(basics.courtName)}&nbsp;&nbsp;&nbsp;&nbsp;귀&nbsp;중`)
    result = result.replace(/[가-힣]+\s*(지방법원|회생법원)\s*귀&nbsp;중/g, `${escapeHtml(basics.courtName)}&nbsp;귀&nbsp;중`)
  }

  result = result.replace(/202\d\s*\.\s*&nbsp;&nbsp;&nbsp;\d{2}\s*\.\s*&nbsp;&nbsp;&nbsp;\d{2}\s*\./g, basics.filingDate)
  result = result.replace(/\[\s*\d{4}\s*\]년/g, basics.firstRepaymentDateParts.year ? `[ ${escapeHtml(basics.firstRepaymentDateParts.year)} ]년` : '[&nbsp;&nbsp;&nbsp;&nbsp;]년')
  result = result.replace(/\[\s*\d{1,2}\s*\]월/g, basics.firstRepaymentDateParts.month ? `[ ${escapeHtml(basics.firstRepaymentDateParts.month)} ]월` : '[&nbsp;&nbsp;]월')
  result = result.replace(/\[\s*\d{1,2}\s*\]일부터/g, basics.firstRepaymentDateParts.day ? `[ ${escapeHtml(basics.firstRepaymentDateParts.day)} ]일부터` : '[&nbsp;&nbsp;]일부터')
  result = result.replace(/\[\s*\d+\s*\]개월간/g, basics.repaymentMonths ? `[ ${escapeHtml(String(basics.repaymentMonths))} ]개월간` : '[&nbsp;&nbsp;]개월간')
  result = result.replace(/\[\s*[\d,]+\s*\]원/g, basics.averageIncome ? `[ ${escapeHtml(basics.averageIncome.toLocaleString('ko-KR'))} ]원` : '[&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]원')

  if (basics.applicantAddress) {
    result = result.replace(/(주\s*&nbsp;&nbsp;&nbsp;소\s*:\s*)[\s\S]*?(<br>)/g, `$1${escapeHtml(basics.applicantAddress)}$2`)
  }

  result = result.replace(/(인지대[\s\S]*?회생<\/td>\s*<td[^>]*>)([\d,]+원)(<\/td>)/g, '$1&nbsp;$3')
  result = result.replace(/(금지<\/td>\s*<td[^>]*>)([\d,]+원)(<\/td>)/g, '$1&nbsp;$3')
  result = result.replace(/(송달료[\s\S]*?회생<\/td>\s*<td[^>]*>)([\d,]+원)(<\/td>)/g, '$1&nbsp;$3')
  result = result.replace(/(산정기준일 )20\d{2}\.\s*\d{2}\.\s*\d{2}\./g, `$1${basics.interestBaseDate.replace(/&nbsp;/g, ' ').replace(/\s+/g, ' ').trim()}`)
  result = result.replace(/(산정기준일 )20\d{2}\.\s+\d{2}\.\s+\d{2}\./g, `$1${basics.interestBaseDate.replace(/&nbsp;/g, ' ').replace(/\s+/g, ' ').trim()}`)

  if (key === 'statement') {
    const statementContent = basics.statementText
      ? escapeHtml(basics.statementText).replace(/\n/g, '<br />')
      : '&nbsp;'
    result = result.replace(
      /(<table style='border:1px solid #000000'[\s\S]*?<td style='padding:10px;' valign='top'><span class='text02' style='line-height:30px;'>)[\s\S]*?(<\/span><\/td>[\s\S]*?<\/table>)/,
      `$1${statementContent}$2`
    )

    result = result.replace(
      /(최종학력<\/span>\s*:\s*&nbsp;&nbsp;<span class='text02'>)[\s\S]*?(<\/span>)/,
      '$1&nbsp;$2'
    )
    result = result.replace(
      /(기\s*간<\/span><\/p>\s*<\/td>\s*<td class='border_l' colspan='5' width='570'>&nbsp;<span class='text02'>)[\s\S]*?(<\/span>)/,
      '$1&nbsp;$2'
    )
    result = result.replace(
      /(업 종<\/span><\/p>\s*<\/td>\s*<td class='border_tl' width='137'>&nbsp;<span class='text02'>)[\s\S]*?(<\/span>)/,
      '$1&nbsp;$2'
    )
    result = result.replace(
      /(직장명<\/span>\s*<\/p>\s*<\/td>\s*<td class='border_tl' style='width:23%;'>&nbsp;<span class='text02'>)[\s\S]*?(<\/span>)/,
      '$1&nbsp;$2'
    )
    result = result.replace(
      /(직 위<\/span>\s*<\/p>\s*<\/td>\s*<td class='border_tl' width='106'>&nbsp;<span class='text02'>)[\s\S]*?(<\/span>)/,
      '$1&nbsp;$2'
    )
  }

  if (key === 'income_expense') {
    result = result.replace(/(<td width='25%' style='padding-left:10px;'><span class='text02'>)[\s\S]*?(<\/span><\/td>)/, `$1&nbsp;$2`)
    result = result.replace(/(<td style='padding-left:10px;'><span class='text02'>)[\s\S]*?(<\/span><\/td>)/g, '$1&nbsp;$2')
    result = result.replace(/(<td align='right'><span class='text02'>)[\d,\s]+원&nbsp;(<\/span><\/td>)/g, '$1&nbsp;$2')
    result = result.replace(/(<td align='right'><span class='text02'>)&nbsp;[\d,\s]+원&nbsp;(<\/span><\/td>)/g, '$1&nbsp;$2')
  }

  if (key === 'copy_submission') {
    result = result.replace(
      /(<span class='text03'>&nbsp;&nbsp;사&nbsp;&nbsp;건[\s\S]*?<u>)[\s\S]*?(<\/u><\/span>)/,
      `$1${escapeHtml(filingYear)}$2`
    )
    result = result.replace(
      /(<span class='text03'>&nbsp;개회<\/span>&nbsp;<span class='text03'><u>)[\s\S]*?(<\/u><\/span>)/,
      `$1${escapeHtml(basics.caseNumber.replace(/^.*개회\s*/, '').replace(/\s*호?$/, ''))}$2`
    )
    result = result.replace(
      /(&nbsp;&nbsp;채 무 자&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)[^<(<\n]+(\s*\([^)]+\))?/,
      `$1${escapeHtml(basics.applicantName || '&nbsp;')}${basics.residentNumber ? ` (${escapeHtml(basics.residentNumber)})` : ''}`
    )
    result = result.replace(/(<span class='text03'>)\d{4}\s*\.\s*&nbsp;&nbsp;&nbsp;\d{2}\s*\.\s*&nbsp;&nbsp;&nbsp;\d{2}\s*\.(<\/span>)/, `$1${basics.filingDate}$2`)
  }

  if (key === 'filing_certificate') {
    result = result.replace(
      /(채무자\(신청인\)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)[^<]+/,
      `$1${escapeHtml(basics.applicantName || '&nbsp;')}`
    )
    result = result.replace(
      /(접수일자&nbsp; :&nbsp;&nbsp; 20<U>)[\s\S]*?(<\/U>년 &nbsp;<U>)[\s\S]*?(<\/U>월 &nbsp;<U>)[\s\S]*?(<\/U>일)/,
      `$1${escapeHtml(filingYear.slice(-2))}$2${escapeHtml(filingMonth)}$3${escapeHtml(filingDay)}$4`
    )
    result = result.replace(
      /(사건번호&nbsp; :&nbsp;&nbsp; )20\d{2}\s+개회\s+&nbsp;\s*<U>[\s\S]*?<\/U>/,
      `$1${escapeHtml(basics.caseNumber || '&nbsp;')}`
    )
    result = result.replace(/(<td class='text03' colspan='2'>)\d{4}\s*\.\s*&nbsp;&nbsp;&nbsp;\d{2}\s*\.\s*&nbsp;&nbsp;&nbsp;\d{2}\s*\.(<\/td>)/, `$1${basics.filingDate}$2`)
    result = result.replace(/(채무자\(신청인\)<\/td>\s*<td class='text03' width='300'>)[^<]*/g, `$1${escapeHtml(basics.applicantName || '&nbsp;')} `)
  }

  if (key === 'prohibition') {
    result = result.replace(
      /(사&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;건<\/td>\s*<td class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>)[\s\S]*?(<\/td>)/,
      `$1${escapeHtml(basics.caseNumber || '&nbsp;')}&nbsp;&nbsp;&nbsp;개인회생$2`
    )
    result = result.replace(
      /(신청인 \(채무자\)<\/td>\s*<td class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>)[\s\S]*?(<\/td>)/,
      `$1${escapeHtml(basics.applicantName || '&nbsp;')}$2`
    )
    result = result.replace(
      /(<td width='140' ><\/td>\s*<td class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>)[\s\S]*?(<\/td>)/,
      `$1${escapeHtml(basics.applicantAddress || '&nbsp;')}$2`
    )
    result = result.replace(
      /(신청인\(채무자\)에 대한 이 법원 )20\d{4}[\s\S]*?호 개인회생사건/,
      `$1${escapeHtml(basics.caseNumber || '&nbsp;')} 개인회생사건`
    )
    result = result.replace(/(<span class='text03'>)\d{4}\s*\.\s*&nbsp;&nbsp;&nbsp;\d{2}\s*\.\s*&nbsp;&nbsp;&nbsp;\d{2}\s*\.(<\/span>)/g, `$1${basics.filingDate}$2`)
  }

  if (key === 'repayment_plan') {
    result = result.replace(
      /(<span class='text03'>&nbsp;&nbsp;사&nbsp;&nbsp;건[\s\S]*?<u>)[\s\S]*?(<\/u><\/span>)/,
      `$1${escapeHtml(filingYear)}$2`
    )
    result = result.replace(
      /(<span class='text03'>&nbsp;개회<\/span>&nbsp;<span class='text03'><u>)[\s\S]*?(<\/u><\/span>\s*&nbsp;\s*<span class='text03'>개인회생)/,
      `$1${escapeHtml(basics.caseNumber.replace(/^.*개회\s*/, '').replace(/\s*호?$/, ''))}$2`
    )
    result = result.replace(
      /(&nbsp;&nbsp;채 무 자&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)[^<(<\n]+(\s*\([^)]+\))?/,
      `$1${escapeHtml(basics.applicantName || '&nbsp;')}${basics.residentNumber ? ` ( ${escapeHtml(basics.residentNumber)} )` : ''}`
    )
    result = result.replace(/(<span class='text03'>)\d{4}\s*\.\s*&nbsp;&nbsp;&nbsp;\d{2}\s*\.\s*&nbsp;&nbsp;&nbsp;\d{2}\s*\.(<\/span><br><br>\s*<span class='text03'>채 &nbsp;무 &nbsp;자)/, `$1${basics.filingDate}$2`)
    result = result.replace(
      /(채 &nbsp;무 &nbsp;자&nbsp;&nbsp;&nbsp;)[^&<]+/,
      `$1${escapeHtml(basics.applicantName || '&nbsp;')}`
    )
    result = result.replace(
      /(대&nbsp;&nbsp;리&nbsp;&nbsp;인&nbsp;&nbsp;&nbsp;)(?:변호사\s*)?[^&<]*/,
      `$1${escapeHtml(basics.lawyerName || '')}`
    )
  }

  if (key === 'assets') {
    result = result.replace(/(<span style='span-size:13.5px' class='text01'>)\(주\)[^<]*(<\/span>)/g, '$1&nbsp;$2')
    result = result.replace(/(<span style='span-size:13.5px' class='text01'>)\d[\d* ]+(<\/span>)/g, '$1&nbsp;$2')
    result = result.replace(/(<span style='span-size:13.5px' class='text01'>)[\d,]+(<\/span>)/g, '$1&nbsp;$2')
    result = result.replace(/(<span class='text01'>)\s*무\s*(<\/span>)/g, '$1&nbsp;$2')
  }

  return result
}

type CreditorDocumentEntry = {
  creditorName: string
  debtContent: string
  addressLine: string
  address: string
  phone: string
  fax: string
  fullClaimAmount: number
  principalAmount: number
  interestAmount: number
  debtDate: string
  proofDate: string
  basisText: string
  rowType: 'secured' | 'unsecured'
}

const buildEntriesFromDebtPages = (pages: Record<string, any>) => {
  const entries: CreditorDocumentEntry[] = []

  const unsecuredDebts = Array.isArray(pages['13_1']?.debts) ? pages['13_1'].debts : []
  unsecuredDebts.forEach((debt: unknown) => {
    if (!debt || typeof debt !== 'object') return
    const debtRecord = debt as Record<string, any>
    const creditorName = String(debtRecord.creditorName || '').trim()
    if (!creditorName) return

    const principalAmount = parseNumeric(debtRecord.principalBalance)
    const interestAmount = parseNumeric(debtRecord.interestBalance)
    entries.push({
      creditorName,
      debtContent: String(debtRecord.customDebtCause || debtRecord.debtCause || '').trim(),
      addressLine: [debtRecord.creditorAddress, debtRecord.creditorPhone ? `(전화) ${debtRecord.creditorPhone}` : '', debtRecord.creditorFax ? `(팩스) ${debtRecord.creditorFax}` : ''].filter(Boolean).join('<br>'),
      address: String(debtRecord.creditorAddress || '').trim(),
      phone: String(debtRecord.creditorPhone || '').trim(),
      fax: String(debtRecord.creditorFax || '').trim(),
      fullClaimAmount: principalAmount + interestAmount,
      principalAmount,
      interestAmount,
      debtDate: String(debtRecord.debtDate || '').trim(),
      proofDate: String(debtRecord.proofDate || '').trim(),
      basisText: String(debtRecord.customCalculationBasis || debtRecord.calculationBasis || debtRecord.proofType || '').trim(),
      rowType: 'unsecured'
    })
  })

  const estateClaims = Array.isArray(pages['4']?.estateClaims) ? pages['4'].estateClaims : []
  estateClaims.forEach((claim: unknown) => {
    if (!claim || typeof claim !== 'object') return
    const claimRecord = claim as Record<string, any>
    const creditorName = String(claimRecord.creditorName || '').trim()
    if (!creditorName) return

    const fullClaimAmount = parseNumeric(claimRecord.debtAmount)
    entries.push({
      creditorName,
      debtContent: String(claimRecord.debtType || '').trim(),
      addressLine: [claimRecord.workerAddress, claimRecord.workerPhone ? `(전화) ${claimRecord.workerPhone}` : '', claimRecord.workerFax ? `(팩스) ${claimRecord.workerFax}` : ''].filter(Boolean).join('<br>'),
      address: String(claimRecord.workerAddress || '').trim(),
      phone: String(claimRecord.workerPhone || '').trim(),
      fax: String(claimRecord.workerFax || '').trim(),
      fullClaimAmount,
      principalAmount: fullClaimAmount,
      interestAmount: 0,
      debtDate: String(claimRecord.debtDate || claimRecord.debtPeriod || '').trim(),
      proofDate: String(claimRecord.proofDate || '').trim(),
      basisText: String(claimRecord.basis || '').trim(),
      rowType: 'secured'
    })
  })

  return entries
}

const creditorFieldKeys = new Set([
  'creditorName',
  'principalBalance',
  'interestBalance',
  'claimAmount',
  'debtAmount',
  'debtContent',
  'debtCause',
  'customDebtCause',
  'proofDate',
  'calculationBasis',
  'customCalculationBasis'
])
const CREDITOR_ENTRY_EXCLUDED_PAGE_IDS = new Set(['15', '15_1', '15_2', '15_3', '15_4', '15_5'])

const buildCreditorEntrySignature = (entry: CreditorDocumentEntry) => [
  entry.rowType,
  entry.creditorName,
  entry.debtContent,
  entry.address,
  entry.phone,
  entry.fax,
  entry.fullClaimAmount,
  entry.principalAmount,
  entry.interestAmount,
  entry.debtDate,
  entry.proofDate,
  entry.basisText
].join('|')

const normalizeCreditorEntry = (candidate: Record<string, any>, pageId = ''): CreditorDocumentEntry | null => {
  const creditorName = String(candidate.creditorName || candidate.creditor || candidate.name || '').trim()
  if (!creditorName) return null

  const principalAmount = parseNumeric(
    candidate.principalBalance ?? candidate.principalAmount ?? candidate.principal ?? candidate.debtAmount
  )
  const interestAmount = parseNumeric(
    candidate.interestBalance ?? candidate.interestAmount ?? candidate.interest ?? candidate.claimedInterest
  )
  const fullClaimAmount = parseNumeric(candidate.claimAmount ?? candidate.amount) || principalAmount + interestAmount
  const debtContent = String(
    candidate.debtContent ||
      candidate.customDebtCause ||
      candidate.debtCause ||
      candidate.debtType ||
      candidate.assetDescription ||
      candidate.proceedingType ||
      candidate.targetAsset ||
      ''
  ).trim()

  const addressPieces = [
    candidate.creditorAddress,
    candidate.address,
    candidate.workerAddress,
    candidate.contactAddress,
    candidate.providerAddress
  ]
    .filter(Boolean)
    .map((item: unknown) => String(item).trim())

  const phone = String(candidate.creditorPhone || candidate.phone || candidate.workerPhone || candidate.contactPhone || '').trim()
  const fax = String(candidate.creditorFax || candidate.fax || candidate.workerFax || candidate.contactFax || candidate.providerFax || '').trim()
  const debtDate = String(candidate.debtDate || candidate.debtPeriod || candidate.startDate || '').trim()
  const proofDate = String(candidate.proofDate || candidate.decisionDate || candidate.serviceDate || '').trim()
  const basisText = String(candidate.calculationBasis || candidate.basis || candidate.note || '').trim()
  const customBasisText = String(candidate.customCalculationBasis || '').trim()

  const rowType = ['11_1', '12', '12_1'].includes(pageId) ? 'secured' : 'unsecured'

  return {
    creditorName,
    debtContent,
    addressLine: [addressPieces.join(' '), phone ? `(전화) ${phone}` : '', fax ? `(팩스) ${fax}` : ''].filter(Boolean).join('<br>'),
    address: addressPieces.join(' '),
    phone,
    fax,
    fullClaimAmount,
    principalAmount,
    interestAmount: Math.max(0, fullClaimAmount - principalAmount || interestAmount),
    debtDate,
    proofDate,
    basisText: customBasisText || basisText,
    rowType
  }
}

const collectCreditorEntriesFromValue = (
  value: unknown,
  pageId: string,
  acc: CreditorDocumentEntry[],
  seen: Set<string>
) => {
  if (CREDITOR_ENTRY_EXCLUDED_PAGE_IDS.has(String(pageId))) return
  if (!value) return

  if (Array.isArray(value)) {
    value.forEach((item) => collectCreditorEntriesFromValue(item, pageId, acc, seen))
    return
  }

  if (typeof value !== 'object') return

  const record = value as Record<string, any>
  const hasCreditorShape = Object.keys(record).some((key) => creditorFieldKeys.has(key))
  if (hasCreditorShape) {
    const normalized = normalizeCreditorEntry(record, pageId)
    if (normalized) {
      const signature = buildCreditorEntrySignature(normalized)

      if (!seen.has(signature)) {
        seen.add(signature)
        acc.push(normalized)
      }
    }
  }

  Object.values(record).forEach((item) => {
    if (item && typeof item === 'object') {
      collectCreditorEntriesFromValue(item, pageId, acc, seen)
    }
  })
}

const collectCreditorEntries = (pages: Record<string, any>) => {
  const directEntries = buildEntriesFromDebtPages(pages)
  const entries: CreditorDocumentEntry[] = [...directEntries]
  const seen = new Set<string>(directEntries.map((entry) => buildCreditorEntrySignature(entry)))

  Object.entries(pages).forEach(([pageId, value]) => {
    collectCreditorEntriesFromValue(value, pageId, entries, seen)
  })

  return entries
}

const buildCreditorsRowBlock = (entry: CreditorDocumentEntry, index: number, isFirst = false) => `
<tr bgcolor='#ffffff'><td rowspan='4' width='36' height='171' class='${isFirst ? 'border_o' : 'border_t'}' align='center' nowrap>
            <span class='text01' style='span-size:13px; '>${index + 1}</span>
        </td>
        <td rowspan='4' width='49' height='171' class='${isFirst ? 'border_l' : 'border_tl'}'>
          <p align='center'>
             <table border='0' cellpadding='0' cellspacing='0' width='41' nowrap>
             <tr><td style='padding-left:2px;' width='41'>
            <span class='text01'>${escapeHtml(entry.creditorName)}</span>
            </td></tr></table>
          </p>
        </td>
        <td width='614' style='height:43.5px;' class='${isFirst ? 'border_l' : 'border_tl'}'>
        <table border='0' cellpadding='0' cellspacing='0' width='100%'>
        <tr>
           <td style='height:59px;' class='border_tlb' width='250'>
             <table border='0' cellpadding='0' cellspacing='0' width='250' style='padding:0px;maring:0px;'>
             <tr>
    <td style='padding-left:5px;'><span class='text01' style='line-height:17px;word-spacing:-1px;'>${[
      [formatHumanDate(entry.debtDate), escapeHtml(entry.debtContent || '채무내용 미입력')].filter(Boolean).join(' '),
      escapeHtml(formatCurrency(entry.principalAmount || entry.fullClaimAmount))
    ].filter(Boolean).join('<br>')}</span></td>
    </tr>
    </table>
           </td>
           <td style='height:59px;' width='414'>
            <table cellpadding='0' cellspacing='0' width='414' style='padding:0px;maring:0px;'>
              <tr><td style='padding-left:3px;'><span class='text01' style='line-height:17px;word-spacing:-1px;'>${buildCreditorContactBlock(entry)}</span></td></tr>
            </table>
           </td>
        </tr>
        </table>
        </td>
      </tr>
      <tr bgcolor='#ffffff'>
        <td width='614' style='height:43.5px;' class='border_tl'>
          <table border='0' cellpadding='0' cellspacing='0' width='100%'>
          <tr><td style='height:43.5px;' class='border_tlb' width='464'>
               <table border='0' cellpadding='0' cellspacing='0' width='100%'>
               <tr><td width='5'></td><td>
              <span class='text01' style='line-height:20px;'>${formatCurrency(entry.fullClaimAmount)} 및 이중 원금잔액 금 ${formatCurrency(
                entry.principalAmount || entry.fullClaimAmount
              )}에 대한 ${escapeHtml(entry.proofDate || '-')} 부터 완제일까지 약정이율의 비율에 의한 금원</span>
               </td></tr></table>
          </td>
          <td height='37' width='120'>
               <span class='text01'>&nbsp;&nbsp;<span class='text04'>□</span></span>
               <span class='text01'>&nbsp;부속서류</span><br>
               <span class='text01'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(1, 2, 3, 4 
                )</span></td></tr></table>
        </td>
      </tr>
      <tr bgcolor='#ffffff'>
        <td width='614' height='37' class='border_tl'>
          <table border='0' cellpadding='0' cellspacing='0' width='100%'>
            <tr height='37'>
              <td width='153' class='border_tlb' align='right'><span class='text01'>${formatCurrency(entry.principalAmount || entry.fullClaimAmount)}&nbsp;&nbsp;</span></td>
              <td width='461'>
                <table border='0' cellpadding='0' cellspacing='0' width='100%'>
                <tr height='37'><td width='5'></td><td><span class='text01'>${escapeHtml(
                  buildCreditorBasisText(entry)
                )}</span></td></tr>
                </table>
              </td>
            </tr>
          </table>
        </td>
      </tr>
      <tr bgcolor='#ffffff'>
        <td width='614' height='37' class='border_tl'>
          <table border='0' cellpadding='0' cellspacing='0' width='100%'>
            <tr height='37'>
              <td width='153' class='border_tlb' align='right'><span class='text01'>${formatCurrency(entry.interestAmount)}&nbsp;&nbsp;</span></td>
              <td width='461'>
                <table border='0' cellpadding='0' cellspacing='0' width='100%'>
                <tr height='37'><td width='5'></td><td><span class='text01'>${escapeHtml(
                  buildCreditorBasisText(entry)
                )}</span></td></tr>
                </table>
              </td>
            </tr>
          </table>
        </td>
      </tr>`

const formatHumanDate = (value: string) => {
  const text = String(value || '').trim()
  if (!text) return ''

  const match = text.match(/(\d{4})[-./ ]+(\d{1,2})[-./ ]+(\d{1,2})/)
  if (!match) return text

  return `${match[1]}. ${match[2].padStart(2, '0')}. ${match[3].padStart(2, '0')}.`
}

const buildCreditorBasisText = (entry: CreditorDocumentEntry) => {
  const proofDateText = formatHumanDate(entry.proofDate)
  if (entry.basisText) {
    if (proofDateText && !entry.basisText.includes('산정기준일')) {
      return `${entry.basisText} 참조 ( 산정기준일 ${proofDateText} )`
    }
    return entry.basisText
  }

  if (proofDateText) {
    return `채무확인서 참조 ( 산정기준일 ${proofDateText} )`
  }

  return '채무확인서 참조'
}

const buildCreditorContactBlock = (entry: CreditorDocumentEntry) => {
  const lines: string[] = []
  if (entry.address) {
    lines.push(escapeHtml(entry.address))
  }

  const phoneFaxLine = [`(전화) ${escapeHtml(entry.phone || '')}`, `(팩스) ${escapeHtml(entry.fax || '')}`]
    .join('&nbsp;&nbsp;&nbsp;&nbsp;')

  if (!lines.length && entry.addressLine) {
    return entry.addressLine
  }

  lines.push(phoneFaxLine)

  return lines.length ? lines.join('<br>') : '&nbsp;'
}

type SecuredCreditorAppendixEntry = {
  creditorName: string
  principalAmount: number
  interestAmount: number
  expectedRepaymentAmount: number
  unrecoveredAmount: number
  securedClaimAmount: number
  securityDetail: string
}

const isVehicleCollateralType = (value: unknown) => /자동차|오토바이|선박/.test(String(value || '').trim())

const buildSecuredPropertyGuaranteeDetail = (item: Record<string, any>, expectedRepaymentBase: number) => {
  const securityDateText = formatHumanDate(String(item.securityDate || item.serviceDate || item.decisionDate || '').trim())
  const collateralLimitAmount = parseNumeric(item.collateralLimitAmount)
  const assetType = String(item.assetType || '').trim()
  const assetDescription = String(item.assetDescription || '').trim()
  const vehicleName = String(item.vehicleName || assetDescription || '').trim()
  const vehicleNumber = String(item.vehicleNumber || '').trim()
  const vehicleYear = String(item.vehicleYear || '').trim()
  const collateralMarketValue = parseNumeric(item.collateralMarketValue)

  const firstLine = [
    securityDateText,
    '저당권',
    collateralLimitAmount > 0 ? `담보한도액 ${formatCurrency(collateralLimitAmount)}` : ''
  ].filter(Boolean).join(', ')

  const secondLine = isVehicleCollateralType(assetType)
    ? [
        `[${assetType || '자동차'}]`,
        vehicleName,
        vehicleYear ? `${vehicleYear}연식` : '',
        vehicleNumber ? `차량번호 : ${vehicleNumber}` : ''
      ].filter(Boolean).join(' ')
    : [
        assetType ? `[${assetType}]` : '',
        assetDescription
      ].filter(Boolean).join(' ')

  const thirdLine = collateralMarketValue > 0
    ? isVehicleCollateralType(assetType)
      ? `환가예상액(시가) ${formatCurrency(collateralMarketValue)}원의 70% = ${formatCurrency(expectedRepaymentBase)}원`
      : `환가예상액(시가) ${formatCurrency(collateralMarketValue)}원`
    : ''

  return [firstLine, secondLine, thirdLine].filter(Boolean)
}

const buildSecuredCreditorAppendixEntries = (context: RecoveryDocumentContext): SecuredCreditorAppendixEntry[] => {
  const page11_1 = context.pages['11_1'] || {}
  const page12 = context.pages['12'] || {}
  const page12_1 = context.pages['12_1'] || {}
  const page13_1 = context.pages['13_1'] || {}
  const page15_1 = context.pages['15_1'] || {}
  const entries: SecuredCreditorAppendixEntry[] = []

  const buildEntry = (
    creditorName: unknown,
    principalValue: unknown,
    interestValue: unknown,
    collateralValue: unknown,
    detailParts: Array<unknown>,
    collateralCapValue?: unknown
  ): SecuredCreditorAppendixEntry | null => {
    const normalizedCreditorName = String(creditorName || '').trim()
    if (!normalizedCreditorName) return null

    const principalAmount = parseNumeric(principalValue)
    const interestAmount = parseNumeric(interestValue)
    const totalAmount = principalAmount + interestAmount
    const collateralAmount = Math.max(0, parseNumeric(collateralValue))
    const collateralCapAmount = Math.max(0, parseNumeric(collateralCapValue))
    const expectedRepaymentAmount = Math.min(
      totalAmount,
      collateralAmount,
      collateralCapAmount > 0 ? collateralCapAmount : Number.POSITIVE_INFINITY
    )
    const unrecoveredAmount = Math.max(0, totalAmount - expectedRepaymentAmount)
    const securedClaimAmount = expectedRepaymentAmount
    const securityDetail = detailParts
      .map((item) => String(item || '').trim())
      .filter(Boolean)
      .join('<br>')

    return {
      creditorName: normalizedCreditorName,
      principalAmount,
      interestAmount,
      expectedRepaymentAmount,
      unrecoveredAmount,
      securedClaimAmount,
      securityDetail
    }
  }

  const insuranceItems = Array.isArray(page11_1.insuranceItems) ? page11_1.insuranceItems : []
  insuranceItems.forEach((item: Record<string, any>) => {
    if (item?.hasLoan !== 'Y') return
    const loan = item?.loan || {}
    const entry = buildEntry(
      loan.creditorName,
      loan.principalBalance,
      loan.interestBalance,
      item.refundAmount,
      [
        [formatHumanDate(String(loan.debtDate || '').trim()), '보험해약환급금 담보'].filter(Boolean).join(' '),
        [item.companyName, item.policyNumber || item.productName].filter(Boolean).join(' / '),
        parseNumeric(item.refundAmount) > 0 ? `예상환급금 ${formatCurrency(item.refundAmount)}` : ''
      ]
    )

    if (entry) entries.push(entry)
  })

  const leases = Array.isArray(page12.leases) ? page12.leases : []
  leases.forEach((lease: Record<string, any>) => {
    if (lease?.hasContractLoan !== 'Y') return
    const loan = lease?.loan || {}
    const collateralAmount = resolveLeaseAssetAmount(lease)
    const entry = buildEntry(
      loan.creditorName,
      loan.principalBalance,
      loan.interestBalance,
      collateralAmount,
      [
        [formatHumanDate(String(loan.debtDate || '').trim()), '임차보증금 담보'].filter(Boolean).join(' '),
        lease.address,
        parseNumeric(lease.depositAmount) > 0 ? `보증금 ${formatCurrency(lease.depositAmount)}` : '',
        collateralAmount > 0 ? `환가예상액(인정가액) ${formatCurrency(collateralAmount)}` : ''
      ]
    )

    if (entry) entries.push(entry)
  })

  const retirementLoans = Array.isArray(page12_1.retirementLoans) ? page12_1.retirementLoans : []
  retirementLoans.forEach((loan: Record<string, any>) => {
    const entry = buildEntry(
      loan.creditorName,
      loan.principalBalance,
      loan.interestBalance,
      page12_1?.formData?.retirementAmount,
      [
        '퇴직금채권 담보',
        page12_1?.formData?.retirementInstitution
          ? `지급기관 ${String(page12_1.formData.retirementInstitution).trim()}`
          : '',
        parseNumeric(page12_1?.formData?.retirementAmount) > 0
          ? `환가예상액 ${formatCurrency(page12_1.formData.retirementAmount)}`
          : ''
      ]
    )

    if (entry) entries.push(entry)
  })

  const debtEntries = Array.isArray(page13_1.debts) ? page13_1.debts : []
  const propertyGuaranteeItems = Array.isArray(page15_1.items) ? page15_1.items : []
  propertyGuaranteeItems
    .filter((item: Record<string, any>) => String(item?.enabled || '').trim() === 'Y')
    .forEach((item: Record<string, any>) => {
      const principalAmount = parseNumeric(item.principalBalance)
      const interestAmount = parseNumeric(item.interestBalance)
      const matchedDebt = debtEntries.find((debt: Record<string, any>) =>
        String(debt?.creditorName || '').trim() === String(item?.creditorName || '').trim() &&
        parseNumeric(debt?.principalBalance) === principalAmount &&
        parseNumeric(debt?.interestBalance) === interestAmount
      )

      const collateralMarketValue = parseNumeric(item.collateralMarketValue)
      const expectedRepaymentBase = isVehicleCollateralType(item.assetType)
        ? Math.floor(collateralMarketValue * 0.7)
        : collateralMarketValue

      const detailParts = buildSecuredPropertyGuaranteeDetail(item, expectedRepaymentBase)
      const entry = buildEntry(
        item.creditorName,
        principalAmount,
        interestAmount,
        expectedRepaymentBase,
        [
          ...detailParts,
          matchedDebt?.proofDate ? `산정기준일 ${formatHumanDate(String(matchedDebt.proofDate).trim())}` : ''
        ],
        item.collateralLimitAmount
      )

      if (entry) entries.push(entry)
    })

  return entries
}

const buildSecuredCreditorAppendixRow = (
  entry: SecuredCreditorAppendixEntry | null,
  index: number,
  isFirst = false
) => {
  const borderClass = isFirst ? 'border_o' : 'border_t'
  const sideBorderClass = isFirst ? 'border_l' : 'border_tl'

  return `
 <tr bgcolor='#ffffff' style='height:36.5px;'>
  <td class='${borderClass}' rowspan='3' width='42'><p align='center'><span class='text01'>${entry ? index + 1 : '&nbsp;'}</span></p></td>
  <td class='${sideBorderClass}' rowspan='3' width='103'><p align='left'><span class='text01'>${entry ? escapeHtml(entry.creditorName) : '&nbsp;'}</span></p></td>
  <td class='border_tl' width='156'><p align='right'><span class='text01'>${entry ? `${formatCurrency(entry.principalAmount)}&nbsp;&nbsp;` : '&nbsp;&nbsp;'}</span></p></td>
  <td class='border_tl' rowspan='2' width='150'><p align='right'><span class='text01'>${entry ? `${formatCurrency(entry.expectedRepaymentAmount)}&nbsp;&nbsp;` : '&nbsp;&nbsp;'}</span></p></td>
  <td class='border_tl' rowspan='2' width='150'><p align='right'><span class='text01'>${entry ? `${formatCurrency(entry.unrecoveredAmount)}&nbsp;&nbsp;` : '&nbsp;&nbsp;'}</span></p></td>
  <td class='border_tl' rowspan='2' width='150'><p align='right'><span class='text01'>${entry ? `${formatCurrency(entry.securedClaimAmount)}&nbsp;&nbsp;` : '&nbsp;&nbsp;'}</span></p></td>
 </tr>
 <tr bgcolor='#ffffff'>
  <td class='border_tl' style='height:36.5px;'><p align='right'><span class='text01'>${entry ? `${formatCurrency(entry.interestAmount)}&nbsp;&nbsp;` : '&nbsp;&nbsp;'}</span></p></td>
 </tr>
 <tr bgcolor='#ffffff'>
  <td class='border_tl' colspan='4' style='height:85px;'>
    <table border='0' cellpadding='0' cellspacing='0' width='100%'><tr>
    <td width='5'></td>
    <td><span class='text01'>${entry ? entry.securityDetail || '&nbsp;' : '&nbsp;'}</span></td>
    </tr></table>
  </td>
 </tr>`
}

const buildSecuredCreditorsAppendixBody = (context: RecoveryDocumentContext) => {
  const entries = buildSecuredCreditorAppendixEntries(context)
  const paddedEntries = [...entries]
  while (paddedEntries.length < 4) {
    paddedEntries.push(null as never)
  }

  const rowMarkup = paddedEntries
    .map((entry, index) => buildSecuredCreditorAppendixRow(entry, index, index === 0))
    .join('')

  const principalTotal = entries.reduce((sum, item) => sum + item.principalAmount, 0)
  const interestTotal = entries.reduce((sum, item) => sum + item.interestAmount, 0)
  const expectedRepaymentTotal = entries.reduce((sum, item) => sum + item.expectedRepaymentAmount, 0)
  const unrecoveredTotal = entries.reduce((sum, item) => sum + item.unrecoveredAmount, 0)
  const securedClaimTotal = entries.reduce((sum, item) => sum + item.securedClaimAmount, 0)

  return `
    ${PAGE_BREAK_MARKER_HTML}
    <span class='text04'>부속서류 1.&nbsp;</span><br>
    <div align='center'><span class='text06_b'>별제권부채권 및 이에 준하는 채권의 내역</span></div>
    <center><div style='width:100%' align='right'><span class='printhidden'></span></div></center>
    <div align='right'><span class='text02'>(단위 : 원)</span></div>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%' class='docxline'>
      <tr>
        <td rowspan='3' width='42' class='border_o'><p align='center'><span class='text02'>채권<br>번호</span></p></td>
        <td rowspan='3' width='103' class='border_l'><p align='center'><span class='text02'>채&nbsp;&nbsp;권&nbsp;&nbsp;자</span></p></td>
        <td width='156' height='39' class='border_l'><p align='center'><span class='text02'>① 채권현재액(원금)</span></p></td>
        <td rowspan='2' width='150' height='48' class='border_l'><p align='left'><span class='text02'>&nbsp;③ 별제권행사등으로<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;변제가 예상되는<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;채권액</span></p></td>
        <td rowspan='2' width='150' height='48' class='border_l'><p align='left'><span class='text01'>&nbsp;④ 별제권행사등으로도<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;변제받을 수 없을<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;채권액<br></span></p></td>
        <td rowspan='2' width='150' height='48' class='border_l'><p align='left'><span class='text01'>&nbsp;⑤ 담보부회생채권액</span><br><br><br></p></td>
      </tr>
      <tr>
        <td width='150' height='39' class='border_tl'><p align='center'><span class='text02'>② 채권현재액(이자)</span></p></td>
      </tr>
      <tr>
        <td colspan='4' style='height:49.5px;' class='border_tl'><p align='left'><span class='text02'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;⑥ 별제권 등의 내용 및 목적물</span></p></td>
      </tr>
      ${rowMarkup}
      <tr bgcolor='#ffffff'>
        <td class='border_t' rowspan='2' colspan='2' width='144' style='height:70.5px;'><p align='center'><span class='text02'>합 &nbsp;계</span></p></td>
        <td class='border_tl' style='height:43.5px;'><p align='right'><span class='text02'>${formatCurrency(principalTotal)}&nbsp;&nbsp;</span></p></td>
        <td class='border_tl' rowspan='2' style='height:70.5px;'><p align='right'><span class='text02'>${formatCurrency(expectedRepaymentTotal)}&nbsp;&nbsp;</span></p></td>
        <td class='border_tl' rowspan='2' style='height:70.5px;'><p align='right'><span class='text02'>${formatCurrency(unrecoveredTotal)}&nbsp;&nbsp;</span></p></td>
        <td class='border_tl' rowspan='2' style='height:70.5px;'><p align='right'><span class='text02'>${formatCurrency(securedClaimTotal)}&nbsp;&nbsp;</span></p></td>
      </tr>
      <tr bgcolor='#ffffff'>
        <td class='border_tl' style='height:43.5px;'><p align='right'><span class='text02'>${formatCurrency(interestTotal)}&nbsp;&nbsp;</span></p></td>
      </tr>
    </table>
  `
}

const buildCreditorsReferenceBody = (context: RecoveryDocumentContext) => {
  const page1 = context.pages['1'] || {}
  const page4 = context.pages['4'] || {}
  const applicantName = context.caseInfo?.applicant_name || page1?.applicantName || '신청인'
  const residentNumber = formatResidentNumber(page1?.residentNumber || page1?.ssn || context.caseInfo?.resident_number || '')
  const caseNumber = context.caseInfo?.case_number || buildRecoveryCaseNumber(page1?.caseYear, page1?.caseNumber) || '사건번호 미입력'
  const baseDate = page4?.interestBaseDate
    ? `${page4.interestBaseDate.year} .&nbsp;&nbsp;&nbsp;${page4.interestBaseDate.month} .&nbsp;&nbsp;&nbsp;${page4.interestBaseDate.day} .`
    : formatDottedDate(context.caseInfo?.registration_date)

  const entries = collectCreditorEntries(context.pages)
  const normalizedEntries = entries.length
    ? entries
    : [{
        creditorName: '',
        debtContent: '',
        addressLine: '',
        address: '',
        phone: '',
        fax: '',
        fullClaimAmount: 0,
        principalAmount: 0,
        interestAmount: 0,
        debtDate: '',
        proofDate: '',
        basisText: '',
        rowType: 'unsecured' as const
      }]

  const totalAmount = normalizedEntries.reduce((sum, item) => sum + item.fullClaimAmount, 0)
  const principalAmount = normalizedEntries.reduce((sum, item) => sum + item.principalAmount, 0)
  const interestAmount = normalizedEntries.reduce((sum, item) => sum + item.interestAmount, 0)
  const securedAmount = normalizedEntries
    .filter((item) => item.rowType === 'secured')
    .reduce((sum, item) => sum + item.fullClaimAmount, 0)
  const unsecuredAmount = normalizedEntries
    .filter((item) => item.rowType === 'unsecured')
    .reduce((sum, item) => sum + item.fullClaimAmount, 0)

  const rowMarkup = normalizedEntries
    .map((item, index) => buildCreditorsRowBlock(item, index, index === 0))
    .join('')

  return (
    extractBodyInnerHtml(recoveryCreditorsBaseHtml)
    .replaceAll('{{CASE_NUMBER}}', escapeHtml(caseNumber))
    .replaceAll('{{APPLICANT_NAME}}', escapeHtml(applicantName))
    .replaceAll('{{RESIDENT_NUMBER}}', escapeHtml(residentNumber))
    .replaceAll('{{BASE_DATE}}', baseDate)
    .replaceAll('{{TOTAL_AMOUNT}}', `${formatCurrency(totalAmount)}`)
    .replaceAll('{{PRINCIPAL_AMOUNT}}', `${formatCurrency(principalAmount)}`)
    .replaceAll('{{INTEREST_AMOUNT}}', `${formatCurrency(interestAmount)}`)
    .replaceAll('{{SECURED_AMOUNT}}', `${formatCurrency(securedAmount)}`)
    .replaceAll('{{UNSECURED_AMOUNT}}', `${formatCurrency(unsecuredAmount)}`)
    .replace('{{CREDITOR_ROWS}}', rowMarkup)
  ) + buildSecuredCreditorsAppendixBody(context)
}

const buildRecoveryCaseNumber = (year: unknown, number: unknown) => {
  const yearText = String(year || '').trim()
  const numberText = String(number || '').trim()
  if (yearText && numberText) return `${yearText} 개회 ${numberText}`
  return numberText || yearText
}

const buildAssetSummarySection = (title: string, rows: Array<[string, string]>) => `
  <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center' style='margin-top:18px;'>
    <tr>
      <td>
        <span class='text03' style='font-weight:bold;'>${escapeHtml(title)}</span>
      </td>
    </tr>
  </table>
  <table border='1' cellpadding='6' cellspacing='0' width='100%' align='center' class='docxline' style='margin-top:6px;'>
    ${rows.map(([label, value]) => `
      <tr bgcolor='#ffffff'>
        <td width='28%'><span class='text01'>${escapeHtml(label)}</span></td>
        <td><span class='text01'>${escapeHtml(value || '해당 없음')}</span></td>
      </tr>
    `).join('')}
  </table>
`

const buildAssetsDepositRows = (accounts: Array<Record<string, any>>) => {
  if (!accounts.length) return ''

  const cellText = (value: unknown) => {
    const text = String(value || '').trim()
    return text ? escapeHtml(text) : '&nbsp;'
  }

  const totalBalance = accounts.reduce((sum, account) => sum + parseNumeric(account.balance), 0)
  const firstRows = accounts.slice(0, 6)
  const blankRows = Math.max(0, 6 - firstRows.length)

  const detailRows = firstRows.map((account, index) => `
    <tr bgcolor='#ffffff' style='height:29.5px;'>
      <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
      <td class='border_tl' width='125' style='padding:5px;'><span style='span-size:13.5px' class='text01'>${cellText(account.bankName)}</span></td>
      <td class='border_tl' width='138' style='padding:5px;'><span style='span-size:13.5px' class='text01'>${cellText(account.accountNumber || account.type)}</span></td>
      <td class='border_tl' width='87' align='right'><span style='span-size:13.5px' class='text01'>${cellText(account.balance)}</span>&nbsp;</td>
      <td class='border_tl' width='87' align='right'><span style='span-size:13.5px' class='text01'>${account.hasLoan === 'Y' ? cellText(account.pledgeAmount || '0') : '0'}</span>&nbsp;</td>
    </tr>
  `).join('')

  const fillerRows = Array.from({ length: blankRows }, () => `
    <tr bgcolor='#ffffff' style='height:29.5px;'>
      <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
      <td class='border_tl' width='125' style='padding:5px;'><span style='span-size:13.5px' class='text01'>&nbsp;</span></td>
      <td class='border_tl' width='138' style='padding:5px;'><span style='span-size:13.5px' class='text01'>&nbsp;</span></td>
      <td class='border_tl' width='87' align='right'><span style='span-size:13.5px' class='text01'>&nbsp;</span>&nbsp;</td>
      <td class='border_tl' width='87' align='right'><span style='span-size:13.5px' class='text01'>&nbsp;</span>&nbsp;</td>
    </tr>
  `).join('')

  return `
    <tr bgcolor='#ffffff' style='height:43.5px;'>
      <td class='border_t' rowspan='${firstRows.length + blankRows + 1}' width='131' align='left'><span class='text01'>&nbsp;예&nbsp;&nbsp;금</span></td>
      <td class='border_tl' width='90' rowspan='${firstRows.length + blankRows + 1}' align='right'><span class='text01'>&nbsp;${totalBalance.toLocaleString('ko-KR')}&nbsp;</span></td>
      <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
      <td class='border_tl' width='125' align='center'><span class='text01'>&nbsp;금 융 기 관 명</span></td>
      <td class='border_tl' width='138' align='center'><span class='text01'>계 좌 번 호</span></td>
      <td class='border_tl' width='87' align='center'><span class='text01'>잔&nbsp;&nbsp;고</span></td>
      <td class='border_tl' width='87' align='center'><span class='text01'>질권설정액</span></td>
    </tr>
    ${detailRows}
    ${fillerRows}
    <tr bgcolor='#ffffff' style='height:29.5px;'>
      <td class='border_tl' align='center'><span class='text01'></span></td>
      <td class='border_tl' colspan='4'><span class='text01'>&nbsp;예금 &nbsp; 중에서 압류할 수 없는 예금등 &nbsp; 제외</span></td>
    </tr>
  `
}

const buildAssetsMainTable = (context: RecoveryDocumentContext) => {
  const basics = getContextBasics(context)
  const page7 = context.pages['7'] || {}
  const page10 = context.pages['10'] || {}
  const page11 = context.pages['11'] || {}
  const page11_1 = context.pages['11_1'] || {}
  const page11_3 = context.pages['11_3'] || {}
  const page12 = context.pages['12'] || {}
  const page12_1 = context.pages['12_1'] || {}

  const cashAmount = parseNumeric(page11?.formData?.cashAmount)
  const accounts = Array.isArray(page11?.accounts) ? page11.accounts : []
  const depositsTotal = accounts.reduce((sum: number, item: any) => sum + parseNumeric(item.balance), 0)
  const vehicles = Array.isArray(page10?.vehicles) ? page10.vehicles : []
  const spouseVehicles = Array.isArray(page10?.spouseVehicles) ? page10.spouseVehicles : []
  const vehicleTotal = vehicles.reduce((sum: number, item: any) => sum + parseNumeric(item.price), 0)
  const spouseVehicleTotal = spouseVehicles.reduce((sum: number, item: any) => sum + parseNumeric(item.price), 0)
  const insurances = Array.isArray(page11_1?.insuranceItems) ? page11_1.insuranceItems : []
  const insuranceTotal = insurances.reduce((sum: number, item: any) => sum + parseNumeric(item.refundAmount), 0)
  const leases = Array.isArray(page12?.leases) ? page12.leases : []
  const leaseTotal = leases.reduce((sum: number, item: any) => sum + resolveLeaseAssetAmount(item), 0)
  const facilities = Array.isArray(page12_1?.facilities) ? page12_1.facilities : []
  const facilityTotal = facilities.reduce((sum: number, item: any) => sum + parseNumeric(item.estimatedValue), 0)
  const retirementAmount = parseNumeric(page12_1?.formData?.retirementAmount)
  const receivables = Array.isArray(page12?.receivables) ? page12.receivables : []
  const receivableTotal = receivables.reduce((sum: number, item: any) => sum + parseNumeric(item.amount), 0)
  const otherAssets = Array.isArray(page11_3?.assetItems) ? page11_3.assetItems : []
  const otherAssetTotal = otherAssets.reduce((sum: number, item: any) => sum + parseNumeric(item.estimatedValue), 0)
  const realEstateNote = [
    page7.hasApplicantProperty ? `본인 명의 ${[page7.apartmentCount ? `아파트 ${page7.apartmentCount}건` : '', page7.houseCount ? `주택 ${page7.houseCount}건` : '', page7.landCount ? `토지 ${page7.landCount}건` : ''].filter(Boolean).join(', ')}` : '',
    page7.hasSpouseProperty ? `배우자 명의 ${[page7.spouseApartmentCount ? `아파트 ${page7.spouseApartmentCount}건` : '', page7.spouseHouseCount ? `주택 ${page7.spouseHouseCount}건` : '', page7.spouseLandCount ? `토지 ${page7.spouseLandCount}건` : ''].filter(Boolean).join(', ')}` : ''
  ].filter(Boolean).join(' / ')

  const totalAssetAmount = cashAmount + depositsTotal + insuranceTotal + vehicleTotal + leaseTotal + facilityTotal + retirementAmount + receivableTotal + otherAssetTotal
  const exemptAmount = resolveLeaseAssetAmount(leases[0]) + insuranceTotal + depositsTotal

  const span = (text?: string) => `<span class='text01'>${escapeHtml(text || '') || '&nbsp;'}</span>`
  const money = (value: number) => `${Math.max(0, Number(value) || 0).toLocaleString('ko-KR')}&nbsp;`

  const rows: string[] = []

  if (cashAmount > 0) {
    rows.push(`
      <tr bgcolor='#ffffff' height='34'>
        <td class='border_t' width='131'><p align='left'><span class='text01'>&nbsp;현&nbsp;&nbsp;금</span></p></td>
        <td class='border_tl' width='90'><p align='right'><span class='text01'>${money(cashAmount)}</span></p></td>
        <td class='border_tl' width='42'><p align='center'><span class='text01'>&nbsp;</span></p></td>
        <td class='border_tl' colspan='4' width='437'><p align='justify'><span class='text01'>&nbsp;보유 현금</span></p></td>
      </tr>
    `)
  }

  if (accounts.length) {
    rows.push(buildAssetsDepositRows(accounts))
  }

  if (insurances.length) {
    rows.push(`
      <tr bgcolor='#ffffff' height='34'>
        <td class='border_t' width='131' align='left'><span class='text01'>&nbsp;보&nbsp;&nbsp;험</span></td>
        <td class='border_tl' width='90' align='right'><span class='text01'>${money(insuranceTotal)}</span></td>
        <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl' colspan='5' width='437' align='left'><span class='text01'>&nbsp;별지기재와 같음 (${insurances.length}건)<br>&nbsp;(예상환급금 &nbsp; 중에서 압류할 수 없는 보험금 &nbsp; 제외)</span></td>
      </tr>
    `)
  }

  if (vehicles.length) {
    rows.push(`
      <tr bgcolor='#ffffff' height='34'>
        <td class='border_t' width='131' align='left'><span class='text01'>&nbsp;자동차/선박/오토바이</span></td>
        <td class='border_tl' width='90' align='right'><span class='text01'>${money(vehicleTotal)}</span></td>
        <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl' colspan='5' width='437' align='left'><span class='text01'>${escapeHtml(vehicles.map((item: any) => formatVehicleEntryText(item)).join(', '))}</span></td>
      </tr>
    `)
  }

  if (spouseVehicles.length) {
    rows.push(`
      <tr bgcolor='#ffffff' height='34'>
        <td class='border_t' width='131' align='left'><span class='text01'>&nbsp;배우자 명의 차량</span></td>
        <td class='border_tl' width='90' align='right'><span class='text01'>${money(spouseVehicleTotal)}</span></td>
        <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl' colspan='5' width='437' align='left'><span class='text01'>${escapeHtml(spouseVehicles.map((item: any) => [
          item.type || '차량',
          item.price ? `시가 ${item.price}원` : '',
          item.securedDebtAmount ? `피담보채무 ${item.securedDebtAmount}원` : ''
        ].filter(Boolean).join(' / ')).join(', '))}</span></td>
      </tr>
    `)
  }

  if (leases.length) {
    rows.push(`
      <tr bgcolor='#ffffff'>
        <td class='border_t' width='131' align='left'><span class='text01'>&nbsp;임차보증금</span></td>
        <td class='border_tl' width='90' align='right'><span class='text01'>${money(leaseTotal)}</span></td>
        <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl' colspan='4' style='height:51.5px;' align='left'><span class='text01'>&nbsp;별지기재와 같음 (${leases.length}건)</span></td>
      </tr>
    `)
  }

  if (realEstateNote) {
    rows.push(`
      <tr bgcolor='#ffffff' height='34'>
        <td class='border_t' width='131' align='left'><span class='text01'>&nbsp;부&nbsp; 동&nbsp; 산</span></td>
        <td class='border_tl' width='90' align='right'><span class='text01'>${money(0)}</span></td>
        <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl' colspan='4' width='312' align='left'><span class='text01'>${escapeHtml(realEstateNote)}</span></td>
      </tr>
    `)
  }

  if (facilities.length) {
    rows.push(`
      <tr bgcolor='#ffffff' height='34'>
        <td class='border_t' width='131' align='left'><span class='text01'>&nbsp;사업용 설비, 비품 등</span></td>
        <td class='border_tl' width='90' align='right'><span class='text01'>${money(facilityTotal)}</span></td>
        <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl' colspan='4' width='312' align='left'><span class='text01'>${escapeHtml(facilities.map((item: any) => [item.assetName, item.estimatedValue ? `예상가액 ${item.estimatedValue}` : '', item.note].filter(Boolean).join(' / ')).join(', '))}</span></td>
      </tr>
    `)
  }

  if (receivables.length) {
    const shown = receivables.slice(0, 3)
    rows.push(shown.map((item: any, index: number) => `
      <tr bgcolor='#ffffff' style='height:43.5px;'>
        ${index === 0 ? `<td class='border_t' rowspan='${shown.length}' width='131' align='left'><span class='text01'>&nbsp;대여금 채권</span></td>
        <td class='border_tl' rowspan='${shown.length}' width='90' align='right'><span class='text01'>${money(receivableTotal)}</span></td>` : ''}
        <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl' colspan='5' width='437' align='left'><table cellpadding='0' cellspacing='0' width='100%'><tr><td width='50%'><span class='text01'>&nbsp;상대방 채무자 ${index + 1} : ${escapeHtml(item.debtorName || '')}</span></td><td width='50%'><span class='text01'>채권액 : ${escapeHtml(item.amount || '')}</span></td></tr></table></td>
      </tr>
    `).join(''))
  }

  if (retirementAmount > 0) {
    rows.push(`
      <tr bgcolor='#ffffff' height='54'>
        <td class='border_t' width='131' align='left'><span class='text01'>&nbsp;예상퇴직금</span></td>
        <td class='border_tl' width='90' align='right'><span class='text01'>${money(retirementAmount)}</span></td>
        <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl' colspan='4' width='437' align='left' style='padding:5px;'><span class='text01'>근무처 : ${escapeHtml(page12_1?.formData?.retirementInstitution || '')}</span></td>
      </tr>
    `)
  }

  if (otherAssets.length) {
    rows.push(`
      <tr bgcolor='#ffffff' height='54'>
        <td class='border_t' width='131' align='left'><span class='text01'>&nbsp;기타 (&nbsp;&nbsp;)</span></td>
        <td class='border_tl' width='90' align='right'><span class='text01'>${money(otherAssetTotal)}</span></td>
        <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl' colspan='4' width='437' align='left' style='padding:5px;'><span class='text01'>${escapeHtml(otherAssets.map((item: any) => [item.description, item.estimatedValue ? `예상가액 ${item.estimatedValue}` : ''].filter(Boolean).join(' / ')).join(', '))}</span></td>
      </tr>
    `)
  }

  rows.push(`
    <tr bgcolor='#ffffff' height='34'>
      <td class='border_t' width='131' align='left'><span class='text01'>&nbsp;합&nbsp;&nbsp;계</span></td>
      <td class='border_tl' width='90' align='right'><span class='text01'>${money(totalAssetAmount)}</span></td>
      <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
      <td class='border_tl' colspan='4' width='437' align='left'><span class='text01'>&nbsp;</span></td>
    </tr>
    <tr bgcolor='#ffffff' height='34'>
      <td class='border_t' width='131' align='left'><span class='text01'>&nbsp;면제재산 신청금액</span></td>
      <td class='border_tl' width='90' align='right'><span class='text01'>${money(exemptAmount)}</span></td>
      <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
      <td class='border_tl' colspan='4' width='437' align='left'><span class='text01'>&nbsp;${exemptAmount > 0 ? '면제재산 신청 예정' : '해당사항 없음'}</span></td>
    </tr>
    <tr bgcolor='#ffffff' height='34'>
      <td class='border_t' width='131' align='left'><span class='text01'>&nbsp;청산가치</span></td>
      <td class='border_tl' width='90' align='right'><span class='text01'>${money(totalAssetAmount)}</span></td>
      <td class='border_tl' width='42' align='center'><span class='text01'>&nbsp;</span></td>
      <td class='border_tl' colspan='4' width='437' align='left'><span class='text01'>&nbsp;</span></td>
    </tr>
  `)

  return `
    <table style='border-collapse:collapse' border='1' bordercolor='#000000' cellpadding='0' cellspacing='0' width='100%' align='center' class='docxline'>
      <tr bgcolor='#ffffff' height='34'>
        <td class='border_o' width='131' align='center' style='span-weight:bold'><span class='text01'>명&nbsp;&nbsp;칭</span></td>
        <td class='border_l' width='90' align='center' style='span-weight:bold'><span class='text01'>금액 (시가)<br></span></td>
        <td class='border_l' width='42' align='center' style='span-weight:bold'><span class='text01'>압류등<br>유ㆍ무</span></td>
        <td class='border_l' width='437' colspan='4' align='center' style='span-weight:bold'><span class='text01'>비&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;고</span></td>
      </tr>
      ${rows.join('')}
    </table>
  `
}

const replaceSimpleAssetRow = (
  source: string,
  labelPattern: string,
  options: {
    amount?: string | number | null
    note?: string | null
    keep?: boolean
  }
) => {
  const rowPattern = new RegExp(
    `<tr bgcolor='#ffffff'[\\s\\S]*?<td class='border_t'[^>]*>[\\s\\S]*?<span class='text01'>&nbsp;${labelPattern}<\\/span>[\\s\\S]*?<\\/td>[\\s\\S]*?<\\/tr>`,
    'i'
  )

  if (options.keep === false) {
    return source.replace(rowPattern, '')
  }

  const amountText = options.amount === null || options.amount === undefined || String(options.amount).trim() === ''
    ? '&nbsp;'
    : `${escapeHtml(String(options.amount))}&nbsp;`
  const noteText = options.note && String(options.note).trim()
    ? escapeHtml(String(options.note).trim())
    : '&nbsp;'

  return source.replace(
    rowPattern,
    (row) => row
      .replace(
        /(<td class='border_tl' width='90'[^>]*>[\s\S]*?<span class='text01'>)[\s\S]*?(<\/span>[\s\S]*?<\/td>)/,
        `$1${amountText}$2`
      )
      .replace(
        /(<td class='border_tl' colspan='4'[^>]*>)[\s\S]*?(<\/td>)|(<td class='border_tl' colspan='5'[^>]*>)[\s\S]*?(<\/td>)|(<td class='border_tl' colspan=5[^>]*>)[\s\S]*?(<\/td>)/,
        (...args) => {
          if (args[1]) return `${args[1]}<span class='text01'>${noteText}</span>${args[2]}`
          if (args[3]) return `${args[3]}<span class='text01'>${noteText}</span>${args[4]}`
          return `${args[5]}<span class='text01'>${noteText}</span>${args[6]}`
        }
      )
  )
}

const replaceAssetRowBlock = (
  source: string,
  labelPattern: string,
  rowHtml: string,
  keep: boolean
) => {
  const rowPattern = new RegExp(
    `<tr bgcolor='#ffffff'[\\s\\S]*?<td class='border_t'[^>]*>[\\s\\S]*?<span class='text01'>&nbsp;${labelPattern}<\\/span>[\\s\\S]*?<\\/td>[\\s\\S]*?<\\/tr>`,
    'i'
  )

  return source.replace(rowPattern, keep ? rowHtml : '')
}

const buildAssetsDataSummary = (context: RecoveryDocumentContext) => {
  const pages = context.pages || {}
  const page7 = pages['7'] || {}
  const page10 = pages['10'] || {}
  const page11 = pages['11'] || {}
  const page11_1 = pages['11_1'] || {}
  const page11_2 = pages['11_2'] || {}
  const page11_3 = pages['11_3'] || {}
  const page12 = pages['12'] || {}
  const page12_1 = pages['12_1'] || {}

  const sections: string[] = []

  sections.push(buildAssetSummarySection('부동산 및 거주 관련 자산', [
    ['신청인 본인 명의 부동산', page7.hasApplicantProperty ? `아파트 ${page7.apartmentCount || 0}건 / 주택 ${page7.houseCount || 0}건 / 토지 ${page7.landCount || 0}건` : '없음'],
    ['배우자 명의 부동산', page7.hasSpouseProperty ? `아파트 ${page7.spouseApartmentCount || 0}건 / 주택 ${page7.spouseHouseCount || 0}건 / 토지 ${page7.spouseLandCount || 0}건` : '없음'],
    ['임차보증금', page12.formData?.hasLease && page12.leases?.length
      ? page12.leases.map((lease: any) => `${lease.address || '주소 미입력'} / 보증금 ${lease.depositAmount || '0'} / 월세 ${lease.monthlyRent || '0'}`).join(' | ')
      : '없음']
  ]))

  sections.push(buildAssetSummarySection('차량 및 현금성 자산', [
    ['차량 보유', page10.formData?.hasVehicle && page10.vehicles?.length
      ? page10.vehicles.map((vehicle: any) => formatVehicleEntryText(vehicle)).join(' | ')
      : '없음'],
    ['배우자 명의 차량', page10.formData?.hasSpouseVehicle && page10.spouseVehicles?.length
      ? page10.spouseVehicles.map((vehicle: any) => [
          vehicle.type || '차량',
          vehicle.price ? `시가 ${vehicle.price}원` : '',
          vehicle.securedDebtAmount ? `피담보채무 ${vehicle.securedDebtAmount}원` : ''
        ].filter(Boolean).join(' / ')).join(' | ')
      : '없음'],
    ['현금 보유', page11.formData?.hasCash ? `${page11.formData?.cashAmount || '0'}원` : '없음'],
    ['예금/적금', page11.formData?.hasAccount && page11.accounts?.length
      ? page11.accounts.map((account: any) => [
          account.bankName || '은행',
          account.accountNumber ? `계좌 ${account.accountNumber}` : (account.type || ''),
          `잔액 ${account.balance || '0'}원`,
          account.hasLoan === 'Y' ? `질권설정액 ${account.pledgeAmount || '0'}원` : ''
        ].filter(Boolean).join(' / ')).join(' | ')
      : '없음']
  ]))

  sections.push(buildAssetSummarySection('보험, 주식, 기타 재산', [
    ['보험 해약환급금', page11_1.formData?.hasInsurance && page11_1.insuranceItems?.length
      ? page11_1.insuranceItems.map((item: any) => {
          const policyLabel = item.policyNumber || item.productName || ''
          return `${item.companyName || '보험사'}${policyLabel ? ` (${policyLabel})` : ''} / 환급금 ${item.refundAmount || '0'}원`
        }).join(' | ')
      : '없음'],
    ['주식/출자지분', page11_2.formData?.hasStock && page11_2.stockItems?.length
      ? page11_2.stockItems.map((item: any) => `${item.companyName || '종목'} / ${item.shareCount || '0'}주 / 평가액 ${item.valuationAmount || item.sharePrice || '0'}원`).join(' | ')
      : '없음'],
    ['기타 동산', page11_3.formData?.hasOtherAsset && page11_3.assetItems?.length
      ? page11_3.assetItems.map((item: any) => `${item.description || '기타 자산'} / 예상가액 ${item.estimatedValue || '0'}원`).join(' | ')
      : '없음']
  ]))

  sections.push(buildAssetSummarySection('퇴직금 및 영업 설비', [
    ['퇴직금(예상)', page12_1.formData?.hasRetirementBenefit ? `${page12_1.formData?.retirementAmount || '0'}원 / 지급기관 ${page12_1.formData?.retirementInstitution || '-'}` : '없음'],
    ['영업 설비/비품', page12_1.formData?.hasBusinessFacility && page12_1.facilities?.length
      ? page12_1.facilities.map((item: any) => `${item.assetName || '설비'} / 예상가액 ${item.estimatedValue || '0'}원`).join(' | ')
      : '없음'],
    ['대여금/외상매출금', page12.formData?.hasLoanReceivable && page12.receivables?.length
      ? page12.receivables.map((item: any) => `${item.debtorName || '채무자'} / ${item.amount || '0'}원 / 변제기 ${item.dueDate || '-'}`).join(' | ')
      : '없음']
  ]))

  return `
    <br><br>
    <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr>
        <td><span class='text07_b'>입력 데이터 기준 재산 요약</span></td>
      </tr>
    </table>
    ${sections.join('')}
  `
}

function buildAssetsReferenceDocumentMarkup (
  context: RecoveryDocumentContext,
  model: ReturnType<typeof buildAssetDocumentModel>,
  tableHtml: string
) {
  const baseHtml = trimAssetAnnexSections(
    scrubReferenceSeeds(extractBodyInnerHtml(recoveryAssetsBaseHtml), context, 'assets')
  )
  const mergedHtml = replaceAssetMainTableMarkup(baseHtml, tableHtml)

  return withAssetLayoutVersionMarker(
    normalizePageBreakMarkup([
      mergedHtml,
      buildAssetsInsuranceAppendixSection(model),
      buildAssetsVehicleAppendixSection(model),
      buildAssetsLeaseAppendixSection(model),
      buildAssetsExemptPropertySection(context, model)
    ].join(''))
  )
}

const buildAssetsReferenceBody = (context: RecoveryDocumentContext) => {
  const model = buildAssetDocumentModel(context.pages || {})
  return buildAssetsReferenceDocumentMarkup(context, model, renderAssetTableHtml(model))
}

const assetReferencePageBreakHtml = PAGE_BREAK_MARKER_HTML
const ASSET_INSURANCE_APPENDIX_ROW_COUNT = 15
const ASSET_VEHICLE_APPENDIX_ROW_COUNT = 10
const ASSET_LEASE_APPENDIX_ENTRY_COUNT = 6
const assetHtmlOrBlank = (value: unknown) => {
  const text = String(value ?? '').trim()
  return text ? escapeHtml(text) : '&nbsp;'
}

const chunkAssetAppendixRows = <T>(rows: T[], pageSize: number): Array<Array<T | null>> => {
  if (pageSize <= 0) return [padAssetRows(rows, 0)]

  const source = Array.isArray(rows) ? rows : []
  const pageCount = Math.max(1, Math.ceil(source.length / pageSize))

  return Array.from({ length: pageCount }, (_, pageIndex) => {
    const start = pageIndex * pageSize
    const chunk = source.slice(start, start + pageSize)
    return padAssetRows(chunk, pageSize)
  })
}

const replaceAssetMainTableMarkup = (source: string, tableHtml: string) => {
  if (source.includes('<!-- ASSET_MAIN_TABLE -->')) {
    return source.replace('<!-- ASSET_MAIN_TABLE -->', tableHtml)
  }

  return source.replace(
    /<table style='border-collapse:collapse' border='1' bordercolor='#000000' cellpadding='0' cellspacing='0' width='100%' align='center' class='docxline'>[\s\S]*?<\/table>\s*<p style='page-break-before:always'>/,
    `${tableHtml}${PAGE_BREAK_MARKER_HTML}`
  )
}

const buildAssetsInsuranceAppendixSection = (model: ReturnType<typeof buildAssetDocumentModel>) => {
  const moneyCell = (value: number) => `${Math.max(0, Number(value) || 0).toLocaleString('ko-KR')}&nbsp;`
  const insurancePages = chunkAssetAppendixRows(model.insurance, ASSET_INSURANCE_APPENDIX_ROW_COUNT)

  return insurancePages.map((insuranceRows) => {
    const rows = insuranceRows.map((item, index) => `
        <tr bgcolor='#ffffff'>
            <td width='95' style='height:51.5px;'>
                <p align='center'><span class='text01'>${index === 0 || item ? '보&nbsp;&nbsp;험' : '&nbsp;'}</span></p>
            </td>
            <td width='90' style='height:51.5px;' align='right'><span class='text01'>${item ? moneyCell(item.refund) : '&nbsp;'}</span></td>
            <td width='49' style='height:51.5px;' align='center'><span class='text01'>${item ? assetHtmlOrBlank(item.seizure) : '&nbsp;'}</span></td>
            <td width='231' style='height:51.5px; padding-left:3px;'><span class='text01'>${item ? assetHtmlOrBlank(item.company) : '&nbsp;'}</span></td>
            <td width='150' style='height:51.5px; padding-left:3px;'><span class='text01'>${item ? assetHtmlOrBlank(item.policy) : '&nbsp;'}</span></td>
            <td width='100' style='height:51.5px;' align='right'><span class='text01'>${item ? moneyCell(item.refund) : '&nbsp;'}</span></td>
        </tr>
    `).join('')

    return `
      ${assetReferencePageBreakHtml}
      ${ASSET_PRINT_SECTION_OPEN}<br><br><br><span class='text01' style='span-weight:bold'>별 지</span><br>
      <table cellpadding='0' cellspacing='0' width='100%' align='center'>
          <tr>
              <td style='span-weight:bold' width='100'><span class='text01'>&nbsp;</span></td>
              <td style='span-weight:bold' align='center'><span class='text07' style='line-height:30px;'>보&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; 험</span></td>
              <td align='right' width='100'><span class='text01'>(단위: 원)</span></td>
          </tr>
          <tr>
              <td colspan='3' style='height:29.5px;'></td>
          </tr>
      </table>
      <table style='border-collapse:collapse' border='1' bordercolor='#000000' cellpadding='0' cellspacing='0' width='100%' align='center' class='docxline'>
          <tr bgcolor='#ffffff' style='height:49.5px;'>
              <td width='95' style='span-weight:bold'><p align='center'><span class='text01'>명&nbsp;&nbsp;칭</span></p></td>
              <td width='90' style='span-weight:bold'><p align='center'><span class='text01'>금액 (시가)<br></span></p></td>
              <td width='50' style='span-weight:bold'><p align='center'><span class='text01'>압류등<br>유ㆍ무</span></p></td>
              <td width='224' style='span-weight:bold'><p align='center'><span class='text01'>보 험 회 사 명</span></p></td>
              <td width='154' style='span-weight:bold'><p align='center'><span class='text01'>증 권 번 호</span></p></td>
              <td width='102' style='span-weight:bold'><p align='center'><span class='text01'>해약시반환금</span></p></td>
          </tr>
          ${rows}
      </table>
      ${ASSET_PRINT_SECTION_CLOSE}
    `
  }).join('')
}

const buildAssetsVehicleAppendixSection = (model: ReturnType<typeof buildAssetDocumentModel>) => {
  const moneyCell = (value: number) => `${Math.max(0, Number(value) || 0).toLocaleString('ko-KR')}&nbsp;`
  const allVehicles = [
    ...model.vehicles.map((item) => ({ ...item, ownerLabel: '' })),
    ...model.spouseVehicles.map((item) => ({ ...item, ownerLabel: '배우자 명의 ' }))
  ]

  if (!allVehicles.length) {
    return ''
  }

  const formatVehicleLine = (item: typeof allVehicles[number]) => {
    const typeLabel = String(item.type || '자동차').trim() || '자동차'
    const title = item.ownerLabel ? `[${item.ownerLabel}${typeLabel}]` : `[${typeLabel}]`
    const firstLine = [
      escapeHtml(title),
      escapeHtml(String(item.name || '').trim()),
      escapeHtml(String(item.number || '').trim()),
      String(item.year || '').trim() ? escapeHtml(`${String(item.year).trim()}연식`) : '',
      item.price > 0 ? `시가 ${item.price.toLocaleString('ko-KR')}` : ''
    ].filter(Boolean).join('&nbsp;&nbsp;')

    const secondLine = item.securedDebtAmount > 0
      ? `피담보채무 ${item.securedDebtAmount.toLocaleString('ko-KR')}`
      : ''

    if (firstLine || secondLine) {
      return `&nbsp;&nbsp;${firstLine || assetHtmlOrBlank(item.label)}${secondLine ? `<br>&nbsp;&nbsp;${secondLine}` : ''}`
    }

    return assetHtmlOrBlank(item.label)
  }

  const vehiclePages = chunkAssetAppendixRows(allVehicles, ASSET_VEHICLE_APPENDIX_ROW_COUNT)

  return vehiclePages.map((vehicleRows) => {
    const rowSpan = Math.max(vehicleRows.length, 1)
    const rows = vehicleRows.map((item, index) => `
          <tr bgcolor='#ffffff' style='height:${item ? '82.5px' : '59px'};'>
              ${index === 0 ? `<td rowspan='${rowSpan}' width='95'><p align='left'><span class='text01'>&nbsp;자동차<br>&nbsp;선&nbsp;박<br>&nbsp;오토바이</span><div align='center'><span class='printhidden'></span></div></p></td>` : ''}
              <td width='90' align='right' style='height:${item ? '82.5px' : '59px'};'><span class='text01'>${item ? moneyCell(item.price) : '&nbsp;'}</span></td>
              <td width='49' align='center' style='height:${item ? '82.5px' : '59px'};'><span class='text01'>${item ? '무' : '&nbsp;'}</span></td>
              <td width='481' style='height:${item ? '82.5px' : '59px'};'><span class='text01'>${item ? formatVehicleLine(item) : '&nbsp;'}</span></td>
          </tr>
    `).join('')

    return `
      ${assetReferencePageBreakHtml}
      ${ASSET_PRINT_SECTION_OPEN}<br><br><br>
      <span class='text01' style='span-weight:bold'>별 지</span><br><br>
      <table cellpadding='0' cellspacing='0' width='100%' align='center'>
          <tr>
              <td width='100'></td>
              <td style='span-weight:bold' align='center'><span class='text07' style='line-height:30px;'>자동차, 선박, 오토바이</span></td>
              <td width='100' align='right'><span class='text01'>(단위: 원)</span></td>
          </tr>
          <tr>
              <td colspan='3' style='height:29.5px;'></td>
          </tr>
      </table>
      <table bordercolor='#000000' cellpadding='0' cellspacing='0' border='1' width='100%' align='center' style='border-collapse:collapse ; border-top:0' class='docxline'>
          <tr bgcolor='#ffffff' style='height:49.5px;'>
              <td width='95' style='span-weight:bold'><p align='center'><span class='text01'>명&nbsp;&nbsp;칭</span></p></td>
              <td width='90' style='span-weight:bold'><p align='center'><span class='text01'>금액 (시가)</span></p></td>
              <td width='48' style='span-weight:bold;'><p align='center'><span class='text01'>압류등<br>유ㆍ무</span></p></td>
              <td width='482' style='span-weight:bold;'><p align='center'><span class='text01'>비&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;고</span></p></td>
          </tr>
          ${rows}
      </table>
      ${ASSET_PRINT_SECTION_CLOSE}
    `
  }).join('')
}

const buildAssetsLeaseAppendixSection = (model: ReturnType<typeof buildAssetDocumentModel>) => {
  const moneyCell = (value: number) => `${Math.max(0, Number(value) || 0).toLocaleString('ko-KR')}&nbsp;`
  const detailText = (lease: typeof model.leases[number] | null) =>
    lease
      ? [
          lease.deposit ? `보증금 ${lease.deposit.toLocaleString('ko-KR')}` : '',
          lease.monthlyRent ? `월세 ${lease.monthlyRent.toLocaleString('ko-KR')}` : ''
        ].filter(Boolean).join(', ')
      : ''

  const leasePages = chunkAssetAppendixRows(model.leases, ASSET_LEASE_APPENDIX_ENTRY_COUNT)

  return leasePages.map((leaseRows) => {
    const rows = leaseRows.map((lease, index) => `
            <tr bgcolor='#ffffff'>
                <td class='border_t' rowspan='3' width='95' align='center'><span class='text01'>${index === 0 || lease ? '임차보증금' : '&nbsp;'}</span></td>
                <td rowspan='3' width='90' style='height:43.5px;'>
                    <p align='right'><span class='text01'>${lease ? moneyCell(lease.recognizedValue) : '&nbsp;'}</span></p>
                </td>
                <td rowspan='3' width='49' style='height:43.5px;'>
                    <p align='center'><span class='text01'>${lease ? assetHtmlOrBlank(lease.seizure) : '&nbsp;'}</span></p>
                </td>
                <td width='113' style='height:43.5px;'>
                    <p align='left' style='padding:5px;'><span class='text01'>임차/전세물건</span></p>
                </td>
                <td width='387' colspan='4' style='word-break:break-all; height:43.5px;'>
                    <p align='left'><span class='text01'>${lease ? assetHtmlOrBlank(lease.address) : '&nbsp;'}</span></p>
                </td>
            </tr>
            <tr bgcolor='#ffffff' style='height:43.5px;'>
                <td width='113' style='height:43.5px;'>
                    <p align='left' style='padding:5px;'><span class='text01'>보증금 및 월세</span></p>
                </td>
                <td colspan='4' width='387' style='word-break:break-all; height:43.5px;'><span class='text01'>${assetHtmlOrBlank(detailText(lease))}</span></td>
            </tr>
            <tr bgcolor='#ffffff' style='height:43.5px;'>
                <td width='113' style='height:43.5px;'>
                    <p align='left' style='padding:5px;'><span class='text01'>차이나는 사유</span></p>
                </td>
                <td colspan='4' width='387' style='word-break:break-all; height:43.5px;'>
                    <p align='left'><span class='text01'>${lease ? assetHtmlOrBlank(lease.reason) : '&nbsp;'}</span></p>
                </td>
            </tr>
    `).join('')

    return `
      ${assetReferencePageBreakHtml}
      ${ASSET_PRINT_SECTION_OPEN}<br><br><br>
      <span class='text01' style='span-weight:bold'>별 지</span><br>
      <table cellpadding='0' cellspacing='0' width='100%' align='center'>
          <tr>
              <td width='100'></td>
              <td style='span-weight:bold' align='center'><span class='text07' style='line-height:30px;'>임차보증금</span></td>
              <td width='100' align='right'><span class='text01'>(단위: 원)</span></td>
          </tr>
          <tr>
              <td colspan='3' style='height:29.5px;'></td>
          </tr>
      </table>
      <table style='border-collapse:collapse;' border='1' bordercolor='#000000' cellpadding='0' cellspacing='0' width='100%' align='center' class='docxline'>
          <tr bgcolor='#ffffff' style='height:43.5px;'>
              <td width='95' style='span-weight:bold'><p align='center'><span class='text01'>명&nbsp;&nbsp;칭</span></p></td>
              <td width='90' style='span-weight:bold'><p align='center'><span class='text01'>금액 (시가)<br></span></p></td>
              <td width='49' style='span-weight:bold'><p align='center'><span class='text01'>압류등<br>유ㆍ무</span></p></td>
              <td width='481' colspan='4' style='span-weight:bold'><p align='center'><span class='text01'>비&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;고</span></p></td>
          </tr>
          ${rows}
      </table>
      ${ASSET_PRINT_SECTION_CLOSE}
    `
  }).join('')
}

const buildAssetsExemptPropertySection = (
  context: RecoveryDocumentContext,
  model: ReturnType<typeof buildAssetDocumentModel>
) => {
  const basics = getContextBasics(context)
  const page1 = context.pages['1'] || {}
  const caseYear = String(page1?.caseYear || basics.filingDateParts?.year || '').trim()
  const caseNumberText = [caseYear, '개회', basics.caseNumber].filter(Boolean).join(' ')
  const courtDepartment = basics.courtName
    ? `${basics.courtName}${String(basics.courtName).includes('개인회생과') ? '' : ' 개인회생과'}`
    : ''

  const firstLease = model.leases[0]
  const leaseAddress = firstLease?.address || context.pages['2']?.rentalInfo?.address || ''
  const exemptLeaseAmount = resolveLeaseAssetAmount(firstLease)
  const insuranceCompanies = model.insurance.map((item) => item.company).filter(Boolean).join(', ')
  const insuranceTotal = model.insurance.reduce((sum, item) => sum + item.refund, 0)
  const bankNames = model.bankAccounts.map((item) => item.bank).filter(Boolean).join(', ')
  const bankTotal = model.bankAccounts.reduce((sum, item) => sum + item.balance, 0)
  const retirementAmount = Math.max(0, Number(model.retirementAmount) || 0)
  const retirementInstitution = String(model.retirementInstitution || '').trim()

  const rows = [
    retirementAmount > 0
      ? `
                <tr bgcolor='#ffffff'>
                    <td class='border_t' width='30' style='height:49.5px;'><p align='center'><span class='text01' style='line-height:23px;'>__ROW_NO__</span></p></td>
                    <td class='border_tl' width='122' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>&nbsp;압류할수 없는 퇴직금</span></p></td>
                    <td class='border_tl' width='130' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>${assetHtmlOrBlank(retirementInstitution)}</span></p></td>
                    <td class='border_tl' width='97' style='height:49.5px;'><p align='right'><span class='text01' style='line-height:23px;'>${retirementAmount.toLocaleString('ko-KR')}</span></p></td>
                    <td class='border_tl' width='160' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>&nbsp;민사집행법 제246조</span></p></td>
                    <td class='border_tl' width='105' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>&nbsp;예상퇴직금 확인서</span></p></td>
                </tr>
    `
      : '',
    exemptLeaseAmount > 0
      ? `
                <tr bgcolor='#ffffff'>
                    <td class='border_t' width='30' style='height:49.5px;'><p align='center'><span class='text01' style='line-height:23px;'>__ROW_NO__</span></p></td>
                    <td class='border_tl' width='122' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>&nbsp;압류할수 없는 소액 임차보증금</span></p></td>
                    <td class='border_tl' width='130' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>${assetHtmlOrBlank(leaseAddress)}</span></p></td>
                    <td class='border_tl' width='97' style='height:49.5px;'><p align='right'><span class='text01' style='line-height:23px;'>${exemptLeaseAmount.toLocaleString('ko-KR')}</span></p></td>
                    <td class='border_tl' width='160' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>민사집행법 제246조 제1항 6호</span></p></td>
                    <td class='border_tl' width='105' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>&nbsp;임대차계약서</span></p></td>
                </tr>
    `
      : '',
    insuranceTotal > 0
      ? `
                <tr bgcolor='#ffffff'>
                    <td class='border_t' width='30' style='height:49.5px;'><p align='center'><span class='text01' style='line-height:23px;'>__ROW_NO__</span></p></td>
                    <td class='border_tl' width='122' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>&nbsp;압류할수 없는 보험금</span></p></td>
                    <td class='border_tl' width='130' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>${assetHtmlOrBlank(insuranceCompanies)}</span></p></td>
                    <td class='border_tl' width='97' style='height:49.5px;'><p align='right'><span class='text01' style='line-height:23px;'>${insuranceTotal.toLocaleString('ko-KR')}</span></p></td>
                    <td class='border_tl' width='160' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>민사집행법 제246조 제1항 7호, 시행령 제6조 제1항</span></p></td>
                    <td class='border_tl' width='105' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>&nbsp;예상환급금확인서</span></p></td>
                </tr>
    `
      : '',
    bankTotal > 0
      ? `
                <tr bgcolor='#ffffff'>
                    <td class='border_t' width='30' style='height:49.5px;'><p align='center'><span class='text01' style='line-height:23px;'>__ROW_NO__</span></p></td>
                    <td class='border_tl' width='122' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>&nbsp;압류할수 없는 예금등</span></p></td>
                    <td class='border_tl' width='130' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>${assetHtmlOrBlank(bankNames)}</span></p></td>
                    <td class='border_tl' width='97' style='height:49.5px;'><p align='right'><span class='text01' style='line-height:23px;'>${bankTotal.toLocaleString('ko-KR')}</span></p></td>
                    <td class='border_tl' width='160' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>민사집행법 제246조 제1항 8호, 시행령 제7조</span></p></td>
                    <td class='border_tl' width='105' style='height:49.5px;'><p align='left'><span class='text01' style='line-height:23px;'>&nbsp;금융거래내역서</span></p></td>
                </tr>
    `
      : ''
  ]
    .filter(Boolean)
    .map((row, index) => row.replace('__ROW_NO__', `&nbsp;${index + 1}`))

  if (!rows.length) {
    return ''
  }

  return `
    ${assetReferencePageBreakHtml}
    ${ASSET_PRINT_SECTION_OPEN}<br><br><br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
        <tr>
            <td align='center'><span class='text11_b'>개인회생재단에 속하지 않는 재산목록 제출서</span></td>
        </tr>
    </table>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
        <tr bgcolor='#ffffff' style='height:49.5px;'>
            <td width='150'><p align='left'><span class='text02' style='line-height:22px;'><span class='text03'>사&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;건</span></span></p></td>
            <td><p align='left'><span class='text02' style='line-height:22px;'><span class='text03'>&nbsp;&nbsp;${escapeHtml(caseNumberText || '')}</span></span></p></td>
        </tr>
        <tr bgcolor='#ffffff' style='height:49.5px;'>
            <td width='150'><p align='left'><span class='text02' style='line-height:22px;'><span class='text03'>신 청 인(채무자)</span></span></p></td>
            <td><p align='left'><span class='text02' style='line-height:22px;'><span class='text03'>&nbsp;<u>${escapeHtml(basics.applicantName || '')}</u></span></span></p></td>
        </tr>
    </table>
    <br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
        <tr style='height:43.5px;'>
            <td align='left'><span class='text03' style='line-height:43px;'>신청인은 다음과 같이 개인회생재단에 속하지 않는 재산목록(채무자 회생 및 파산에 관한 법률 제580조 제3항, 제1항 제1호, 제383조 제1항 소정)을 제출합니다.</span></td>
        </tr>
    </table>
    <br><br>
    <table style='border-collapse:collapse' border='1' bordercolor='#000000' cellpadding='0' cellspacing='0' width='100%' align='center' class='docxline'>
        <tr bgcolor='#ffffff' style='height:59px;'>
            <td class='border_o' width='30' style='height:59px;'><p align='center'><span class='text02' style='line-height:32px;'>순번</span></p></td>
            <td class='border_l' width='122' style='height:59px;'><p align='center'><span class='text02' style='line-height:32px;'>재산의 대상과 명칭</span></p></td>
            <td class='border_l' width='130' style='height:59px;'><p align='center'><span class='text02' style='line-height:32px;'>소 재 지</span></p></td>
            <td class='border_l' width='97' style='height:59px;'><p align='center'><span class='text02' style='line-height:32px;'>추 정 가 액</span></p></td>
            <td class='border_l' width='160' style='height:59px;'><p align='center'><span class='text02' style='line-height:32px;'>압류금지 근거조문</span></p></td>
            <td class='border_l' width='105' style='height:59px;'><p align='center'><span class='text02' style='line-height:32px;'>소 명 자 료</span></p></td>
        </tr>
        ${rows.join('')}
    </table>
    <br><br><br><br>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
        <tr style='height:43.5px;'>
            <td><span class='text03'>
                    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${formatHtmlDottedDateFromParts(basics.filingDateParts?.year, basics.filingDateParts?.month, basics.filingDateParts?.day)}<br><br>
                    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;신&nbsp;&nbsp;청&nbsp;&nbsp;인&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${escapeHtml(basics.applicantName || '')}<br>
                    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;대&nbsp;&nbsp;리&nbsp;&nbsp;인 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${escapeHtml(basics.lawyerName || '')} &nbsp;&nbsp;&nbsp;&nbsp;(인)
                    <br></span></td>
        </tr>
    </table>
    <br><br><br><br>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
        <tr style='height:43.5px;'>
            <td><span class='text06_b' style='line-height:25px;'>${escapeHtml(courtDepartment || '')}&nbsp;&nbsp;&nbsp;&nbsp;귀&nbsp;중</span></td>
        </tr>
    </table>
    ${ASSET_PRINT_SECTION_CLOSE}
  `
}

const buildAssetsReferenceBodyAsync = async (context: RecoveryDocumentContext) => {
  const model = buildAssetDocumentModel(context.pages || {})
  return buildAssetsReferenceDocumentMarkup(context, model, renderAssetTableHtml(model))
}

const incomeExpenseMedianIncomeTable: Record<number, number> = {
  1: 2564284,
  2: 4199292,
  3: 5359037,
  4: 6509751,
  5: 7580469
}

const getIncomeExpenseMedianIncome = (householdSize: number) => {
  const normalized = Math.min(Math.max(Math.round(householdSize) || 1, 1), 5)
  return incomeExpenseMedianIncomeTable[normalized] || incomeExpenseMedianIncomeTable[1]
}

const formatNumberText = (value: unknown) => parseNumeric(value).toLocaleString('ko-KR')

const formatMoneyCell = (value: unknown) => `${formatNumberText(value)} 원`

type RepaymentPlanMetrics = {
  employment: Record<string, any>
  isSalary: boolean
  averageIncome: number
  householdSize: number
  medianIncome: number
  adjustedExpenseAmount: number
  availableIncome: number
  monthlyCommissionerFee: number
  actualAvailableIncome: number
  repaymentMonths: number
  firstDate: { year: string; month: string; day: string }
  endDate: { year: string; month: string; day: string }
  totalActualAvailableIncome: number
  generalClaimBaseTotal: number
  repaymentRate: number
}

const getRepaymentPlanMetrics = (
  context: RecoveryDocumentContext,
  basics = getContextBasics(context)
): RepaymentPlanMetrics => {
  const page2 = context.pages['2'] || {}
  const page3 = context.pages['3'] || {}
  const page4 = context.pages['4'] || {}
  const page5 = context.pages['5'] || {}
  const page16 = context.pages['16'] || {}
  const employment = getEmploymentDataFromContext(context)

  const enabledMonthColumns = getEnabledPage5MonthColumns(page5)
  const incomeRows = Array.isArray(page5?.incomeRows) ? page5.incomeRows : []
  const enabledMonthCount = enabledMonthColumns.length || 12
  const totalForRows = (rows: Array<Record<string, any>>) =>
    rows.reduce((sum, row) => {
      const amounts = row?.amounts || {}
      return sum + enabledMonthColumns.reduce((monthSum, column) => monthSum + parseNumeric(amounts[column.key]), 0)
    }, 0)

  const averageIncome = Math.round(totalForRows(incomeRows) / enabledMonthCount)
  const displayHouseholdSize = getDisplayHouseholdSize(page2?.dependentsCount)
  const householdSize = getMedianIncomeHouseholdSize(page2?.dependentsCount)
  const medianIncome = getIncomeExpenseMedianIncome(householdSize)
  const adjustmentRatio = Number(page3?.adjustmentRatio || 60)
  const adjustedExpenseAmount = Math.round(medianIncome * (adjustmentRatio / 100))
  const availableIncome = averageIncome - adjustedExpenseAmount
  const commissionerPercent = Number(employment.commissionerPercent || employment.commissionerRate || 0)
  const monthlyCommissionerFee = employment.hasExternalCommissioner
    ? Math.max(0, Math.round(Math.max(availableIncome, 0) * (commissionerPercent / 100)))
    : 0
  const actualAvailableIncome = Math.max(0, availableIncome - monthlyCommissionerFee)
  const repaymentMonths = parseNumeric(basics.repaymentMonths)
  const firstDate = basics.firstRepaymentDateParts
  const endDate = repaymentMonths
    ? addMonthsFromDateParts(firstDate.year, firstDate.month, firstDate.day, Math.max(repaymentMonths - 1, 0))
    : { year: '', month: '', day: '' }
  const totalActualAvailableIncome = actualAvailableIncome * repaymentMonths
  const employmentType = String(employment.employmentType || '').trim()
  const isSalary = employmentType !== '자영'
  const collectBaseEntries = collectCreditorEntries(context.pages)
  const generalClaimBaseTotal = collectBaseEntries.reduce((sum, entry) => {
    if (entry.rowType !== 'unsecured') return sum
    return sum + (page4?.repaymentPlanType === 'B' ? entry.fullClaimAmount : entry.principalAmount)
  }, 0)
  const repaymentRate = generalClaimBaseTotal > 0
    ? ((totalActualAvailableIncome / generalClaimBaseTotal) * 100)
    : 0

  return {
    employment,
    isSalary,
    averageIncome,
    displayHouseholdSize,
    householdSize,
    medianIncome,
    adjustedExpenseAmount,
    availableIncome,
    monthlyCommissionerFee,
    actualAvailableIncome,
    repaymentMonths,
    firstDate,
    endDate,
    totalActualAvailableIncome,
    generalClaimBaseTotal,
    repaymentRate
  }
}

const formatSummaryDateText = (parts: { year?: string; month?: string; day?: string }) => {
  if (!parts?.year || !parts?.month || !parts?.day) return '-'
  return `${parts.year}년 ${parts.month}월 ${parts.day}일`
}

const buildAllApplicationSummaryBody = (context: RecoveryDocumentContext) => {
  const basics = getContextBasics(context)
  const metrics = getRepaymentPlanMetrics(context, basics)
  const assetModel = buildAssetDocumentModel(context.pages)
  const unsecuredEntries = collectCreditorEntries(context.pages).filter((entry) => entry.rowType === 'unsecured')
  const principalDebtTotal = unsecuredEntries.reduce((sum, entry) => sum + entry.principalAmount, 0)
  const expectedForgivenessAmount = Math.max(0, metrics.generalClaimBaseTotal - metrics.totalActualAvailableIncome)
  const medianRatio = metrics.medianIncome > 0
    ? ((metrics.adjustedExpenseAmount / metrics.medianIncome) * 100)
    : 0
  const employmentLabel = metrics.isSalary ? '급여소득자' : '영업소득자'
  const repaymentBaseLabel = context.pages['4']?.repaymentPlanType === 'B' ? '원리금' : '원금'

  const summaryRows = [
    ['월평균수입', `${formatNumberText(metrics.averageIncome)} 원`],
    ['가구원수(본인포함)', `${metrics.displayHouseholdSize}인 가구`],
    ['기준중위소득 조정금액', `${formatNumberText(metrics.adjustedExpenseAmount)} 원`],
    ['기준중위소득 조정비율', `${medianRatio.toFixed(1)} %`],
    ['월가용소득', `${formatNumberText(Math.max(metrics.availableIncome, 0))} 원`],
    ['최초 입금일자', formatSummaryDateText(metrics.firstDate)],
    [
      '변제기간',
      `${formatSummaryDateText(metrics.firstDate)} ~ ${formatSummaryDateText(metrics.endDate)} (${metrics.repaymentMonths}개월)`
    ],
    ['총변제예정(유보)액', `${formatNumberText(metrics.totalActualAvailableIncome)} 원`],
    ['변제예정액의 현재가치', `${formatNumberText(metrics.totalActualAvailableIncome)} 원`],
    ['청산가치', `${formatNumberText(assetModel.totalAmount)} 원`],
    ['면제재산 신청금액', `${formatNumberText(assetModel.exemptAmount)} 원`],
    ['변제율', `${metrics.generalClaimBaseTotal > 0 ? `${metrics.repaymentRate.toFixed(2)}%` : '0.00%'} (${repaymentBaseLabel} 기준)`],
    ['탕감예상 채무금액', `${formatNumberText(expectedForgivenessAmount)} 원`],
    ['기타', principalDebtTotal > 0 ? `총 원금 ${formatNumberText(principalDebtTotal)} 원` : '-']
  ]

  return `
    <br><br><br><br>
    <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr>
        <td align='center'><span class='text10_b'>[ 변제계획안 요약 ]</span></td>
      </tr>
      <tr><td style='height:22px;'></td></tr>
      <tr>
        <td align='center'><span class='text04_b'>${escapeHtml(basics.applicantName)} ( ${employmentLabel} )</span></td>
      </tr>
    </table>
    <br><br>
    <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center'>
      ${summaryRows.map(([label, value]) => `
        <tr style='height:36px;'>
          <td width='320' style='padding-left:32px;'><span class='text03'>○ ${escapeHtml(label)}</span></td>
          <td align='right' style='padding-right:32px;'><span class='text03'>${escapeHtml(value)}</span></td>
        </tr>
      `).join('')}
    </table>
  `
}

const buildAllApplicationCoverBody = (context: RecoveryDocumentContext) => {
  const basics = getContextBasics(context)
  const page2 = context.pages['2'] || {}
  const courtName = String(page2?.court || basics.courtName || '').trim()
  const courtDepartment = courtName
    ? `${courtName}${courtName.includes('개인회생과') ? '' : ' 개인회생과'}`
    : ''
  const representativeLabel = String(basics.lawyerName || '').trim() || '&nbsp;'
  const coverFeeTextStyle = 'font-size:10pt; line-height:1.15;'

  return `
    <div style='padding:8px 4px 0;'>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%' style='border-collapse:collapse; border:1px solid #000;'>
      <tr>
        <td style='padding:10px 12px 14px;'>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%' style='border-collapse:collapse;'>
            <tr>
              <td width='31%' style='border-top:3px double #000; height:8px;'></td>
              <td width='6%' align='center' style='font-size:18px; line-height:1;'>○</td>
              <td width='26%' style='border-top:1px solid #000;'></td>
              <td width='6%' align='center' style='font-size:18px; line-height:1;'>○</td>
              <td width='31%' style='border-top:3px double #000; height:8px;'></td>
            </tr>
          </table>
          <table border='0' cellpadding='0' cellspacing='0' width='100%' style='border-collapse:collapse; margin-top:6px;'>
            <tr>
              <td align='left'>
                <table border='0' cellpadding='0' cellspacing='0' style='border-collapse:collapse;'>
                  <tr>
                    <td style='border:1px solid #000; padding:6px 12px;'><span class='text01'>금지명령 포함</span></td>
                  </tr>
                </table>
              </td>
              <td width='96'></td>
            </tr>
          </table>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%' style='border-collapse:collapse; margin-top:56px;'>
            <tr>
              <td align='center'><span class='text11_b'>개인회생절차 개시신청서</span></td>
            </tr>
          </table>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%' style='border-collapse:collapse; margin-top:82px;'>
            <tr>
              <td width='100%' valign='top'>
                <table border='0' cellpadding='0' cellspacing='0' width='100%' style='border-collapse:collapse;'>
                  <tr height='34'>
                    <td align='left'><span class='text04'>신청인 (채무자)&nbsp;&nbsp;&nbsp;${escapeHtml(basics.applicantName || '')}</span></td>
                  </tr>
                  <tr height='34'>
                    <td align='left'><span class='text04'>대&nbsp;&nbsp;리&nbsp;&nbsp;인&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${escapeHtml(representativeLabel)}</span></td>
                  </tr>
                </table>
              </td>
            </tr>
          </table>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%' style='border-collapse:collapse; margin-top:34px;'>
            <tr>
              <td width='258'>&nbsp;</td>
              <td width='242' valign='top'>
                <table border='0' cellpadding='0' cellspacing='0' width='242' style='border-collapse:collapse;'>
                  <tr height='40'>
                    <td width='114' style='border:1px solid #000; padding:0 8px;'><span class='text03'>사&nbsp;&nbsp;건&nbsp;&nbsp;번&nbsp;&nbsp;호</span></td>
                    <td style='border:1px solid #000;'>&nbsp;</td>
                  </tr>
                  <tr height='40'>
                    <td style='border:1px solid #000; padding:0 8px;'><span class='text03'>해당순번번호</span></td>
                    <td style='border:1px solid #000;'>&nbsp;</td>
                  </tr>
                  <tr height='40'>
                    <td style='border:1px solid #000; padding:0 8px;'><span class='text03'>재&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;판&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;부</span></td>
                    <td style='border:1px solid #000; padding:0 8px;'>
                      <table border='0' cellpadding='0' cellspacing='0' width='100%' style='border-collapse:collapse;'>
                        <tr>
                          <td align='left'><span class='text03'>제</span></td>
                          <td align='right'><span class='text03'>부(단독)</span></td>
                        </tr>
                      </table>
                    </td>
                  </tr>
                  <tr height='40'>
                    <td style='border:1px solid #000; padding:0 8px;'><span class='text03'>주&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;심</span></td>
                    <td style='border:1px solid #000;'>&nbsp;</td>
                  </tr>
                </table>
              </td>
              <td>&nbsp;</td>
            </tr>
          </table>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%' style='border-collapse:collapse; margin-top:34px;'>
            <tr>
              <td width='198' valign='top'>
                <table border='0' cellpadding='0' cellspacing='0' width='100%' style='border-collapse:collapse;'>
                  <tr height='28'>
                    <td align='right'><span class='text03' style='${coverFeeTextStyle}'>인지대</span></td>
                    <td width='34' align='center'><span class='text03' style='${coverFeeTextStyle}'>회생</span></td>
                    <td width='74' align='right'><span class='text03' style='${coverFeeTextStyle}'>30,000원</span></td>
                  </tr>
                  <tr height='28'>
                    <td></td>
                    <td align='center'><span class='text03' style='${coverFeeTextStyle}'>금지</span></td>
                    <td align='right'><span class='text03' style='${coverFeeTextStyle}'>2,000원</span></td>
                  </tr>
                  <tr height='28'>
                    <td align='right'><span class='text03' style='${coverFeeTextStyle}'>송달료</span></td>
                    <td align='center'><span class='text03' style='${coverFeeTextStyle}'>회생</span></td>
                    <td align='right'><span class='text03' style='${coverFeeTextStyle}'>407,000원</span></td>
                  </tr>
                  <tr height='28'>
                    <td></td>
                    <td align='center'><span class='text03' style='${coverFeeTextStyle}'>금지</span></td>
                    <td align='right'><span class='text03' style='${coverFeeTextStyle}'>49,500원</span></td>
                  </tr>
                </table>
              </td>
              <td width='52'></td>
              <td width='276' valign='top'>
                <table border='0' cellpadding='0' cellspacing='0' width='276' style='border-collapse:collapse;'>
                  <tr height='40'>
                    <td style='border:1px solid #000;' colspan='3' align='center'><span class='text03'>최초면담기일통지</span></td>
                    <td style='border:1px solid #000;' width='78' align='center'><span class='text03'>영&nbsp;&nbsp;수&nbsp;&nbsp;인</span></td>
                  </tr>
                  <tr height='40'>
                    <td style='border:1px solid #000; padding-left:8px;' width='60'><span class='text03'>20</span></td>
                    <td style='border:1px solid #000;' align='center'><span class='text03'>.</span></td>
                    <td style='border:1px solid #000;' align='center'><span class='text03'>.&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;</span></td>
                    <td style='border:1px solid #000;' rowspan='2'>&nbsp;</td>
                  </tr>
                  <tr height='40'>
                    <td style='border:1px solid #000;' colspan='3' align='center'><span class='text03'>심문실</span></td>
                  </tr>
                </table>
                <table border='0' cellpadding='0' cellspacing='0' width='80' style='border-collapse:collapse; margin-top:24px;'>
                  <tr height='34'>
                    <td style='border:1px solid #000;' align='center'><span class='text03'>당일면담<br>희망여부</span></td>
                  </tr>
                  <tr height='62'>
                    <td style='border:1px solid #000;'>&nbsp;</td>
                  </tr>
                </table>
              </td>
            </tr>
          </table>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%' style='border-collapse:collapse; margin-top:104px;'>
            <tr>
              <td align='left'><span class='text08_b'>${escapeHtml(courtDepartment || '')}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;귀&nbsp;중</span></td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    </div>
  `
}

const buildAllApplicationOpeningBody = (context: RecoveryDocumentContext) => {
  const basics = getContextBasics(context)
  const page1 = context.pages['1'] || {}
  const page2 = context.pages['2'] || {}
  const registeredAddress = [
    page1?.registeredZipCode ? `[${page1.registeredZipCode}]` : '',
    page1?.registeredAddress || '',
    page1?.registeredAddressDetail || ''
  ].filter(Boolean).join(' ').trim()
  const currentAddress = [
    page1?.currentZipCode ? `[${page1.currentZipCode}]` : '',
    page1?.currentAddress || '',
    page1?.currentAddressDetail || ''
  ].filter(Boolean).join(' ').trim()
  const applicantPhone = basics.mobileNumber || basics.phoneNumber || ''
  const courtName = String(page2?.court || basics.courtName || '').trim()
  const moveInDate = page2?.moveInDate
    ? `${page2.moveInDate.year || ''}.${String(page2.moveInDate.month || '').padStart(2, '0')}.${String(page2.moveInDate.day || '').padStart(2, '0')}`
    : ''
  const householdLabel = getDisplayHouseholdSize(page2?.dependentsCount)
  const houseTypeLabelMap: Record<string, string> = {
    A: '자가',
    B: '사택/기숙사',
    C: '본인 명의 임차',
    D: '배우자/친족 소유',
    E: '타인 소유',
    F: '타인 명의 임차',
    G: '배우자 명의 임차',
    X: '기타'
  }
  const houseTypeLabel = houseTypeLabelMap[String(page2?.houseType || '').trim()] || '-'

  return `
    <br><br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr>
        <td align='center'><span class='text07_b'>개인회생절차 개시신청서</span></td>
      </tr>
    </table>
    <table bgcolor='#000000' cellpadding='0' cellspacing='0' width='100%' align='center' class='docxline' style='margin-top:18px;'>
      <tr bgcolor='#ffffff' height='40'>
        <td rowspan='6' width='62' class='border_o'><p align='center'><span class='text02'>신청인</span></p></td>
        <td width='114' class='border_l'><p align='center'><span class='text02'>성명</span></p></td>
        <td width='223' class='border_l'><p align='left'><span class='text02'>&nbsp;${escapeHtml(basics.applicantName || '')}</span></p></td>
        <td width='119' class='border_l'><p align='center'><span class='text02'>주민등록번호</span></p></td>
        <td width='218' class='border_l'><p align='left'><span class='text02'>&nbsp;${escapeHtml(basics.residentNumber || '')}</span></p></td>
      </tr>
      <tr bgcolor='#ffffff' height='40'>
        <td width='114' class='border_tl'><p align='center'><span class='text02'>주민등록상<br>주소</span></p></td>
        <td colspan='3' width='560' class='border_tl' style='padding-left:5px;'><span class='text01'>${escapeHtml(registeredAddress || '')}</span></td>
      </tr>
      <tr bgcolor='#ffffff' height='40'>
        <td class='border_tl'><p align='center'><span class='text02'>현재 주소</span></p></td>
        <td colspan='3' class='border_tl' style='padding-left:5px;'><span class='text01'>${escapeHtml(currentAddress || registeredAddress || '')}</span></td>
      </tr>
      <tr bgcolor='#ffffff' height='40'>
        <td class='border_tl'><p align='center'><span class='text02'>휴대전화</span></p></td>
        <td class='border_tl'><p align='left'><span class='text02'>&nbsp;${escapeHtml(applicantPhone || '')}</span></p></td>
        <td class='border_tl'><p align='center'><span class='text02'>관할법원</span></p></td>
        <td class='border_tl'><p align='left'><span class='text02'>&nbsp;${escapeHtml(courtName || '')}</span></p></td>
      </tr>
      <tr bgcolor='#ffffff' height='40'>
        <td class='border_tl'><p align='center'><span class='text02'>거주형태</span></p></td>
        <td class='border_tl'><p align='left'><span class='text02'>&nbsp;${escapeHtml(houseTypeLabel)}</span></p></td>
        <td class='border_tl'><p align='center'><span class='text02'>거주시작일</span></p></td>
        <td class='border_tl'><p align='left'><span class='text02'>&nbsp;${escapeHtml(moveInDate || '')}</span></p></td>
      </tr>
      <tr bgcolor='#ffffff' height='40'>
        <td class='border_tl'><p align='center'><span class='text02'>가구원수</span></p></td>
        <td class='border_tl'><p align='left'><span class='text02'>&nbsp;${escapeHtml(householdLabel)}인 가구</span></p></td>
        <td class='border_tl'><p align='center'><span class='text02'>대리인</span></p></td>
        <td class='border_tl'><p align='left'><span class='text02'>&nbsp;${escapeHtml(basics.lawyerName || '')}</span></p></td>
      </tr>
    </table>
  `
}

const buildConfirmationReferenceBody = (context: RecoveryDocumentContext) => {
  const basics = getContextBasics(context)
  const creditors = collectCreditorEntries(context.pages).filter((entry) => entry.rowType === 'unsecured')
  const totalPrincipal = creditors.reduce((sum, entry) => sum + entry.principalAmount, 0)
  const totalInterest = creditors.reduce((sum, entry) => sum + entry.interestAmount, 0)
  const totalAmount = totalPrincipal + totalInterest
  const applicantPhone = basics.mobileNumber || context.caseInfo?.applicant_phone || '-'
  const applicantAddress = basics.applicantAddress || context.caseInfo?.applicant_address || '-'
  const filingDateText = formatHtmlDottedDateFromParts(
    basics.filingDateParts.year,
    basics.filingDateParts.month,
    basics.filingDateParts.day
  )

  const creditorRows = (creditors.length ? creditors : [{
    creditorName: '',
    debtDate: '',
    principalAmount: 0,
    interestAmount: 0
  }]).map((entry: any, index: number) => `
    <tr style='height:25px;'>
      <td class='text01' width='30' style='font-size:13.5px;padding-left:5px;'>${creditors.length ? index + 1 : '&nbsp;'}</td>
      <td class='text01' style='font-size:13.5px' width='100'>${creditors.length ? escapeHtml(formatHumanDate(entry.debtDate).replace(/\.\s*$/, '')) : '&nbsp;'}</td>
      <td class='text01' style='padding-left:3px;padding-right:3px;'>${creditors.length ? escapeHtml(entry.creditorName) : '&nbsp;'}</td>
      <td class='text01' width='90' align='right'>${creditors.length ? `${formatNumberText(entry.principalAmount)}&nbsp;` : '&nbsp;'}</td>
      <td class='text01' width='95' align='right'>${creditors.length ? `${formatNumberText(entry.interestAmount)}&nbsp;` : '&nbsp;'}</td>
      <td class='text01' style='padding-left:3px;' width='100'>&nbsp;</td>
    </tr>
  `).join('')

  return `
    <br><br><br><br>
    <p align='center'><b class='text11_b'>확&nbsp;&nbsp;&nbsp;인&nbsp;&nbsp;&nbsp;서</b></p>
    <table cellpadding='3' cellspacing='0'>
      <tr>
        <td valign='top' rowspan='3' class='text02'>1.</td>
        <td valign='top' rowspan='3' class='text02'>확인자</td>
        <td width='10' rowspan='3'></td>
        <td class='text02'>성&nbsp;&nbsp;&nbsp;명: ${escapeHtml(basics.applicantName)}${basics.residentNumber ? ` ( ${escapeHtml(basics.residentNumber)} )` : ''}<br></td>
      </tr>
      <tr>
        <td valign='top' class='text02'>주&nbsp;&nbsp;&nbsp;소: ${escapeHtml(applicantAddress)}<br></td>
      </tr>
      <tr>
        <td valign='top' class='text02'>휴대폰: ${escapeHtml(applicantPhone)}<br></td>
      </tr>
      <tr>
        <td valign='top' class='text02'>2.</td>
        <td colspan='3' class='text02'>채무 내용</td>
      </tr>
    </table>
    <table cellpadding='0' cellspacing='0' width='100%'>
      <tr>
        <td style='padding-left:23px;'>
          <table cellpadding='3' cellspacing='0' width='100%' style='border-collapse:collapse' border='1' bordercolor='#000000' class='docxline'>
            <tr style='height:25px;'>
              <td class='text01_b' style='text-align:center' width='30'>순번</td>
              <td class='text01_b' style='text-align:center' width='100'>채무발생일자</td>
              <td class='text01_b' style='text-align:center'>채권자</td>
              <td class='text01_b' style='text-align:center' width='90'>채무원금</td>
              <td class='text01_b' style='text-align:center' width='80'>이자</td>
              <td class='text01_b' style='text-align:center' width='100'>비고</td>
            </tr>
            ${creditorRows}
            <tr>
              <td class='text01' colspan='3' align='center'>소 계</td>
              <td class='text01' width='90' style='text-align:right;padding-right:5px;'>${formatNumberText(totalPrincipal)}</td>
              <td class='text01' width='95' style='text-align:right;padding-right:5px;'>${formatNumberText(totalInterest)}</td>
              <td class='text01' width='90'></td>
            </tr>
            <tr>
              <td class='text01' colspan='3' align='center'>합 계</td>
              <td class='text01' style='text-align:right;padding-right:5px;' colspan='2'>${formatNumberText(totalAmount)}</td>
              <td width='100'></td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    <table cellpadding='0' cellspacing='0' width='100%' style='padding-top:10px;'>
      <tr>
        <td valign='top' class='text02' style='line-height:22px;'>3.</td>
        <td colspan='2' class='text02' style='line-height:22px;' valign='top'>귀 사무소에 개인회생 사건의뢰를 함에 있어서 다음사항을 확인합니다.</td>
      </tr>
      <tr>
        <td width='20' valign='top' class='text02' style='line-height:22px;'></td>
        <td class='text02' width='20' valign='top' style='line-height:22px;'>1)</td>
        <td class='text02' style='line-height:22px;text-align:left;' valign='top'>재산상태, 소득내용 등에 관하여 숨김없이 사실대로 진술하였습니다.</td>
      </tr>
      <tr>
        <td valign='top' class='text02' style='line-height:22px;'></td>
        <td valign='top' class='text02' style='line-height:22px;'>2)</td>
        <td class='text02' style='line-height:22px;text-align:left;' valign='top'>위 2항 기재의 채무내용 외 누락된 채무가 없습니다.</td>
      </tr>
      <tr>
        <td valign='top' class='text02' style='line-height:22px;'></td>
        <td valign='top' class='text02' style='line-height:22px;'>3)</td>
        <td class='text02' style='line-height:22px;text-align:left;' valign='top'>귀 사무소로부터 주채무, 보증채무의 누락시 면책받을 수 없다는 점, 재산(소득)은닉에 따른 벌칙, 허위진술로 인한 불이익 등에 관하여 고지를 받았습니다.</td>
      </tr>
      <tr>
        <td colspan='3' height='5'></td>
      </tr>
      <tr>
        <td valign='top' class='text02' style='line-height:22px;'>4.</td>
        <td class='text02' style='line-height:22px;text-align:left;' valign='top' colspan='2'>위 확인사항에 근거하여 채무누락, 허위진술 등으로 인한 불이익은 전적으로 본인이 감수하겠습니다.<br></td>
      </tr>
    </table>
    <br><br>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr style='height:43.5px;'>
        <td align='right'><span class='text03'>${filingDateText}</span></td>
      </tr>
      <tr style='height:43.5px;'>
        <td align='right'><span class='text03'>위 확인자 (사건 의뢰인) ${escapeHtml(basics.applicantName)} &nbsp;&nbsp;(인)</span></td>
      </tr>
      <tr style='height:43.5px;'>
        <td align='right'><span class='text03'>${escapeHtml(basics.lawyerName || '')} 귀 중</span></td>
      </tr>
    </table>
  `
}

const isConfirmationPlaceholderHtml = (html: string) => {
  const text = stripHtml(html)
  return (
    text.includes('현재 이 문서는 레퍼런스 템플릿 연결 작업을 진행 중입니다.') ||
    text.includes('입력된 데이터에 따라 본 문서 양식을 채워드립니다.')
  )
}

const htmlOrBlank = (value: unknown) => {
  const text = String(value ?? '').trim()
  return text ? escapeHtml(text) : '&nbsp;'
}

const INCOME_EXPENSE_COMPACT_TEXT01_STYLE = 'font-size:7pt; line-height:1.2;'
const isDocumentMonthColumnEnabled = (column?: { enabled?: boolean }) => column?.enabled !== false
const getDocumentMonthColumnNumericValue = (
  amounts: Record<string, any>,
  column: { key: string; enabled?: boolean }
) => (isDocumentMonthColumnEnabled(column) ? parseNumeric(amounts?.[column.key]) : 0)
const getDocumentMonthColumnDisplayText = (
  amounts: Record<string, any>,
  column: { key: string; enabled?: boolean }
) => {
  if (!isDocumentMonthColumnEnabled(column)) {
    return '0'
  }

  const value = parseNumeric(amounts?.[column.key])
  return value ? formatNumberText(value) : '0'
}

const buildIncomeExpenseDetailTable = (
  title: string,
  rows: Array<Record<string, any>>,
  emptyLabel: string,
  monthColumns: Array<{ key: string; year: string; month: string; enabled?: boolean }>
) => {
  const allMonthColumns = monthColumns.length ? monthColumns : buildDefaultPage5MonthColumns()
  const normalizedRows = rows.length
    ? [...rows]
    : [{ source: emptyLabel, amounts: {} }]

  while (normalizedRows.length < 10) {
    normalizedRows.push({ source: '', amounts: {} })
  }

  const renderRow = (row: Record<string, any>) => {
    const amounts = row?.amounts || {}
    const sum = allMonthColumns.reduce((acc, column) => acc + getDocumentMonthColumnNumericValue(amounts, column), 0)
    const sourceText = String(row?.source || emptyLabel || '').trim()
    const hasSource = sourceText.length > 0

    return `
      <tr style='height:36.5px;'>
        <td style='padding-left:5px;width:100px;'><span class='text01' style='${INCOME_EXPENSE_COMPACT_TEXT01_STYLE}'>${hasSource ? escapeHtml(sourceText) : '&nbsp;'}</span></td>
        ${allMonthColumns.map((column) => `<td align='right'><span class='text01' style='${INCOME_EXPENSE_COMPACT_TEXT01_STYLE}'>${hasSource ? getDocumentMonthColumnDisplayText(amounts, column) : '&nbsp;'}</span></td>`).join('')}
        <td align='right'><span class='text01' style='${INCOME_EXPENSE_COMPACT_TEXT01_STYLE}'>${hasSource ? (sum ? formatNumberText(sum) : '0') : '&nbsp;'}</span></td>
      </tr>
    `
  }

  return `
    <table width='100%' border='0' cellspacing='0' cellpadding='0' align='center'>
      <tr>
        <td align='center' colspan='15' class='text07_b'>${escapeHtml(title)}</td>
      </tr>
      <tr><td colspan='15' height='20' align='right'><span class='printhidden'></span></td></tr>
      <tr>
        <td>
          <table border='1' cellpadding='0' cellspacing='0' width='100%' style='border-collapse:collapse' bordercolor='#000000' class='docxline'>
            <tr style='height:36.5px;'>
              <td align='center' style='width:100px;'><span class='text02'>소득내용</span></td>
              ${allMonthColumns.map((column) => `<td align='center'><span class='text02'>${getPage5MonthLabel(column)}</span></td>`).join('')}
              <td align='center'><span class='text02'>합계</span></td>
            </tr>
            ${normalizedRows.map(renderRow).join('')}
          </table>
        </td>
      </tr>
    </table>
  `
}

const buildIncomeExpenseNetTable = (
  netByMonth: Record<string, number>,
  monthColumns: Array<{ key: string; year: string; month: string; enabled?: boolean }>
) => {
  const allMonthColumns = monthColumns.length ? monthColumns : buildDefaultPage5MonthColumns()
  const total = allMonthColumns.reduce((sum, column) => sum + (isDocumentMonthColumnEnabled(column) ? (netByMonth[column.key] || 0) : 0), 0)

  return `
    <table width='100%' border='0' cellspacing='0' cellpadding='0' align='center'>
      <tr>
        <td>
          <table border='1' cellpadding='0' cellspacing='0' width='100%' style='border-collapse:collapse' bordercolor='#000000' class='docxline'>
            <tr style='height:36.5px;'>
              <td align='center' style='width:100px;'><span class='text02'>소득내용</span></td>
              ${allMonthColumns.map((column) => `<td align='center'><span class='text02'>${getPage5MonthLabel(column)}</span></td>`).join('')}
              <td align='center'><span class='text02'>합계</span></td>
            </tr>
            <tr style='height:36.5px;'>
              <td style='padding-left:5px;width:100px;'><span class='text01'>실수령</span></td>
              ${allMonthColumns.map((column) => `<td align='right'><span class='text01'>${isDocumentMonthColumnEnabled(column) && netByMonth[column.key] ? formatNumberText(netByMonth[column.key]) : '0'}</span></td>`).join('')}
              <td align='right'><span class='text01'>${formatNumberText(total)}</span></td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
  `
}

const buildIncomeExpenseSummaryTable = (summary: Array<[string, string]>, rowSize = 2) => {
  const rows = Array.from({ length: Math.ceil(summary.length / rowSize) }, (_, index) =>
    summary.slice(index * rowSize, index * rowSize + rowSize)
  )
  const labelWidth = rowSize === 3 ? '12%' : '18%'
  const valueWidth = rowSize === 3 ? '21.3333%' : '32%'

  return `
    <table width='100%' border='0' cellspacing='0' cellpadding='0' align='center' style='margin-top:24px; margin-bottom:24px;'>
      <tr>
        <td>
          <table border='1' cellpadding='0' cellspacing='0' width='100%' style='border-collapse:collapse' bordercolor='#000000' class='docxline'>
            ${rows.map((row) => `
              <tr style='height:36.5px;'>
                ${row.map(([label, value], cellIndex) => `
                  <td align='center' style='width:${labelWidth};${cellIndex === 0 ? '' : ' border-left:1px solid #000;'}'><span class='text02'>${escapeHtml(label)}</span></td>
                  <td align='right' style='width:${valueWidth}; padding-right:8px;'><span class='text01'>${escapeHtml(value)}</span></td>
                `).join('')}
                ${row.length < rowSize ? `
                  <td align='center' style='width:${labelWidth};'><span class='text02'>&nbsp;</span></td>
                  <td align='right' style='width:${valueWidth}; padding-right:8px;'><span class='text01'>&nbsp;</span></td>
                ` : ''}
              </tr>
            `).join('')}
          </table>
        </td>
      </tr>
    </table>
  `
}

const buildIncomeExpenseReferenceBody = (context: RecoveryDocumentContext) => {
  const page2 = context.pages['2'] || {}
  const page3 = context.pages['3'] || {}
  const page5 = context.pages['5'] || {}
  const page16 = context.pages['16'] || {}
  const employment = getEmploymentDataFromContext(context)

  const allMonthColumns = getPage5MonthColumns(page5)
  const enabledMonthColumns = getEnabledPage5MonthColumns(page5)
  const incomeRows = Array.isArray(page5?.incomeRows) ? page5.incomeRows : []
  const deductionRows = Array.isArray(page5?.deductionRows) ? page5.deductionRows : []
  const enabledMonthCount = enabledMonthColumns.length || 12
  const totalForRows = (rows: Array<Record<string, any>>) =>
    rows.reduce((sum, row) => {
      const amounts = row?.amounts || {}
      return sum + enabledMonthColumns.reduce((monthSum, column) => monthSum + parseNumeric(amounts[column.key]), 0)
    }, 0)

  const salaryRows = incomeRows.filter((row: Record<string, any>) => String(row?.source || '').includes('급여'))
  const extraIncomeRows = incomeRows.filter((row: Record<string, any>) => !String(row?.source || '').includes('급여'))
  const averageSalaryIncome = salaryRows.length ? Math.round(totalForRows(salaryRows) / enabledMonthCount) : 0
  const averageExtraIncome = extraIncomeRows.length ? Math.round(totalForRows(extraIncomeRows) / enabledMonthCount) : 0
  const averageIncome = Math.round(totalForRows(incomeRows) / enabledMonthCount)
  const averageDeduction = Math.round(totalForRows(deductionRows) / enabledMonthCount)
  const averageNetIncome = Math.max(0, averageIncome - averageDeduction)
  const annualIncomeTotal = totalForRows(incomeRows)
  const annualDeductionTotal = totalForRows(deductionRows)
  const annualNetIncomeTotal = Math.max(0, annualIncomeTotal - annualDeductionTotal)
  const annualGrossEquivalent = averageIncome * 12
  const netByMonth = allMonthColumns.reduce<Record<string, number>>((acc, column) => {
    if (!isDocumentMonthColumnEnabled(column)) {
      acc[column.key] = 0
      return acc
    }

    const incomeTotal = incomeRows.reduce((sum: number, row: Record<string, any>) => sum + parseNumeric(row?.amounts?.[column.key]), 0)
    const deductionTotal = deductionRows.reduce((sum: number, row: Record<string, any>) => sum + parseNumeric(row?.amounts?.[column.key]), 0)
    acc[column.key] = Math.max(0, incomeTotal - deductionTotal)
    return acc
  }, {})

  const displayHouseholdSize = getDisplayHouseholdSize(page2?.dependentsCount)
  const householdSize = getMedianIncomeHouseholdSize(page2?.dependentsCount)
  const medianIncome = getIncomeExpenseMedianIncome(householdSize)
  const adjustmentRatio = Number(page3?.adjustmentRatio || 60)
  const adjustedExpenseAmount = Math.round(medianIncome * (adjustmentRatio / 100))
  const isOverSixty = adjustmentRatio > 60

  const additionalExpenses = page3?.additionalExpenses || {}
  const expenseReasonLines = [
    ...(Array.isArray(page3?.expenseReasons) ? page3.expenseReasons : []),
    String(page3?.supplementaryDescription || '').trim(),
    page3?.hasChildSupport && parseNumeric(page3?.childSupportAmount)
      ? `양육비: ${formatMoneyCell(page3.childSupportAmount)} / 상대방 ${escapeHtml(page3.exSpouseName || '-')}`
      : ''
  ].filter((item: unknown) => String(item || '').trim())

  const expenseRows = [
    { label: '식료품비', amount: additionalExpenses.food, reason: page3?.expenseReasons?.[0] || '' },
    { label: '광열수도비', amount: additionalExpenses.utilities, reason: page3?.expenseReasons?.[1] || '' },
    { label: '가구집기비', amount: additionalExpenses.furniture, reason: page3?.expenseReasons?.[2] || '' },
    { label: '피복신발비', amount: additionalExpenses.clothing, reason: page3?.expenseReasons?.[3] || '' },
    { label: '교양오락비', amount: additionalExpenses.culture, reason: page3?.expenseReasons?.[4] || '' },
    { label: '교통통신비', amount: additionalExpenses.transport, reason: page3?.expenseReasons?.[5] || '' },
    { label: '기타 비용', amount: additionalExpenses.other, reason: page3?.expenseReasons?.[6] || '' }
  ]
  const livingExpenseTotal = expenseRows.reduce((sum, item) => sum + parseNumeric(item.amount), 0)
  const housingExpense = parseNumeric(additionalExpenses.housing)
  const medicalExpense = parseNumeric(additionalExpenses.medical)
  const educationExpense = parseNumeric(additionalExpenses.education)
  const totalAdditionalExpense = livingExpenseTotal + housingExpense + medicalExpense + educationExpense

  const familyRows: Array<{
    relation: string
    name: string
    age: string
    cohabitation: string
    job: string
    income: string
    assets: string
    support: string
  }> = []

  const familyMembers = Array.isArray(page2?.familyMembers) ? page2.familyMembers : []
  if (familyMembers.length) {
    familyMembers.forEach((member: Record<string, any>) => {
      const cohabitationLabel = String(member.cohabitation || '').trim()
      const years = String(member.cohabitationYears || '').trim()
      const months = String(member.cohabitationMonths || '').trim()
      const cohabitationPeriod = [years ? `${years}년` : '', months ? `${months}월` : '']
        .filter(Boolean)
        .join(' ')

      familyRows.push({
        relation: String(member.relationship || '').trim(),
        name: String(member.name || '').trim(),
        age: String(member.age || '').trim(),
        cohabitation: [cohabitationLabel, cohabitationPeriod].filter(Boolean).join(' '),
        job: String(member.job || '').trim(),
        income: formatNumberText(member.monthlyIncome),
        assets: formatNumberText(member.totalAssets),
        support: String(member.supportStatus || '').trim()
      })
    })
  } else if (page3?.hasChildSupport && Array.isArray(page3?.childNames)) {
    page3.childNames
      .filter((name: unknown) => String(name || '').trim())
      .forEach((name: string) => {
        familyRows.push({
          relation: '자녀',
          name: String(name || '').trim(),
          age: '',
          cohabitation: page3?.exSpouseName ? '별거' : '',
          job: '',
          income: page3?.childSupportAmount ? formatNumberText(page3.childSupportAmount) : '0',
          assets: '0',
          support: '부양함'
        })
      })
  }

  while (familyRows.length < 7) {
    familyRows.push({
      relation: '',
      name: '',
      age: '',
      cohabitation: '',
      job: '',
      income: '',
      assets: '',
      support: ''
    })
  }

  const familyRowMarkup = familyRows
    .slice(0, 7)
    .map((item) => `
      <tr bgcolor='#ffffff' style='height:43.5px;'>
        <td class='border_t'><p align='center'><span class='text01'>${htmlOrBlank(item.relation)}</span></p></td>
        <td class='border_tl'><p align='center'><span class='text01'>${htmlOrBlank(item.name)}</span></p></td>
        <td class='border_tl'><p align='center'><span class='text01'>${htmlOrBlank(item.age)}</span></p></td>
        <td class='border_tl'><p align='center'><span class='text01'>${htmlOrBlank(item.cohabitation)}</span></p></td>
        <td class='border_tl'><p align='center'><span class='text01'>${htmlOrBlank(item.job)}</span></p></td>
        <td class='border_tl'><p align='right'><span class='text01'>${htmlOrBlank(item.income)}</span></p></td>
        <td class='border_tl'><p align='right'><span class='text01'>${htmlOrBlank(item.assets)}</span></p></td>
        <td class='border_tl'><p align='center'><span class='text01'>${htmlOrBlank(item.support)}</span></p></td>
      </tr>
    `)
    .join('')

  const expenseRowMarkup = expenseRows
    .map((item, index) => `
      <tr bgcolor='#ffffff' style='height:41.5px;'>
        ${index === 0 ? `<td class='text02' style='border-left:0 ; border-top:0 ; border-bottom:0' rowspan='8' align='center' width='35' nowrap>생<br><br>계<br><br>비</td>` : ''}
        <td class='text02' ${index === 0 ? "style='border-top:0'" : ''} width='126' nowrap>&nbsp;&nbsp;&nbsp;${escapeHtml(item.label)}</td>
        <td class='text02' ${index === 0 ? "style='border-top:0'" : ''} align='right' width='137' nowrap>${formatNumberText(item.amount)}</td>
        <td class='text01' ${index === 0 ? "style='border-top:0 ;border-right:0;padding-left:3px;'" : "style='border-right:0;padding-left:3px;'"} width='382'>${htmlOrBlank(item.reason)}</td>
      </tr>
    `)
    .join('')

  return `
    <br><br><br>
    <p align='center'>
      <b><span class='text09_b'>수입 및 지출에 관한 목록</span></b>
      <div style='width:100%' align='right'><span class='printhidden'></span></div>
    </p>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr><td><span class='text03_b'>I. 현재의 수입목록</span></td><td align='right'><span class='text02'>(단위 : 원)&nbsp;</span></td></tr>
      <tr><td colspan='2' style='height:12px;'></td></tr>
      <tr>
        <td colspan='2' height='10' style='padding-left:20px;'>
          <table cellpadding='0' cellspacing='0' border='1' bordercolor='1' style='border-collapse:collapse' width='100%' class='docxline'>
            <tr style='height:43.5px;'>
              <td width='20%' rowspan='3' align='center'><span class='text02'>수입상황</span></td>
              <td width='15%' style='padding-left:10px;'><span class='text02'>자영(상호)</span></td>
              <td width='20%' style='padding-left:10px;'><span class='text02'>&nbsp;</span></td>
              <td width='20%' style='padding-left:10px;'><span class='text02'>고용(직장명)</span></td>
              <td width='25%' style='padding-left:10px;'><span class='text02'>${htmlOrBlank(employment.workplaceName)}</span></td>
            </tr>
            <tr style='height:43.5px;'>
              <td style='padding-left:10px;'><span class='text02'>업&nbsp;&nbsp;종</span></td>
              <td style='padding-left:10px;'><span class='text02'>${htmlOrBlank(employment.jobTitle || employment.jobDescription)}</span></td>
              <td style='padding-left:10px;'><span class='text02'>직&nbsp;&nbsp;위</span></td>
              <td style='padding-left:10px;'><span class='text02'>${htmlOrBlank(employment.position)}</span></td>
            </tr>
            <tr style='height:43.5px;'>
              <td style='padding-left:10px;'><span class='text02'>종사경력</span></td>
              <td style='padding-left:10px;'><span class='text02'>${htmlOrBlank(employment.workPeriod)}</span></td>
              <td style='padding-left:10px;'><span class='text02'>근무기간</span></td>
              <td style='padding-left:10px;'><span class='text01'>${htmlOrBlank(employment.workPeriod)}</span></td>
            </tr>
            <tr style='height:43.5px;'>
              <td align='center'><span class='text02'>명&nbsp;&nbsp;목</span></td>
              <td style='padding-left:10px;'><span class='text02'>기간구분</span></td>
              <td style='padding-left:10px;'><span class='text02'>금&nbsp;액</span></td>
              <td style='padding-left:10px;'><span class='text02'>연간환산금액</span></td>
              <td style='padding-left:10px;'><span class='text02'>압류, 가압류 등 유무</span></td>
            </tr>
            <tr style='height:43.5px;'>
              <td align='center'><span class='text02'>급&nbsp;&nbsp;여</span></td>
              <td style='padding-left:10px;'><span class='text02'>월&nbsp;&nbsp;간</span></td>
              <td align='right'><span class='text02'>${formatMoneyCell(averageSalaryIncome)}&nbsp;</span></td>
              <td align='right'><span class='text02'>${formatMoneyCell(averageSalaryIncome * 12)}&nbsp;</span></td>
              <td align='center'><span class='text02'>&nbsp;무</span></td>
            </tr>
            <tr style='height:43.5px;'>
              <td align='center'><span class='text02'>상여금 등</span></td>
              <td align='center'><span class='text02'>&nbsp;</span></td>
              <td align='right'><span class='text02'>${formatMoneyCell(averageExtraIncome)}&nbsp;</span></td>
              <td align='right'><span class='text02'>${formatMoneyCell(averageExtraIncome * 12)}&nbsp;</span></td>
              <td align='center'><span class='text02'>&nbsp;</span></td>
            </tr>
            <tr style='height:43.5px;'>
              <td colspan='3' align='right'><span class='text02'>연수입 &nbsp;${formatMoneyCell(averageIncome * 12)}&nbsp;</span></td>
              <td><p align='left'><span class='text02'>&nbsp;</span></p></td>
              <td align='right'><span class='text02'>월평균수입 ${formatMoneyCell(averageIncome)}</span>&nbsp;</td>
            </tr>
          </table>
        </td>
      </tr>
    </table>

    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr><td style='height:43.5px;'></td></tr>
      <tr>
        <td><span class='text03_b'>II. 변제계획 수행시의 예상지출목록</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class='printhidden'></span></td>
      </tr>
      <tr>
        <td style='height:43.5px;'><br><span class='text03'><span class='text04'>${isOverSixty ? '□' : '▣'}</span> 채무자가 예상하는 생계비가 보건복지부 공표 기준중위소득의 60%이하인 경우</span></td>
      </tr>
      <tr>
        <td style='padding-left:38px;'><span class='text03' style='line-height:43px;'>보건복지부 공표
          ( ${displayHouseholdSize} )인 가족 기준중위소득
          ( ${formatNumberText(medianIncome)} )원의 &nbsp;약
          ( 60 )%인
          ( ${formatNumberText(Math.round(medianIncome * 0.6))} )원을 지출할 것으로 예상됩니다.</span>
        </td>
      </tr>
      <tr>
        <td style='height:43.5px;'><br><span class='text03'><span class='text04'>${isOverSixty ? '▣' : '□'}</span> 채무자가 예상하는 생계비가 보건복지부 공표 기준중위소득의 60%를 초과하는 경우</span></td>
      </tr>
      <tr>
        <td style='padding-left:38px;'><span class='text03' style='line-height:43px;'>보건복지부 공표
          ( ${displayHouseholdSize} )인 가족 기준중위소득
          ( ${formatNumberText(medianIncome)} )원의 &nbsp;약
          ( ${escapeHtml(String(adjustmentRatio))} )%인
          ( ${formatNumberText(adjustedExpenseAmount)} )원을 지출할 것으로 예상됩니다 (뒷면 표에 내역과 사유를 상세히 기재하십시오.)</span>
        </td>
      </tr>
    </table>

    <br>

    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr>
        <td class='text03_b'>III. 가족 관계<div align='right' class='printhidden'></div></td>
      </tr>
      <tr><td style='height:12px;'></td></tr>
      <tr>
        <td style='padding-left:20px;'>
          <table cellpadding='0' cellspacing='0' width='100%' class='docxline'>
            <tr style='height:43.5px;'>
              <td class='border_o' width='10%'><p align='center'><span class='text02'>관계</span></p></td>
              <td class='border_l' width='10%'><p align='center'><span class='text02'>성 &nbsp;명</span></p></td>
              <td class='border_l' width='6%'><p align='center'><span class='text02'>연령</span></p></td>
              <td class='border_l' width='18%'><p align='center'><span class='text02'>동거여부 및 기간</span></p></td>
              <td class='border_l' width='15%'><p align='center'><span class='text02'>직 업</span></p></td>
              <td class='border_l' width='16%'><p align='center'><span class='text02'>월 수입</span></p></td>
              <td class='border_l' width='16%'><p align='center'><span class='text02'>재산총액</span></p></td>
              <td class='border_l' width='9%'><p align='center'><span class='text02'>부양유무</span></p></td>
            </tr>
            ${familyRowMarkup}
          </table>
        </td>
      </tr>
    </table>

    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'>
    <br><br><br>

    <table align='center' style='width:100%'>
      <tr><td class='text02-b'>☞. 채무자가 예상하는 생계비가 보건복지부 공표 기준중위소득의 60%를 초과하는 경우 &nbsp;</td><td align='right'><span class='printhidden'></span></td></tr>
    </table>
    <br>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'><tr><td><span class='text02' style='line-height:40px;'>1. 생계비의 추가 지출 내역&nbsp;</span></td><td class='text02' align='right'>(단위 : 원)</td></tr></table>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr>
        <td width='16'></td>
        <td>
          <table bordercolor='#000000' cellpadding='0' cellspacing='0' style='border-collapse:collapse' border='1' width='100%' class='docxline'>
            <tr style='height:49.5px;'>
              <td width='160' nowrap colspan='2'><p align='center'><span class='text02'>비 &nbsp;&nbsp;&nbsp;목</span></p></td>
              <td width='138' nowrap><p align='center'><span class='text02'>지출예상기준중위소득</span></p></td>
              <td width='382' nowrap><p align='center'><span class='text02'>추가지출 사유</span></p></td>
            </tr>
            ${expenseRowMarkup}
            <tr bgcolor='#ffffff' style='height:41.5px;'>
              <td class='text02' style='border-bottom:0'>&nbsp;&nbsp;&nbsp;합 계</td>
              <td class='text02' style='border-bottom:0' align='right' width='138' nowrap>${livingExpenseTotal ? formatNumberText(livingExpenseTotal) : '&nbsp;'}</td>
              <td class='text01' style='border-bottom:0; border-right:0' width='382' nowrap>&nbsp;</td>
            </tr>
            <tr bgcolor='#ffffff' style='height:41.5px;'>
              <td colspan='2'><p align='center'><span class='text02'>&nbsp;&nbsp;주&nbsp; 거&nbsp; 비</span></p></td>
              <td><p align='right'><span class='text02'>${housingExpense ? formatNumberText(housingExpense) : '&nbsp;'}</span></p></td>
              <td width='382'><p align='left'><span class='text02' style='padding-left:3px;'>${htmlOrBlank(page3?.expenseReasons?.[7])}</span></p></td>
            </tr>
            <tr bgcolor='#ffffff' style='height:41.5px;'>
              <td colspan='2'><p align='center'><span class='text02'>&nbsp;&nbsp;의&nbsp; 료&nbsp; 비</span></p></td>
              <td><p align='right'><span class='text02'>${medicalExpense ? formatNumberText(medicalExpense) : '&nbsp;'}</span></p></td>
              <td width='382'><p align='left'><span class='text02' style='padding-left:3px;'>${htmlOrBlank(page3?.expenseReasons?.[8])}</span></p></td>
            </tr>
            <tr bgcolor='#ffffff' style='height:41.5px;'>
              <td colspan='2'><p align='center'><span class='text02'>&nbsp;&nbsp;교&nbsp; 육&nbsp; 비</span></p></td>
              <td><p align='right'><span class='text02'>${educationExpense ? formatNumberText(educationExpense) : '&nbsp;'}</span></p></td>
              <td width='382'><p align='left'><span class='text02' style='padding-left:3px;'>${htmlOrBlank(page3?.expenseReasons?.[9])}</span></p></td>
            </tr>
            <tr bgcolor='#ffffff' style='height:49.5px;'>
              <td colspan='2'><p align='center'><span class='text02'>계</span></p></td>
              <td><p align='right'><span class='text02'>${formatNumberText(totalAdditionalExpense)}&nbsp;</span></p></td>
              <td width='382'><p align='justify'><span class='text02'>&nbsp;&nbsp;추가비율 : &nbsp;${escapeHtml(String(adjustmentRatio))}&nbsp;%</span></p></td>
            </tr>
          </table>
          <br><br>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
            <tr><td colspan='2'><span class='text02' style='line-height:40px;'>2. 생계비 추가지출사유에 관한 보충기재사항 &nbsp;</span></td></tr>
            <tr>
              <td width='15'></td>
              <td>
                <table border='0' cellpadding='10' cellspacing='0' bgcolor='#000000' width='100%'>
                  <tr><td class='border_o' height='300' valign='top' bgcolor='#ffffff'><span class='text02' style='line-height:35px;'>${expenseReasonLines.length ? escapeHtml(expenseReasonLines.join('\n\n')).replace(/\n/g, '<br>') : '&nbsp;&nbsp;&nbsp;'}</span></td></tr>
                </table>
              </td>
            </tr>
          </table>
        </td>
      </tr>
    </table>

    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'>
    <br><br><br>
    <style>
      .fincure-income-expense-compact .text01 {
        font-size: 7pt !important;
        line-height: 1.2 !important;
      }
    </style>
    <div class='fincure-income-expense-compact'>
      ${buildIncomeExpenseDetailTable('월평균소득 산출 내역서 (수입)', incomeRows, '', allMonthColumns)}
      <br><br>
      ${buildIncomeExpenseDetailTable('월평균소득 산출 내역서 (공제)', deductionRows, '', allMonthColumns)}
      <br><br>
      ${buildIncomeExpenseNetTable(netByMonth, allMonthColumns)}
      ${buildIncomeExpenseSummaryTable([
        ['연 소득총액(A)', `${formatNumberText(annualIncomeTotal)}원`],
        ['연 공제총액(B)', `${formatNumberText(annualDeductionTotal)}원`],
        ['연 실수령액(A-B)', `${formatNumberText(annualNetIncomeTotal)}원`]
      ], 3)}
      ${buildIncomeExpenseSummaryTable([
        ['월평균수입', `${formatNumberText(averageIncome)}원`],
        ['기준중위소득조정금액', `${formatNumberText(Math.round(medianIncome * 0.6))}원`],
        ['월평균가용소득', `${formatNumberText(averageNetIncome - Math.round(medianIncome * 0.6))}원`],
        ['연간총수입환산액', `${formatNumberText(annualGrossEquivalent)}원`]
      ])}
    </div>
  `
}

const formatStatementMonthRange = (
  startYear?: string | null,
  startMonth?: string | null,
  endYear?: string | null,
  endMonth?: string | null,
  isCurrent = false
) => {
  const normalizedStartYear = String(startYear || '').trim() || '&nbsp;&nbsp;&nbsp;'
  const normalizedStartMonth = String(startMonth || '').trim() || '&nbsp;&nbsp;&nbsp;'
  const normalizedEndYear = isCurrent ? '현재' : String(endYear || '').trim() || '&nbsp;&nbsp;&nbsp;'
  const normalizedEndMonth = isCurrent ? '' : String(endMonth || '').trim() || '&nbsp;&nbsp;&nbsp;'

  if (isCurrent) {
    return `${normalizedStartYear}년&nbsp;&nbsp;${normalizedStartMonth}월 부터&nbsp;&nbsp;&nbsp;현재까지`
  }

  return `${normalizedStartYear}년&nbsp;&nbsp;${normalizedStartMonth}월 부터&nbsp;&nbsp;&nbsp;${normalizedEndYear}년&nbsp;&nbsp;${normalizedEndMonth}월 까지`
}

const buildStatementHistoryLabel = (type: string) => {
  switch (String(type || '').trim()) {
    case '근무':
      return '근무'
    case '자영':
      return '자영'
    default:
      return '&nbsp;&nbsp;&nbsp;'
  }
}

const buildStatementHousingMark = (selected: boolean) =>
  selected ? '○' : '&nbsp;'

const buildStatementReasonMark = (selected: boolean) =>
  selected ? '●' : '○'

const addMonthsFromDateParts = (year: string, month: string, day: string, offsetMonths: number) => {
  if (!year || !month || !day) {
    return { year: '', month: '', day: '' }
  }

  const baseDate = new Date(Number(year), Number(month) - 1, Number(day))
  if (Number.isNaN(baseDate.getTime())) {
    return { year: '', month: '', day: '' }
  }

  baseDate.setMonth(baseDate.getMonth() + offsetMonths)
  return {
    year: String(baseDate.getFullYear()),
    month: String(baseDate.getMonth() + 1).padStart(2, '0'),
    day: String(baseDate.getDate()).padStart(2, '0')
  }
}

const buildStatementReferenceBody = (context: RecoveryDocumentContext) => {
  const page1 = context.pages['1'] || {}
  const page2 = context.pages['2'] || {}
  const page16 = context.pages['16'] || {}
  const page17 = context.pages['17'] || {}
  const employment = getEmploymentDataFromContext(context)
  const statement = page17?.formData || {}
  const applicantName = String(context.caseInfo?.applicant_name || page1?.applicantName || '').trim()

  const currentCareer = {
    startYear: String(employment.currentWorkStart?.year || '').trim(),
    startMonth: String(employment.currentWorkStart?.month || '').trim(),
    employmentType: String(employment.employmentType || '').trim(),
    industry: String(employment.jobTitle || '').trim(),
    workplaceName: String(employment.workplaceName || '').trim(),
    position: String(employment.position || '').trim()
  }

  const pastCareers = Array.isArray(page16?.pastCareers) ? page16.pastCareers : []
  const careerRows = [
    currentCareer,
    ...pastCareers
  ].slice(0, 4)

  while (careerRows.length < 4) {
    careerRows.push({
      startYear: '',
      startMonth: '',
      endYear: '',
      endMonth: '',
      employmentType: '',
      industry: '',
      workplaceName: '',
      position: ''
    })
  }

  const careerMarkup = careerRows.map((row, index) => {
    const isCurrent = index === 0
    const periodText = formatStatementMonthRange(
      row.startYear,
      row.startMonth,
      row.endYear,
      row.endMonth,
      isCurrent
    )

    return `
      <tr style='height:36.5px;'>
        <td class='border_o' width='80'>
          <p align='center'><span class='text02'>기 간</span></p>
        </td>
        <td class='border_l' colspan='5' width='570'>&nbsp;<span class='text02'>${periodText}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;( <b>${buildStatementHistoryLabel(row.employmentType)}</b> )</span></td>
      </tr>
      <tr style='height:36.5px;'>
        <td class='border_t' width='80'>
          <p align='center'><span class='text02'>업 종</span></p>
        </td>
        <td class='border_tl' width='137'>&nbsp;<span class='text02'>${htmlOrBlank(row.industry)}</span></td>
        <td class='border_tl' width='79'>
          <p align='center'><span class='text02'>직장명</span></p>
        </td>
        <td class='border_tl' style='width:23%;'>&nbsp;<span class='text02'>${htmlOrBlank(row.workplaceName)}</span></td>
        <td class='border_tl' width='88'>
          <p align='center'><span class='text02'>직 위</span></p>
        </td>
        <td class='border_tl' width='106'>&nbsp;<span class='text02'>${htmlOrBlank(row.position)}</span></td>
      </tr>
    `
  }).join('')

  const maritalHistories = Array.isArray(page16?.maritalHistories) ? page16.maritalHistories : []
  const maritalPairs = Array.from({ length: Math.max(1, Math.ceil(maritalHistories.length / 2)) }, (_, index) =>
    maritalHistories.slice(index * 2, index * 2 + 2)
  )

  const maritalMarkup = maritalPairs.map((pair) => {
    const [left, right] = pair
    const renderItem = (item?: Record<string, any>) => {
      if (!item) return `<span class='text02'>&nbsp;</span>`
      const year = String(item.historyYear || '').trim()
      const month = String(item.historyMonth || '').trim()
      const day = String(item.historyDay || '').trim()
      const name = String(item.partnerName || '').trim()
      const type = String(item.historyType || '').trim()
      if (!year && !month && !day && !name && !type) {
        return `<span class='text02'>&nbsp;</span>`
      }

      return `<span class='text02'>${year || '&nbsp;&nbsp;&nbsp;&nbsp;'}년&nbsp;&nbsp;${month || '&nbsp;&nbsp;'}월&nbsp;&nbsp;${day || '&nbsp;&nbsp;'}일&nbsp;&nbsp;<b>${escapeHtml(name || '&nbsp;')}</b> 와&nbsp;( ${escapeHtml(type || '&nbsp;')} )</span>`
    }

    return `<tr><td valign='top'>${renderItem(left)}</td><td>${renderItem(right)}</td></tr><tr><td></td></tr>`
  }).join('')

  const moveInDateText = `(&nbsp;${String(page2?.moveInDate?.year || '').trim() || '&nbsp;&nbsp;&nbsp;&nbsp;'}년&nbsp;&nbsp;&nbsp;${String(page2?.moveInDate?.month || '').trim() || '&nbsp;&nbsp;'}월&nbsp;&nbsp;&nbsp;${String(page2?.moveInDate?.day || '').trim() || '&nbsp;&nbsp;'}일&nbsp;)&nbsp;`
  const rentalInfo = page2?.rentalInfo || {}
  const dormitoryInfo = page2?.dormitoryInfo || {}
  const ownerInfo = page2?.ownerInfo || {}
  const otherOwnerInfo = page2?.otherOwnerInfo || {}
  const otherRentalInfo = page2?.otherRentalInfo || {}
  const spouseRentalInfo = page2?.spouseRentalInfo || {}
  const houseType = String(page2?.houseType || '').trim()

  const proceedingsPages = ['15_1', '15_2', '15_3', '15_4', '15_5']
  const proceedingRows = proceedingsPages
    .flatMap((pageId) => Array.isArray(context.pages[pageId]?.items) ? context.pages[pageId].items : [])
    .filter((item: Record<string, any>) => String(item?.enabled || '').trim() === 'Y')
    .slice(0, 7)

  const proceedingMarkup = proceedingRows.length
    ? proceedingRows.map((item: Record<string, any>) => `
      <tr style='height:43.5px;'>
        <td class='text01' style='padding-left:5px;'>${htmlOrBlank(item.proceedingType || item.procedureStatus || item.note)}</td>
        <td class='text01' style='padding-left:5px;'>${htmlOrBlank(item.creditorName)}</td>
        <td class='text01' style='padding-left:5px;'>${htmlOrBlank(item.court)}</td>
        <td class='text01' style='padding-left:5px;'>${htmlOrBlank(item.caseNumber)}</td>
      </tr>
    `).join('')
    : `
      <tr style='height:43.5px;'>
        <td class='text01'>&nbsp;</td>
        <td class='text01' rowspan='3' colspan='2' align='center' style='font-weight:bold'>해당사항 없습니다.</td>
        <td class='text01'>&nbsp;</td>
      </tr>
      <tr style='height:43.5px;'><td class='text01'>&nbsp;</td><td class='text01'>&nbsp;</td></tr>
      <tr style='height:43.5px;'><td class='text01'>&nbsp;</td><td class='text01'>&nbsp;</td></tr>
    `

  const proceedingBaseRowCount = proceedingRows.length ? proceedingRows.length : 3
  const proceedingPaddingRows = Array.from({ length: Math.max(0, 7 - proceedingBaseRowCount) }, () => `
    <tr style='height:43.5px;'>
      <td class='text01'>&nbsp;</td>
      <td class='text01'>&nbsp;</td>
      <td class='text01'>&nbsp;</td>
      <td class='text01'>&nbsp;</td>
    </tr>
  `).join('')

  const selectedReasons = Array.isArray(statement.reasons) ? statement.reasons : []
  const hasReason = (label: string) => selectedReasons.includes(label)
  const otherReasonText = String(statement.otherReason || '').trim()
  const debtNarrative = String(statement.debtNarrative || '').trim()
  const recoveryNarrative = String(statement.recoveryNarrative || '').trim()
  const attachmentNarrative = [debtNarrative, recoveryNarrative].filter(Boolean).join('\n\n')

  const historySource = statement.history || {}
  const historyRows = [
    { label: '파산.면책절차', key: 'bankruptcy' },
    { label: '화의.회생.개인회생절차', key: 'recovery' },
    { label: '신용회복위원회 워크아웃', key: 'workout' },
    { label: '배드뱅크', key: 'badBank' }
  ].map(({ label, key }) => {
    const item = historySource[key] || {}
    const checked = String(item.used || 'N') === 'Y'
    return `
      <tr style='height:43.5px;'>
        <td class='border_t' width='206'><p align='left'><span class='text02'>&nbsp;<span class='text06'>${checked ? '■' : '□'}</span> ${label}</span></p></td>
        <td class='border_tl' width='141'><p align='left'><span class='text02'>&nbsp;${htmlOrBlank(item.court)}</span></p></td>
        <td class='border_tl' width='166'><p align='left'><span class='text02'>&nbsp;${checked ? `${escapeHtml(item.startYear || '&nbsp;&nbsp;&nbsp;&nbsp;')}년&nbsp;&nbsp;&nbsp;${escapeHtml(item.startMonth || '&nbsp;&nbsp;')}월` : '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;년&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;월'}</span></p></td>
        <td class='border_tl' width='226'><p align='left'><span class='text01' style='line-height:20px;'>&nbsp;${htmlOrBlank(item.status)}</span></p></td>
      </tr>
    `
  }).join('')

  return `
    <br><br><br><br>
    <p align='center'>
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b><span class='text11_b'>진 &nbsp;&nbsp;술 &nbsp;&nbsp;서</span></b><span class=printhidden></span>
    </p>
    <table cellpadding='0' cellspacing='0' width='100%' align='center' border='0'>
      <tr><td><span class='text03_b'>I. 경력</span></td></tr>
      <tr><td style='height:29.5px;'></td></tr>
      <tr>
        <td style='padding-left:20px;'><span class='text02'>1. 최종학력</span> :&nbsp;&nbsp;<span class='text02'>${htmlOrBlank(employment.finalEducationYear)}년&nbsp;&nbsp;${htmlOrBlank(employment.finalEducationMonth)}월&nbsp;&nbsp;&nbsp;&nbsp;${htmlOrBlank(employment.finalEducationSchool)} 학교 ( <b>${escapeHtml(employment.finalEducationStatus || '졸업')}</b> ) </span></td>
      </tr>
      <tr><td style='height:29.5px;'></td></tr>
      <tr><td style='padding-left:20px;'><span class='text02'>2. 과거 경력 (최근 경력부터 기재하여 주십시오)</span></td></tr>
      <tr><td height='5'></td></tr>
      <tr>
        <td style='padding-left:40px;'>
          <table cellpadding='0' cellspacing='0' class='docxline' width='100%'>
            ${careerMarkup}
          </table>
        </td>
      </tr>
      <tr><td style='height:29.5px;'></td></tr>
      <tr><td style='padding-left:20px;'><span class='text02'>3. 과거 결혼, 이혼, 입양, 파양 경력</span></td></tr>
      <tr><td height='5'></td></tr>
      <tr>
        <td style='padding-left:40px;'>
          <table border='0' cellpadding='0' cellspacing='0' width='660' class='docxline'>${maritalMarkup}</table>
        </td>
      </tr>
    </table>
    <br><br>
    <center><div style='width:100%' align='right'><span class='printhidden'></span></div></center>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr><td><b><span class='text03_b'>II. 현재 주거상황</span></b></td></tr>
      <tr><td style='height:29.5px;'></td></tr>
      <tr><td style='padding-left:40px;'><span class='text02'>거주를 시작한 시점 &nbsp;&nbsp;${moveInDateText}</span></td></tr>
      <tr><td style='height:12px;'></td></tr>
      <tr>
        <td style='padding-left:40px;'>
          <table cellpadding='0' cellspacing='0' class='docxline' width='100%'>
            <tr style='height:59px;'>
              <td class='border_o' width='350'><p align='center'><span class='text02'>거주관계(해당란에 표시)</span></p></td>
              <td class='border_l' width='420'><p align='center'><span class='text02'>상세한 내역</span></p></td>
            </tr>
            <tr style='height:54.5px;'>
              <td class='border_t'><p align='justify'><span class='text02'>&nbsp;<span style='span-size:13.5px'>( ${buildStatementHousingMark(houseType === 'A')} )</span>&nbsp;㉠ 신청인 소유의 주택</span></p></td>
              <td class='border_tl'><p align='justify'><span class='text02'>&nbsp;</span></p></td>
            </tr>
            <tr style='height:82.5px;'>
              <td class='border_t'>
                <p align='justify'><span class='text02' style='line-height:35px;'>&nbsp;<span style='span-size:13.5px'>( ${buildStatementHousingMark(houseType === 'B')} )</span>&nbsp;㉡ 사택 또는 기숙사<br>
                &nbsp;<span style='span-size:13.5px'>( ${buildStatementHousingMark(houseType === 'C' || houseType === 'G' || houseType === 'F')} )</span>&nbsp;㉢ 임차( 전ㆍ월세 ) 주택</span></p>
              </td>
              <td class='border_tl'>
                <p align='justify'><span class='text02' style='line-height:22px;'>&nbsp;임대보증금&nbsp;( ${escapeHtml(rentalInfo.deposit || spouseRentalInfo.deposit || dormitoryInfo.deposit || otherRentalInfo.deposit || '&nbsp;')} 원 )<br>
                &nbsp;임&nbsp;대&nbsp;료&nbsp;(월 ${escapeHtml(rentalInfo.monthlyRent || spouseRentalInfo.monthlyRent || dormitoryInfo.monthlyRent || otherRentalInfo.monthlyRent || '&nbsp;')} 원 )<br>&nbsp;연&nbsp;체&nbsp;액&nbsp;( ${escapeHtml(rentalInfo.arrears || spouseRentalInfo.arrears || dormitoryInfo.arrears || '&nbsp;')} 원 )<br>
                &nbsp;임차인 성명&nbsp;&nbsp;(&nbsp;${escapeHtml(rentalInfo.tenantName || spouseRentalInfo.spouseName || otherRentalInfo.contractorName || applicantName || '&nbsp;')} )</span></p>
              </td>
            </tr>
            <tr height='65'>
              <td class='border_t'>
                <p align='justify'><span class='text02' style='line-height:22px;'>&nbsp;<span style='span-size:13.5px'>( ${buildStatementHousingMark(houseType === 'D')} )</span>&nbsp;㉣ 친족 소유 주택에 무상 거주<br>
                &nbsp;<span style='span-size:13.5px'>( ${buildStatementHousingMark(houseType === 'E')} )</span>&nbsp;㉤ 친족외 소유 주택에 무상 거주</span></p>
              </td>
              <td class='border_tl'>
                <p align='justify'><span class='text02' style='line-height:22px;'>&nbsp;소유자 성명&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;( ${htmlOrBlank(ownerInfo.name || otherOwnerInfo.name)} )<br>
                &nbsp;신청인과의 관계&nbsp;&nbsp;( ${htmlOrBlank(ownerInfo.relationship || otherOwnerInfo.relationship)} )</span></p>
              </td>
            </tr>
            <tr style='height:54.5px;'>
              <td class='border_t'>
                <p align='justify'><span class='text02'>&nbsp;<span style='span-size:13.5px'>( ${buildStatementHousingMark(houseType === 'X')} )</span>&nbsp;&nbsp;㉥ 기타(${htmlOrBlank(page2?.otherInfo)})</span></p>
              </td>
              <td class='border_tl'><p align='justify'>&nbsp;<span class='text02'>${htmlOrBlank(rentalInfo.address || spouseRentalInfo.address || page2?.otherInfo)}</span></p></td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'><br><br><br>
    <center><div style='width:100%' align='right'><span class='printhidden'></span></div></center>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr><td><b><span class='text03_b'>III. 부채 상황</span></b></td></tr>
      <tr><td style='height:29.5px;'></td></tr>
      <tr><td style='padding-left:20px;'><span class='text02'>1. 채권자로부터 민사소송·지급명령·전부명령·압류·가압류 등을 받은 경험이 있음 ${proceedingRows.length ? "<span class='text06'>■</span>" : "<span class='text06'>□</span>"}&nbsp;&nbsp;없음 ${proceedingRows.length ? "<span class='text06'>□</span>" : "<span class='text06'>■</span>"}</span></td></tr>
      <tr><td style='height:29.5px;'></td></tr>
      <tr>
        <td style='padding-left:40px;'>
          <table cellpadding='3' cellspacing='0' width='100%' style='border-collapse:collapse' border='1' bordercolor='#000000' class='docxline'>
            <tr>
              <td width='137' style='height:43.5px;'><p align='center'><span class='text02'>내&nbsp;&nbsp;&nbsp;역</span></p></td>
              <td width='266' style='height:43.5px;'><p align='center'><span class='text02'>채&nbsp;권&nbsp;자</span></p></td>
              <td width='226' style='height:43.5px;'><p align='center'><span class='text02'>관&nbsp;할&nbsp;법&nbsp;원&nbsp;</span></p></td>
              <td width='110' style='height:43.5px;'><p align='center'><span class='text02'>사 건 번 호</span></p></td>
            </tr>
            ${proceedingMarkup}
            ${proceedingPaddingRows}
          </table>
        </td>
      </tr>
    </table>
    <br><br>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr><td style='padding-left:20px;'><span class='text02'>2. 개인회생절차에 이르게 된 사정(여러 항목 중복 선택 가능)</span></td></tr>
      <tr><td height='5'></td></tr>
      <tr>
        <td style='padding-left:30px;'>
          <table border='0' cellpadding='0' cellspacing='0' width='100%'>
            <tr>
              <td><span class='text02'>&nbsp;${buildStatementReasonMark(hasReason('생활비 부족'))} 생활비 부족</span></td>
              <td><span class='text02'>&nbsp;${buildStatementReasonMark(hasReason('병원비 과다지출'))} 병원비 과다지출</span></td>
              <td><span class='text02'>&nbsp;${buildStatementReasonMark(hasReason('교육비 과다지출'))} 교육비 과다지출</span></td>
            </tr>
            <tr><td height='5'></td></tr>
            <tr>
              <td><span class='text02'>&nbsp;${buildStatementReasonMark(hasReason('음식 / 음주 / 여행 / 도박 또는 취미활동'))} 음식, 음주, 여행, 도박 또는 취미활동</span></td>
              <td><span class='text02'>&nbsp;${buildStatementReasonMark(hasReason('점포운영의 실패'))} 점포 운영의 실패</span></td>
              <td><span class='text02'>&nbsp;${buildStatementReasonMark(hasReason('타인 채무의 보증'))} 타인 채무의 보증</span></td>
            </tr>
            <tr><td height='5'></td></tr>
            <tr>
              <td><span class='text02'>&nbsp;${buildStatementReasonMark(hasReason('주식투자 실패'))} 주식투자 실패</span></td>
              <td><span class='text02'>&nbsp;${buildStatementReasonMark(hasReason('사기피해'))} 사기 피해</span></td>
            </tr>
            <tr><td height='5'></td></tr>
            <tr>
              <td colspan='3' style='word-break:break-all'>
                <table cellpadding='0' cellspacing='0' width='100%'>
                  <tr><td width='120' valign='top'><span class='text02'>&nbsp;${buildStatementReasonMark(hasReason('기타'))} 기&nbsp;타</span></td><td valign='top' style='word-break:break-all'><span class='text02'>( ${escapeHtml(otherReasonText || '&nbsp;')} )</span></td></tr>
                </table>
              </td>
            </tr>
          </table>
        </td>
      </tr>
      <tr><td height='15'></td></tr>
      <tr>
        <td style='padding-left:20px;'>
          <table cellpadding='0' cellspacing='0' width='100%'>
            <tr>
              <td width='15' valign='top'><span class='text02' style='line-height:35px;'>3.</span></td>
              <td valign='top'><span class='text02' style='line-height:35px;'>채무자가 많은 채무를 부담하게 된 사정 및 개인회생절차 개시의 신청에 이르게 된 사정에 관하여 구체적으로 기재하여 주십시오.</span>&nbsp;<span class='text02'><b>별지 기재와 같음</b></span></td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    <br><br>
    <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr><td colspan='2'><b><span class='text03_b'>IV. 과거 면책절차 등의 이용 상황</span></b></td></tr>
      <tr><td style='height:29.5px;'></td></tr>
      <tr>
        <td width='30'></td>
        <td border='0'>
          <table cellpadding='0' cellspacing='0' class='docxline' width='100%'>
            <tr style='height:43.5px;'>
              <td class='border_o' width='206'><p align='center'><span class='text02'>&nbsp;절&nbsp;&nbsp;&nbsp;차</span></p></td>
              <td class='border_l' width='141'><p align='center'><span class='text02'>&nbsp;법원 또는 기관</span></p></td>
              <td class='border_l' width='166'><p align='center'><span class='text02'>&nbsp;신 청 시 기</span></p></td>
              <td class='border_l' width='226'><p align='center'><span class='text02'>&nbsp;현재까지 진행상황</span></p></td>
            </tr>
            ${historyRows}
          </table>
        </td>
      </tr>
    </table>
    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'><br><br>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr><td><span class='text02'>별 지</span></td></tr>
      <tr><td align='center'><span class='text07_b'><br>채무자가 많은 채무를 부담하게 된 사정 등에 관한 진술서</span></td></tr>
      <tr><td>&nbsp;<center><div style='width:100%' align='right'><span class='printhidden'></span></div></center></td></tr>
    </table>
    <table style='border:1px solid #000000' cellpadding='0' cellspacing='0' width='100%' height='900' class='docxline' align='center' valign='top'>
      <tr>
        <td style='padding:10px;' valign='top'><span class='text02' style='line-height:30px;'>${attachmentNarrative ? escapeHtml(attachmentNarrative).replace(/\n/g, '<br /><br />') : '&nbsp;'}<br /><br />신 청 인 : ${escapeHtml(applicantName || '&nbsp;')}</span></td>
      </tr>
    </table>
  `
}

const buildRepaymentPlanReferenceBody = (context: RecoveryDocumentContext) => {
  const basics = getContextBasics(context)
  const page2 = context.pages['2'] || {}
  const page3 = context.pages['3'] || {}
  const page4 = context.pages['4'] || {}
  const page5 = context.pages['5'] || {}
  const page16 = context.pages['16'] || {}
  const employment = getEmploymentDataFromContext(context)

  const enabledMonthColumns = getEnabledPage5MonthColumns(page5)
  const incomeRows = Array.isArray(page5?.incomeRows) ? page5.incomeRows : []
  const deductionRows = Array.isArray(page5?.deductionRows) ? page5.deductionRows : []
  const enabledMonthCount = enabledMonthColumns.length || 12
  const totalForRows = (rows: Array<Record<string, any>>) =>
    rows.reduce((sum, row) => {
      const amounts = row?.amounts || {}
      return sum + enabledMonthColumns.reduce((monthSum, column) => monthSum + parseNumeric(amounts[column.key]), 0)
    }, 0)

  const averageIncome = Math.round(totalForRows(incomeRows) / enabledMonthCount)
  const displayHouseholdSize = getDisplayHouseholdSize(page2?.dependentsCount)
  const householdSize = getMedianIncomeHouseholdSize(page2?.dependentsCount)
  const medianIncome = getIncomeExpenseMedianIncome(householdSize)
  const adjustmentRatio = Number(page3?.adjustmentRatio || 60)
  const adjustedExpenseAmount = Math.round(medianIncome * (adjustmentRatio / 100))
  const availableIncome = averageIncome - adjustedExpenseAmount
  const commissionerPercent = Number(employment.commissionerPercent || employment.commissionerRate || 0)
  const monthlyCommissionerFee = employment.hasExternalCommissioner
    ? Math.max(0, Math.round(Math.max(availableIncome, 0) * (commissionerPercent / 100)))
    : 0
  const actualAvailableIncome = Math.max(0, availableIncome - monthlyCommissionerFee)
  const repaymentMonths = parseNumeric(basics.repaymentMonths)
  const firstDate = basics.firstRepaymentDateParts
  const endDate = repaymentMonths
    ? addMonthsFromDateParts(firstDate.year, firstDate.month, firstDate.day, Math.max(repaymentMonths - 1, 0))
    : { year: '', month: '', day: '' }
  const totalActualAvailableIncome = actualAvailableIncome * repaymentMonths

  const employmentType = String(employment.employmentType || '').trim()
  const isSalary = employmentType !== '자영'
  const salaryCheck = isSalary ? "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='16' height='16'>" : "<span class='text04'>□</span>"
  const businessCheck = isSalary ? "<span class='text04'>□</span>" : "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='16' height='16'>"

  const hasAssets = false
  const hasSecuredClaims = false
  const estateClaims = page4?.hasEstateClaim && Array.isArray(page4?.estateClaims)
    ? page4.estateClaims
    : []
  const priorityClaims = page4?.hasPriorityClaim && Array.isArray(page4?.priorityClaims)
    ? page4.priorityClaims
    : []
  const hasEstatePriorityClaims = estateClaims.length > 0
  const hasPriorityClaims = priorityClaims.length > 0
  const hasUnconfirmedClaims = false
  const paymentDay = firstDate.day || '25'
  const collectBaseEntries = collectCreditorEntries(context.pages)
  const generalClaimBaseTotal = collectBaseEntries.reduce((sum, entry) => {
    if (entry.rowType !== 'unsecured') return sum
    return sum + (page4?.repaymentPlanType === 'B' ? entry.fullClaimAmount : entry.principalAmount)
  }, 0)
  const repaymentRate = generalClaimBaseTotal > 0
    ? ((totalActualAvailableIncome / generalClaimBaseTotal) * 100)
    : 0

  const priorityRows = priorityClaims.slice(0, 14)
    .map((item: Record<string, any>) => `
      <tr bgcolor='#ffffff'>
        <td class='border_t' width='143' style='height:51.5px;padding-left:5px;'>${htmlOrBlank(item.institutionName)}</td>
        <td class='border_tl' width='103' style='height:51.5px;' align='right'><span class='text01' style='padding-right:3px;'>${formatNumberText(parseNumeric(item.principalAmount) + parseNumeric(item.surchargeAmount))}원</span></td>
        <td class='border_tl' width='350' style='height:51.5px;padding-left:5px;'>${htmlOrBlank(`${item.claimType || ''}${item.basis ? ` (${item.basis})` : ''}`.trim())}</td>
        <td class='border_tl' width='120' style='height:51.5px;padding-left:5px;'>즉시변제</td>
      </tr>
    `).join('')

  const priorityBlankRows = Array.from({ length: Math.max(0, 14 - priorityClaims.length - (hasPriorityClaims ? 0 : 2)) }, () => `
    <tr bgcolor='#ffffff'>
      <td class='border_t' width='143' style='height:51.5px;'>&nbsp;</td>
      <td class='border_tl' width='103' style='height:51.5px;'>&nbsp;</td>
      <td class='border_tl' width='350' style='height:51.5px;'>&nbsp;</td>
      <td class='border_tl' width='120' style='height:51.5px;'>&nbsp;</td>
    </tr>
  `).join('')

  const priorityTableBody = hasPriorityClaims
    ? `${priorityRows}${priorityBlankRows}`
    : `
      <tr bgcolor='#ffffff'>
        <td class='border_t' width='143' style='height:51.5px;'>&nbsp;</td>
        <td class='border_tl' width='103' style='height:51.5px;'>&nbsp;</td>
        <td class='border_tl' width='350' style='height:51.5px;'>&nbsp;</td>
        <td class='border_tl' width='120' style='height:51.5px;'>&nbsp;</td>
      </tr>
      <tr bgcolor='#ffffff'>
        <td class='border_t' width='143' style='height:51.5px;'>&nbsp;</td>
        <td class='border_tl' width='423' align='center' style='height:51.5px;' colspan='2' rowspan='2'><span class='text02'>해당사항 없습니다.</span></td>
        <td class='border_tl' width='120' style='height:51.5px;'>&nbsp;</td>
      </tr>
      <tr bgcolor='#ffffff'>
        <td class='border_t' width='143' style='height:51.5px;'>&nbsp;</td>
        <td class='border_tl' width='120' style='height:51.5px;'>&nbsp;</td>
      </tr>
      ${priorityBlankRows}
    `

  return `
    <br><br><br>
    <table border='0' cellpadding='0' width='100%' cellspacing='0' align='center' style='padding:16px; border:4px double #000000;'>
      <tr>
        <td>
          <table border='0' cellpadding='0' cellspacing='0' width='100%'><tr><td align='right' class='text01_b'>[전산양식 A5433]</td></tr></table>
          <br><br>
          <table border='0' cellpadding='0' cellspacing='0' width='100%'>
            <tr><td style='padding-left:5px;'>
              <table cellpadding='0' cellspacing='0' bgcolor='#000000' width='260' style='height:49.5px;' border='1'>
                <tr><td bgcolor='#ffffff' align='center' class='text03_b'>가용소득만으로 변제하는 경우</td></tr>
              </table>
            </td></tr>
          </table>
          <br><br><br><br>
          <p align='center'><b><span class='text10_b'>변제계획안 제출서</span></b></p>
          <br><br><br><br>
          <p align='justify'>
            <span class='text03'>&nbsp;&nbsp;사&nbsp;&nbsp;건&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<u>${escapeHtml(String(context.pages['1']?.caseYear || basics.filingDateParts.year || ''))}</u></span>
            <span class='text03'>&nbsp;개회</span>&nbsp;<span class='text03'><u>${escapeHtml(String(basics.caseNumber || '').replace(/^.*개회\s*/, '').replace(/\s*호?$/, ''))}</u></span>&nbsp;<span class='text03'>개인회생</span>
            <br><br><br>
            <span class='text03'>&nbsp;&nbsp;채 무 자&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${escapeHtml(basics.applicantName)}${basics.residentNumber ? ` ( ${escapeHtml(basics.residentNumber)} )` : ''}</span>
          </p>
          <br><br><br><br>
          <p align='center'><span class='text03'>채무자는 별지와 같이 변제계획안을 작성하여 제출하니 인가하여 주시기 바랍니다.</span></p>
          <br><br><br><br><br><br>
          <table border='0' cellpadding='0' cellspacing='0' align='center' class='center'>
            <tr>
              <td>
                <span class='text03'>${basics.filingDate}</span><br><br>
                <span class='text03'>채 &nbsp;무 &nbsp;자&nbsp;&nbsp;&nbsp;${escapeHtml(basics.applicantName)}&nbsp;&nbsp;&nbsp;</span><br><br>
                <span class='text03'>대&nbsp;&nbsp;리&nbsp;&nbsp;인&nbsp;&nbsp;&nbsp;${escapeHtml(basics.lawyerName || '')}&nbsp;&nbsp;&nbsp;(인)</span>
              </td>
            </tr>
          </table>
          <br><br><br><br><br>
          <p align='justify'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b><span class='text07_b'>${escapeHtml(page2?.court || '')}&nbsp;&nbsp;귀중</span></b></p>
          <br><br>
        </td>
      </tr>
    </table>
    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'>
    <br><br><br>
    <p align='justify'><span class='text01' style='line-height:24px;'>&nbsp;&nbsp;&nbsp;&nbsp;<u>${escapeHtml(basics.caseNumber)}&nbsp;&nbsp;&nbsp;&nbsp;채무자&nbsp;${escapeHtml(basics.applicantName)}${basics.residentNumber ? `  ( ${escapeHtml(basics.residentNumber)} )` : ''}</u></span></p>
    <p align='center' style='padding-top:10px;'><b><span class='text09_b'>변 제 계 획 (안)</span></b></p>
    <div style='text-align:right;'><span class='text02' style='line-height:22px;'>${basics.filingDate}&nbsp;작성</span></div>
    <span class='text02' style='line-height:22px;'>1. 변제기간</span><br>
    <span class='text02' style='line-height:22px;'>&nbsp;&nbsp;&nbsp;&nbsp;
      [ ${escapeHtml(firstDate.year || '')} ]년
      [ ${escapeHtml(firstDate.month || '')} ]월
      [ ${escapeHtml(firstDate.day || '')} ]일부터
      [ ${escapeHtml(endDate.year || '')} ]년
      [ ${escapeHtml(endDate.month || '')} ]월
      [ ${escapeHtml(endDate.day || '')} ]일까지
      [ ${escapeHtml(String(repaymentMonths || ''))} ]개월간
    </span>
    <span class='text02' style='line-height:22px;'><br><br></span>
    <span class='text02' style='line-height:35px;'>2. 변제에 제공되는 소득 또는 재산<br></span>
    <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;가. 소득</span><br>
    <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;(1) 수입 &nbsp;</span><br>
    <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${salaryCheck}  변제기간 동안 [ ${htmlOrBlank(employment.workplaceName)} ] 에서 받는 월 평균수입 [ ${formatNumberText(averageIncome)} ]원&nbsp;</span><br>
    <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${businessCheck}  변제기간 동안 [ ${isSalary ? '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' : htmlOrBlank(employment.workplaceName)} ] 를 운영하여 얻는 월 평균 수입 [ ${isSalary ? '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' : formatNumberText(averageIncome)} ]원&nbsp;</span><br>
    <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;(2) 채무자 및 피부양자의 생활에 필요한 생계비</span><br>
    <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(가) 채무자 및 피부양자 : &nbsp;총 [ ${displayHouseholdSize} ]명&nbsp;</span><br>
    <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(나) 국민기초생활보장법 제6조의 규정에 따라 공표된 기준중위소득 : 월 [ ${formatNumberText(medianIncome)} ]원&nbsp;</span><br>
    <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(다) 채무자회생및파산에관한법률 제579조에 따라 조정된 생계비 : 월 [ ${formatNumberText(adjustedExpenseAmount)} ]원 &nbsp;</span><br>
    <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;(3) 채무자의 가용소득</span><br>
    <span class='text02' style='line-height:35px;word-spacing:-1;'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
      [ ${escapeHtml(firstDate.year || '')} ]년
      [ ${escapeHtml(firstDate.month || '')} ]월
      [ ${escapeHtml(firstDate.day || '')} ]일부터
      [ ${escapeHtml(endDate.year || '')} ]년
      [ ${escapeHtml(endDate.month || '')} ]월
      [ ${escapeHtml(endDate.day || '')} ]일까지
      [ ${escapeHtml(String(repaymentMonths || ''))} ]개월간
    </span><br>
    <table cellpadding='0' cellspacing='0' border='0' width='100%' align='center'>
      <tr><td style='width:6%;'></td><td border='0'>
        <table cellpadding='0' cellspacing='0' class='docxline' width='100%'>
          <tr>
            <td class='border_o' width='113' height='49'><p align='center'><span class='text01' style='line-height:24px;'>① 월평균수입</span></p></td>
            <td class='border_l' width='113' height='49'><p align='center'><span class='text01' style='line-height:24px;'>② 월평균생계비</span></p></td>
            <td class='border_l' width='113' height='49'><p align='center'><span class='text01' style='line-height:24px;'>③월평균가용소득(①-②)<br></span></p></td>
            <td class='border_l' width='105' height='49'><p align='center'><span class='text01' style='line-height:20px;word-spacing: -5px;'>④월회생위원보수</span></p></td>
            <td class='border_l' width='105' height='49'><p align='center'><span class='text01' style='line-height:24px;'>⑤월실제가용소득(③-④)</span></p></td>
            <td class='border_l' width='105' height='49'><p align='center'><span class='text01' style='line-height:24px;'>⑥변제횟수(월)</span></p></td>
            <td class='border_l' width='117' height='49'><p align='center'><span class='text01' style='line-height:24px;'>⑦총실제가용소득(⑤X⑥)</span></p></td>
          </tr>
          <tr>
            <td class='border_t' width='143' height='44'><p align='right'><span class='text01' style='line-height:22px;padding-right:2px;'>${formatNumberText(averageIncome)}원</span></p></td>
            <td class='border_tl' width='143' height='44'><p align='right'><span class='text01' style='line-height:22px;padding-right:2px;'>${formatNumberText(adjustedExpenseAmount)}원</span></p></td>
            <td class='border_tl' width='143' height='44'><p align='right'><span class='text01' style='line-height:22px;padding-right:2px;'>${formatNumberText(Math.max(availableIncome, 0))}원</span></p></td>
            <td class='border_tl' width='143' height='44'><p align='right'><span class='text01' style='line-height:20px;padding-right:2px;'>${formatNumberText(monthlyCommissionerFee)}원</span></p></td>
            <td class='border_tl' width='143' height='44'><p align='right'><span class='text01' style='line-height:22px;padding-right:2px;'>${formatNumberText(actualAvailableIncome)}원</span></p></td>
            <td class='border_tl' width='129' height='44'><p align='right'><span class='text01' style='line-height:22px;padding-right:2px;'>${repaymentMonths}회</span></p></td>
            <td class='border_tl' width='157' height='44'><p align='right'><span class='text01' style='line-height:22px;padding-right:2px;'>${formatNumberText(totalActualAvailableIncome)}원</span></p></td>
          </tr>
        </table>
      </td></tr>
    </table><br>
    <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;나. 재산 :</span>
    <span class='text02' style='line-height:35px;'><u>[ 해당 있음 ${hasAssets ? "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'>" : "<span class='text04'>□</span>"} &nbsp;/ 해당 없음 ${hasAssets ? "<span class='text04'>□</span>" : "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'>"} ]</u></span><br><br>
    <span class='text02' style='line-height:22px;'>3. 개인회생재단채권에 대한 변제</span>
    <u><span class='text02' style='line-height:22px;'>[ 해당 있음 ${page4?.hasEstateClaim ? "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'>" : "<span class='text04'>□</span>"} / 해당 없음 &nbsp;${page4?.hasEstateClaim ? "<span class='text04'>□</span>" : "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'>"} ]</span></u><br><br>
    <span class='text02' style='line-height:25px;'>&nbsp;가. 회생위원의 보수 및 비용 <u><span class='text02' style='line-height:25px;'>[ 있음 ${employment.hasExternalCommissioner ? "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'>" : "<span class='text04'>□</span>"} / 없음 ${employment.hasExternalCommissioner ? "<span class='text04'>□</span>" : "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'>"} ]</span></u></span><br><br>
    <span class='text02' style='line-height:25px;'>&nbsp;나. 기타 개인회생재단채권<u><span class='text02' style='line-height:25px;'>[ 있음 ${hasEstatePriorityClaims ? "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'>" : "<span class='text04'>□</span>"} / 없음 ${hasEstatePriorityClaims ? "<span class='text04'>□</span>" : "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'>"} ]</span></u></span><br>
    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'><br><br><br>
    <span class='text02' style='line-height:22px;'>4. 일반의 우선권 있는 개인회생채권에 대한 변제</span>
    <u><span class='text02' style='line-height:22px;'>[ 해당 있음 ${hasPriorityClaims ? "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'>" : "<span class='text04'>□</span>"} &nbsp;/ 해당 없음 ${hasPriorityClaims ? "<span class='text04'>□</span>" : "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'>"} ]</span></u>
    <br>
    <table cellpadding='0' cellspacing='0' border='0' width='100%' style='padding-left:30px;padding-top:15px;'><tr><td style='padding-bottom:10px;line-height:22px;'><span class='text02'>(1) 채권의 내용</span></td><td style='padding-bottom:10px;line-height:22px;text-align:right;'><span class='text02'>(단위 : 원)</span></td></tr></table>
    <table cellpadding='0' cellspacing='0' width='100%' style='padding-left:30px;' class='docxline'>
      <tr>
        <td class='border_o' width='143' style='height:43.5px;'><p align='center'><span class='text02' style='line-height:22px;'>채 권 자</span></p></td>
        <td class='border_l' width='103' style='height:43.5px;'><p align='center'><span class='text02' style='line-height:22px;'>채권현재액</span></p></td>
        <td class='border_l' width='350' style='height:43.5px;'><p align='center'><span class='text02' style='line-height:22px;'>채권발생원인(우선권의 근거)</span></p></td>
        <td class='border_l' width='120' style='height:43.5px;'><p align='center'><span class='text02' style='line-height:22px;'>변 제 기</span></p></td>
      </tr>
      ${priorityTableBody}
    </table>
    <br>
    <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;(2) 변제방법</span><br>
    <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;&nbsp;변제계획 인가일 직후 최초 도래하는 변제기일에 원리금 전액을 우선하여 변제한다. <br>
    &nbsp;&nbsp;&nbsp;&nbsp;남은 채권이 있을 경우에는 일반 개인회생채권의 매 변제기일에 우선하여 변제한다.</span>
    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'><br><br><br>
    <span class='text02' style='line-height:30px;'>5. 별제권부 채권 및 이에 준하는 채권의 처리 <u>[ 해당 있음 ${hasSecuredClaims ? "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='16' height='16'>" : "<span class='text04'>□</span>"} &nbsp;/ 해당 없음 ${hasSecuredClaims ? "<span class='text04'>□</span>" : "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='16' height='16'>"} ]</u></span>
    <br>
    <span class='text02' style='line-height:30px;'>&nbsp;&nbsp;가. 채권의 내용</span><br>
    <table cellpadding='0' cellspacing='0' border='0' width='100%' align='center'>
      <tr>
        <td width='30'></td>
        <td valign='top'>
          <table cellpadding='0' cellspacing='0' class='docxline' width='100%'>
            <tr bgcolor='#ffffff'>
              <td class='border_o' rowspan='3' width='46'><p align='center'><span class='text01' style='line-height:24px;'>채권<br>번호</span></p></td>
              <td class='border_l' rowspan='3' width='103'><p align='center'><span class='text01' style='line-height:24px;'>채 &nbsp;&nbsp;권&nbsp;&nbsp; 자</span></p></td>
              <td class='border_l' width='178' style='height:43.5px;'><p align='center'><span class='text01' style='line-height:24px;'>① 채권현재액(원금)</span></p></td>
              <td class='border_l' rowspan='2' width='192'><p align='justify'><span class='text01' style='line-height:24px;'>&nbsp;③ 별제권행사등으로 <br>&nbsp; &nbsp;변제가예상되는 채권액</span></p></td>
              <td class='border_l' rowspan='2' width='192'><p align='justify'><span class='text01' style='line-height:24px;'>&nbsp;④ 별제권행사 등으로도 <br>&nbsp; &nbsp;변제받을수없을 채권액</span></p></td>
            </tr>
            <tr bgcolor='#ffffff'>
              <td class='border_tl' width='178' style='height:43.5px;'><p align='center'><span class='text01' style='line-height:24px;'>② 채권현재액(이자)</span></p></td>
            </tr>
            <tr bgcolor='#ffffff'>
              <td class='border_tl' colspan='3' width='562' style='height:43.5px;'><p align='left'><span class='text01' style='line-height:24px;'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;⑤ 별제권 등의 내용 및 목적물</span></p></td>
            </tr>
            ${Array.from({ length: 5 }, () => `
              <tr bgcolor='#ffffff'>
                <td class='border_t' rowspan='3' width='46'>&nbsp;</td>
                <td class='border_tl' rowspan='3' width='103'>&nbsp;</td>
                <td class='border_tl' width='178' style='height:43.5px;'>&nbsp;</td>
                <td class='border_tl' rowspan='2' width='192'>&nbsp;</td>
                <td class='border_tl' rowspan='2' width='192'>&nbsp;</td>
              </tr>
              <tr bgcolor='#ffffff'>
                <td class='border_tl' width='178' style='height:43.5px;'>&nbsp;</td>
              </tr>
              <tr bgcolor='#ffffff'>
                <td class='border_tl' colspan='3' width='562' height='67'>&nbsp;</td>
              </tr>
            `).join('')}
          </table>
        </td>
      </tr>
    </table>
    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'><br><br><br>
    <p align='justify'><span class='text02' style='line-height:25px; padding-left:16px;'>나. 변제방법</span><br></p>
    <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr valign='top'><td width='37'></td><td width='25'><span class='text02' style='line-height:35px;'>(1)</span></td><td><span class='text02' style='line-height:35px;'>위 각 채권에 대하여 별제권 행사 등으로 변제가예상되는 채권액(③)은 별제권 행사 등에 의한 방법으로 변제하고 이 변제계획상의 가용소득이나 재산처분에 의한 변제대상에서 제외한다.</span></td></tr>
      <tr valign='top'><td></td><td><span class='text02' style='line-height:35px;'>(2)</span></td><td><span class='text02' style='line-height:35px;'>위 (1)항 기재 각 채권 중 별제권행사 등으로도 변제받을수없을 채권액(④)은 미확정채권으로 보아 유보하였다가 아래 7항 기재와 같은 방법으로 변제한다.</span></td></tr>
      <tr valign='top'><td></td><td><span class='text02' style='line-height:35px;'>(3)</span></td><td><span class='text02' style='line-height:35px;'>별제권 행사 등으로도 변제받을수없을 채권액이 위 가의 ④항 기재 금액을 초과하는 것으로 확정된 경우에는 채권자가 그 초과부분을 변제계획안의 변경 절차를 통하여 변제받을 수 있다.</span></td></tr>
    </table>
    <br>
    <span class='text02' style='line-height:35px;'>6. 일반 개인회생채권에 대한 변제</span><br>
    <span class='text02' style='line-height:25px; padding-left:16px;'>가. 가용소득에 의한 변제</span><br>
    <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr><td width='35' valign='top'></td><td width='23' valign='top'><span class='text02' style='line-height:35px;'>(1)&nbsp;</span></td><td colspan='3' valign='top'><span class='text02' style='line-height:35px;'>월 변제예정(유보)액 및 총 변제예정(유보)액의 산정</span></td></tr>
      <tr><td width='35' valign='top'></td><td valign='top'><span class='text02' style='line-height:35px;'>&nbsp;&nbsp;</span></td><td colspan='3' valign='top'><span class='text02' style='line-height:35px;'>각 일반 개인회생채권의 [ ${page4?.repaymentPlanType === 'B' ? '원리금' : '원금'} ]의 액수를 기준으로 월 평균가용소득에서 월회생위원보수를 차감한 월실제가용소득을 안분하여 산출한 금액을 각 일반 개인회생채권자에게 변제한다. 이를 기초로 산정한 월 변제예정(유보)액은 <u>제1회부터 제${repaymentMonths}회까지는 [ ${formatNumberText(actualAvailableIncome)} ]원씩이고 총 변제예정(유보)액은 [ ${formatNumberText(totalActualAvailableIncome)} ]원이다.</u></span></td></tr>
      <tr heigt='10'><td>&nbsp;</td></tr>
      <tr><td width='35' valign='top'></td><td><span class='text02' valign='top'>(2)&nbsp;</span></td><td colspan='3' valign='top'><span class='text02' style='line-height:35px;'>변제방법</span></td></tr>
      <tr><td width='35' valign='top'></td><td valign='top'><span class='text02' style='line-height:35px;'>&nbsp;&nbsp;</span></td><td colspan='3' valign='top'><span class='text02' style='line-height:35px;'>위 (1)항의 변제예정(유보)액은 다음과 같이 분할하여 변제한다.</span></td></tr>
      <tr><td width='35' valign='top'></td><td valign='top'><span class='text02' style='line-height:35px;'>&nbsp;&nbsp;</span></td><td valign='top'><span class='text02' style='line-height:35px;'>(가)</span></td><td colspan='2' valign='top'><span class='text02' style='line-height:35px;'>기간 및 횟수</span></td></tr>
      <tr><td width='35' valign='top'></td><td valign='top'><span class='text02' style='line-height:35px;'>&nbsp;&nbsp;</span></td><td valign='top'><span class='text02' style='line-height:35px;'>&nbsp;</span></td><td colspan='2' valign='top'><span class='text02' style='line-height:25px;'>
        [ ${escapeHtml(firstDate.year || '')} ]년
        [ ${escapeHtml(firstDate.month || '')} ]월
        [ ${escapeHtml(firstDate.day || '')} ]일부터
        [ ${escapeHtml(endDate.year || '')} ]년
        [ ${escapeHtml(endDate.month || '')} ]월
        [ ${escapeHtml(endDate.day || '')} ]일까지
        [ ${escapeHtml(String(repaymentMonths || ''))} ]개월간 합계 [ ${escapeHtml(String(repaymentMonths || ''))} ]회
      </span></td></tr>
      <tr><td width='35' valign='top'></td><td valign='top'><span class='text02' style='line-height:35px;'>&nbsp;&nbsp;</span></td><td valign='top'><span class='text02' style='line-height:35px;'>(나)</span></td><td colspan='2' valign='top'><span class='text02' style='line-height:35px;'>변제월 및 변제일</span></td></tr>
      <tr><td width='35' valign='top'></td><td valign='top'><span class='text02' style='line-height:35px;'>&nbsp;&nbsp;</span></td><td valign='top'><span class='text02' style='line-height:35px;'> &nbsp;</span></td><td colspan='2' valign='top'><span class='text02' style='line-height:35px;'>
        ① [ ${escapeHtml(firstDate.year || '')} ] 년 [ ${escapeHtml(firstDate.month || '')} ]월 [ ${escapeHtml(firstDate.day || '')} ]일부터 변제계획인가일 직전 [ ${escapeHtml(firstDate.day || '')} ]일까지 기간
      </span><br>
      &nbsp;&nbsp;&nbsp;<span class='text02' style='line-height:35px;'><img src='/images/restoration/check.gif' border='0' align='absmiddle' width='16' height='16'> 변제계획인가일 직후 최초 도래하는 월의 [ ${escapeHtml(paymentDay)} ]일에 위 기간 동안의 변제분을 개인회생절차 개시후 변제계획 인가 전에 적립된 가용소득으로 일시에 조기 변제</span><br>
      &nbsp;&nbsp;&nbsp;<span class='text02' style='line-height:25px;'><span class='text04'>□</span> 기타 : [ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ]</span><br>
      <span class='text02' style='line-height:35px;'>② 변제계획인가일 직후 최초 도래하는 월의 [ ${escapeHtml(paymentDay)} ]일부터 [ ${escapeHtml(endDate.year || '')} ]년 [ ${escapeHtml(endDate.month || '')} ]월 [ ${escapeHtml(endDate.day || '')} ]일까지 기간</span><br>
      <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='16' height='16'> 매월마다 [ ${escapeHtml(paymentDay)} ]일에 변제</span><br>
      <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;<span class='text04'>□</span> 매 [ &nbsp;&nbsp; ]개월마다 [ &nbsp;&nbsp; ]일에 각 변제</span><br>
      <span class='text02' style='line-height:35px;'>&nbsp;&nbsp;&nbsp;<span class='text04'>□</span> 기타 : [ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ]</span>
      </td></tr>
    </table>
    <br>
    <span class='text02' style='line-height:25px; padding-left:16px;'>나. 재산의 처분에 의한 변제</span><u><span class='text02' style='line-height:35px;'>[ 해당 있음 ${hasAssets ? "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'>" : "<span class='text04'>□</span>"} &nbsp;/ 해당 없음 ${hasAssets ? "<span class='text04'>□</span>" : "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'>"} ]</span></u>
    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'><br><br><br>
    <span class='text02' style='line-height:32px;'>7. 미확정 개인회생채권에 대한 조치</span>
    <u><span class='text02' style='line-height:32px;'>[ 해당 있음 ${hasUnconfirmedClaims ? "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='16' height='16'>" : "<span class='text04'>□</span>"} &nbsp;/ 해당 없음 ${hasUnconfirmedClaims ? "<span class='text04'>□</span>" : "<img src='/images/restoration/check.gif' border='0' align='absmiddle' width='16' height='16'>"} ]</span></u><br>
    <span class='text02' style='line-height:23px; padding-left:17px;'>가. 변제금액의 유보</span><br>
    <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr><td width='35'></td><td width='23' valign='top'><span class='text02' style='line-height:32px;'>(1)</span></td><td colspan='2'><span class='text02' style='line-height:32px;'>미확정 개인회생채권에 대하여는 변제를 유보하고, 별지 개인회생채권 변제예정액표에 기재한 금액을 당해 채권이 확정될 때까지 유보하여 둔다.</span></td></tr>
      <tr><td colspan='4' style='height:12px;'></td></tr>
      <tr><td width='35'></td><td valign='top'><span class='text02' style='line-height:32px;'>(2)</span></td><td colspan='2'><span class='text02' style='line-height:32px;'>채무자는 위와 같이 유보한 금액도, 즉시 지급되는 다른 채권에 대한 변제금과 마찬가지로 아래 8항 기재 계좌에 입금한다.</span></td></tr>
    </table>
    <span class='text02' style='line-height:23px; padding-left:17px;'>나. 미확정 개인회생채권에 대한 변제</span><br>
    <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr><td width='35'></td><td width='23' valign='top'><span class='text02' style='line-height:32px;'>(1)</span></td><td colspan='2'><span class='text02' style='line-height:32px;'>미확정 개인회생채권이 전부 그대로 확정된 경우<br>미확정 개인회생채권의 전액에 관하여 채권의 존재가 확정된 경우에는, 그 확정 직후 유보비율을 변제비율로 적용하여 변제를 개시하고 매월의 변제기에 그 해당금액을 변제하되, 이미 분할 변제기가 도래한 부분 즉 그 동안의 유보액에 대하여는 곧바로 일시 변제한다.</span></td></tr>
      <tr><td colspan='4' style='height:12px;'></td></tr>
      <tr><td width='35'></td><td valign='top'><span class='text02' style='line-height:32px;'>(2)</span></td><td colspan='2'><span class='text02' style='line-height:32px;'>미확정 개인회생채권이 전부 또는 일부 부존재 하는 것으로 확정된 경우<br>미확정 개인회생채권이 전부 또는 일부 부존재하는 것으로 확정된 경우에는, 그 확정 직후, 존재하는 것으로 확정된 [ ${page4?.repaymentPlanType === 'B' ? '원리금' : '원금'} ]의 인용 비율에 위 가항에 의하여 지급을 유보한 금액을 곱하여 산출된 금액을 당해 개인회생채권자에게 일시에 변제한다. 유보금액 중, 미확정 개인회생채권의 일부가 존재하지 않는 것으로 됨에 따라 그 개인회생채권자에게 변제할 필요가 없게 된 나머지 유보금액은, 그 채권액 확정 직후 일반 개인회생채권자들에게 각 [ ${page4?.repaymentPlanType === 'B' ? '원리금' : '원금'} ]의 액수를 기준으로 안분하여 변제한다.</span><br><span class='text02' style='line-height:32px;'>향후의 매월 입금액을 분배하는 기준이 될 변제비율은 위 확정 ${page4?.repaymentPlanType === 'B' ? '원리금' : '원금'}들 사이의 비율에 따라 새로 계산하여 정하는데, 미확정 개인회생채권의 일부가 존재하지 않는 것으로 확정됨에 따라 향후 당해 개인회생채권자를 위한 유보가 불필요하게 된 변제기 미도래분에 대한 변제 유보예정액은, 향후 변제기 도래시 일반 개인회생채권자들에게 그 각 [ ${page4?.repaymentPlanType === 'B' ? '원리금' : '원금'} ]의 액수를 기준으로 안분되도록 한다.</span></td></tr>
      <tr><td colspan='4' style='height:12px;'></td></tr>
      <tr><td width='35'></td><td valign='top'><span class='text02' style='line-height:32px;'>(3)</span></td><td colspan='2'><span class='text02' style='line-height:32px;'>변제기간 종료시까지 미확정 개인회생채권이 미확정상태로 남는 경우에는 최종변제기에 유보한 금액 전부를 일반개인회생채권자들에게 각 [ ${page4?.repaymentPlanType === 'B' ? '원리금' : '원금'} ]의 액수를 기준으로 안분하여 변제한다.</span></td></tr>
      <tr><td colspan='4' style='height:12px;'></td></tr>
      <tr><td width='35'></td><td valign='top'><span class='text02' style='line-height:32px;'>(4)</span></td><td colspan='2'><span class='text02' style='line-height:32px;'>임대차보증금반환액수가 확정되지 않은 임대차보증금 반환채권은 미확정채권으로 보아 위 가, 나항에 따라 변제하되 그 액수가 확정되고 임차인이 임차목적물을 명도함과 동시에 변제한다.</span></td></tr>
    </table>
    <br>
    <span class='text02' style='line-height:32px;'>8. 변제금원의 회생위원에 대한 임치 및 지급</span>
    <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center'><tr><td width='15'></td><td><span class='text02' style='line-height:32px;'>채무자는 위 [ 6 ]항에 의하여 개인회생채권자들에게 변제하여야 할 금액을 개시결정시 통지되는 개인회생위원의 예금계좌 { [ ${escapeHtml(context.pages['1']?.bankName || '')} ] 은행 계좌번호 [ ${escapeHtml(context.pages['1']?.accountNumber || '')} ] }에 순차 임치하고, 회생위원은 이를 즉시 개인회생채권자들이 신고한 예금 계좌에 송금하는 방법으로 지급한다. 계좌번호를 신고하지 않은 개인회생채권자에 대하여는 변제액을 적립하였다가 이를 연 1회 개인회생사건이 계속되어 있는 지방법원에 공탁하여 지급할 수 있다.</span></td></tr></table>
    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'><br><br><br>
    <span class='text02' style='line-height:35px;'>9. 면책의 범위 및 효력발생시기</span>
    <table border='0' cellpadding='0' cellspacing='0' width='100%' align='center'><tr><td width='15'></td><td><span class='text02' style='line-height:35px;'>채무자가 개인회생채권에 대하여 이 변제계획에 따라 변제를 완료하고 면책신청을 하여 면책결정이 확정되었을 경우에는, 이 변제계획에 따라 변제한 것을 제외하고 개인회생채권자에 대한 채무에 관하여 그 책임이 면제된다. 단, 채무자 회생 및 파산에 관한 법률 제625조 제2항 단서 각호 소정의 채무에 관하여는 그러하지 아니하다.</span></td></tr></table>
    <br><br>
    <span class='text02' style='line-height:35px;'>10. 기타사항&nbsp;[ 해당 있음 <span class='text04'>□</span> &nbsp;/ 해당 없음 <img src='/images/restoration/check.gif' border='0' align='absmiddle' width='18' height='18'> ]</span><br>
    <table cellpadding='0' cellspacing='0' width='100%' height='650' align='center'>
      <tr><td colspan='2' style='height:12px;'></td></tr>
      <tr><td width='15'></td><td style='border:1px solid #000000;padding:10px;vertical-align:top;' valign='top'><table border='0' cellpadding='0' cellspacing='0' width='100%' style='vertical-align:top;'><tr><td valign='top' style='padding-left:70px;'><span style='line-height:25px;'><br><br><br><span class='text02'>해당사항 없습니다.</span></span></td></tr></table></td></tr>
    </table>
    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'><br><br><br>
    <p align='center'><b><span class='text10_b'>개인회생채권 변제예정액표</span></b><span style='span-size:25px;'>&nbsp;&nbsp;&nbsp;</span></p>
    <br><br><br>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr><td colspan='2'><span class='text03_b'>1. 기초사항</span></td></tr>
      <tr><td colspan='2' align='right' style='padding-right:10px;'><span class='text02'>(단위 : 원)</span></td></tr>
      <tr><td colspan='2' style='height:12px;'></td></tr>
      <tr>
        <td colspan='2' class='text02' style='padding-left:10px;padding-bottom:5px;padding-top:5px;word-spacing:1px;'>
          <table width='100%' cellpadding='0' cellspacing='0'>
            <tr bgcolor='#ffffff'>
              <td width='198' style='height:33.5px;'>
                <table width='100%' cellpadding='0' cellspacing='0' height='100%' style='border-collapse:collapse' border='1' bordercolor='#000000' class='docxline'>
                  <tr bgcolor='#ffffff'><td width='90' style='height:33.5px;'><span class='text01'>③ 월평균가용<br>&nbsp;&nbsp;&nbsp;소득</span></td><td width='108' style='height:33.5px;' align='center'><span class='text02'>${formatNumberText(Math.max(availableIncome, 0))}</span></td></tr>
                </table>
              </td>
              <td width='9' style='height:33.5px;'></td>
              <td width='480' style='height:33.5px;'>
                <table width='100%' cellpadding='0' cellspacing='0' height='100%' style='border-collapse:collapse' border='1' bordercolor='#000000' class='docxline'>
                  <tr bgcolor='#ffffff'><td width='89' class='border_o' style='padding-left:2px;letter-spacing:-1.8px;'><span class='text01'>④ 월회생위원<br>&nbsp;&nbsp;&nbsp;보수</span></td><td width='381' style='height:33.5px;' align='left'><span class='text02'>${formatNumberText(monthlyCommissionerFee)}</span></td></tr>
                </table>
              </td>
            </tr>
          </table>
          <table><tr><td colspan='2' height='2'></td></tr></table>
          <table cellpadding='0' cellspacing='0' border='0' width='100%'>
            <tr bgcolor='#ffffff'>
              <td width='195' height='85'>
                <table width='100%' cellpadding='0' cellspacing='0' height='100%' style='border-collapse:collapse' border='1' bordercolor='#000000' class='docxline'>
                  <tr bgcolor='#ffffff'><td width='89' height='85' style='padding-left:2px;'><span class='text01'>(A) 월실제<br>&nbsp;&nbsp;&nbsp;가용소득<br>&nbsp;&nbsp;&nbsp;(③ - ④)</span></td><td width='106' height='85' align='center'><span class='text02'>${formatNumberText(actualAvailableIncome)}</span></td></tr>
                </table>
              </td>
              <td width='9' height='85'></td>
              <td width='250' height='85'>
                <table width='100%' cellpadding='0' cellspacing='0' height='100%' style='border-collapse:collapse' border='1' bordercolor='#000000' class='docxline'>
                  <tr bgcolor='#ffffff'><td width='90' height='85' style='padding-left:2px;'><span class='text01'>(B)변제횟수</span></td><td width='110' height='85' align='center'><span class='text01'>${repaymentMonths}</span></td><td width='50' height='85' align='center'><span class='text01'>회</span></td></tr>
                </table>
              </td>
              <td width='9' height='85'></td>
              <td width='210' height='85'>
                <table width='100%' cellpadding='0' cellspacing='0' height='100%' style='border-collapse:collapse' border='1' bordercolor='#000000' class='docxline'>
                  <tr bgcolor='#ffffff'><td width='90' height='85' style='padding-left:2px;'><span class='text01'>(C)총가용소득</span></td><td width='120' height='85'><p align='center'><span class='text02'>${formatNumberText(totalActualAvailableIncome)}</span></p></td></tr>
                </table>
              </td>
            </tr>
          </table>
        </td>
      </tr>
      <tr><td colspan='2'><br><br><br><p align='justify'><span class='text03_b'>2. 채권자별 변제예정액의 산정내역</span><span class='text02'><br></span><span class='text02'><br></span><span class='text02'>&nbsp;&nbsp;&nbsp;&nbsp;별지기재와 같음</span><br><br><br></p><br><br></td></tr>
      <tr><td colspan='2'><span class='text03_b'>3. 변제율 </span><br><br><span class='text02'>&nbsp;&nbsp;&nbsp;&nbsp;${page4?.repaymentPlanType === 'B' ? '원리금' : '원금'}의 [ ${repaymentRate ? repaymentRate.toFixed(2) : '0.00'} ]% 상당액 </span><br><br></td></tr>
      <tr><td colspan='2'><table cellpadding='0' cellspacing='0' width='100%'><tr><td><span class='text03_b'>4. 청산가치와의 비교</span></td></tr><tr><td align='right' style='padding-right:10px;'><span class='text02'>(단위 : 원)</span></td></tr><tr><td style='height:12px;'></td></tr></table></td></tr>
      <tr><td colspan='2'><table cellpadding='0' cellspacing='0' width='100%'><tr><td width='20'></td><td><table bgcolor='#000000' cellpadding='0' cellspacing='0' width='100%'><tr bgcolor='#ffffff'><td rowspan='2' width='100' height='104' class='border_o'><p align='center'><span class='text02'>(J) 청산가치</span></p></td><td rowspan='2' width='160' height='104' class='border_l'><p align='center'><span class='text02'>0</span></p></td><td width='30' height='53'></td><td width='220' height='53' class='border_o' style='padding-left:5px;'><p align='left'><span class='text02'>(K) 가용소득에 의한<br>&nbsp;&nbsp;총변제예정(유보)액</span></p></td><td width='240' height='53' class='border_l'><p align='center'><span class='text02'>${formatNumberText(totalActualAvailableIncome)}</span></p></td></tr><tr bgcolor='#ffffff'><td style='height:60px;'></td><td style='height:60px;' class='border_t' style='padding-left:5px;'><p align='justify'><span class='text02'>&nbsp;</span></p></td><td style='height:60px;' class='border_tl'><p align='center'><span class='text02'>${totalActualAvailableIncome >= 0 ? '청산가치 이상' : '&nbsp;'}</span></p></td></tr></table></td></tr></table></td></tr>
    </table>
  `
}

const buildProhibitionReferenceBody = (context: RecoveryDocumentContext) => {
  const basics = getContextBasics(context)
  const page1 = context.pages['1'] || {}
  const page16 = context.pages['16'] || {}
  const employment = getEmploymentDataFromContext(context)

  const caseYear = String(page1?.caseYear || basics.filingDateParts.year || '').trim()
  const caseNumberOnly = String(page1?.caseNumber || '').trim()
  const caseLabel = caseYear && caseNumberOnly
    ? buildRecoveryCaseNumber(caseYear, caseNumberOnly)
    : String(basics.caseNumber || '').trim()
  const applicantAddress = basics.applicantAddress || context.caseInfo?.applicant_address || ''
  const employmentType = String(employment.employmentType || '').trim()
  const workplaceName = String(employment.workplaceName || '').trim()
  const workplaceAddress = String(
    [employment.companyZipCode, employment.companyAddress].filter(Boolean).join(' ') ||
    employment.companyAddress ||
    ''
  ).trim()
  const representativeName = String(
    employment.companyRepresentative ||
    employment.ceoName ||
    employment.representativeName ||
    ''
  ).trim()
  const incomeSourceLabel = employmentType === '자영' ? '영업수입' : '급여'
  const incomeSourceTitle = employmentType === '자영' ? '&lt;영업장&gt;' : '&lt;급여 등&gt;'
  const filingDateText = formatHtmlDottedDateFromParts(
    basics.filingDateParts.year,
    basics.filingDateParts.month,
    basics.filingDateParts.day
  )

  return `
    <br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr>
        <td align='right'>
          <table border='1' cellpadding='2' cellspacing='0' bordercolordark='white' bordercolorlight='#000000' width='95' height='80'>
            <tr>
              <td align='center'><span class='text01' style='line-height:25px'>인&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;지 <br>2,000원</span></td>
            </tr>
          </table>
        </td>
      </tr>
    </table><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr>
        <td align='center'><span class='text11_b' style='font-size:20pt;font-weight:bold;color:#000000;line-height:30px;font-family:Batang, BatangChe, Nanum Myeongjo, serif;text-decoration:none;'>금&nbsp;&nbsp;지&nbsp;&nbsp;명&nbsp;&nbsp;령&nbsp;&nbsp;신&nbsp;&nbsp;청&nbsp;&nbsp;서</span></td>
      </tr>
    </table>
    <br><br><br>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr bgcolor='#ffffff' style='height:49.5px;'>
        <td width='140' class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>사&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;건</span></td>
        <td class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>${escapeHtml(caseLabel)} 호&nbsp;&nbsp;&nbsp;개인회생</span></td>
      </tr>
      <tr bgcolor='#ffffff' style='height:49.5px;'>
        <td width='140' class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>신청인 (채무자)</span></td>
        <td class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>${escapeHtml(basics.applicantName)}</span></td>
      </tr>
      <tr bgcolor='#ffffff' style='height:49.5px;'>
        <td width='140'></td>
        <td class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>${htmlOrBlank(applicantAddress)}</span></td>
      </tr>
    </table>
    <br><br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr>
        <td align='center'><span class='text11_b' style='font-size:20pt;font-weight:bold;color:#000000;line-height:30px;font-family:Batang, BatangChe, Nanum Myeongjo, serif;text-decoration:none;'>신&nbsp;청&nbsp;취&nbsp;지</span></td>
      </tr>
    </table>
    <br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr style='height:43.5px;'>
        <td align='left'>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
            <tr valign='top'>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='20' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td align='left'><span class='text03' style='line-height:40px'>신청인(채무자)에 대한 이 법원 ${escapeHtml(caseLabel)} 호 개인회생사건에 관하여 개인회생절차의 개시신청에 대한 개시결정이 있을 때까지 신청인에 대하여 그 결정 전의 원인으로 생긴 재산상의 청구권에 기하여 향후 개인회생재단에 속하게 될 소득 및 재산에 대하여 하는 강제집행 · 가압류 또는 가처분행위를 각 금지한다.<br>라는 결정을 구합니다. </span></td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    <br><br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr>
        <td align='center'><span class='text11_b' style='font-size:20pt;font-weight:bold;color:#000000;line-height:30px;font-family:Batang, BatangChe, Nanum Myeongjo, serif;text-decoration:none;'>신&nbsp;청&nbsp;이&nbsp;유</span></td>
      </tr>
    </table>
    <br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr style='height:43.5px;'>
        <td align='left'>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
            <tr>
              <td width='10' valign='top'><span class='text03' style='line-height:30px'>&nbsp;</span></td>
              <td width='20' valign='top'><span class='text03' style='line-height:30px'>1.&nbsp;</span></td>
              <td align='left'><span class='text03' style='line-height:30px'>신청인(채무자)은 귀원 ${escapeHtml(caseLabel)}호 사건의 채무자입니다.</span></td>
            </tr>
          </table>
          <br>
        </td>
      </tr>
      <tr style='height:43.5px;'>
        <td align='left'>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
            <tr valign='top'>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='20' valign='top'><span class='text03' style='line-height:40px'>2.&nbsp;</span></td>
              <td align='left'><span class='text03' style='line-height:40px'>신청인이 위 개인회생 사건에서 채무자(신청인)가 매월 ${employmentType === '자영' ? '영업장으로부터 얻는' : '사용자(아래기재)로부터 지급 받는'} ${incomeSourceLabel}에서 기준중위소득를 제외한 나머지 가용소득으로 채무를 변제하는 계획안을 제출하였습니다.</span></td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'><br><br><br><br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr style='height:43.5px;'>
        <td align='left'>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
            <tr valign='top'>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='20' valign='top'><span class='text03' style='line-height:40px'>3.&nbsp;</span></td>
              <td align='left'><span class='text03' style='line-height:40px'>그런데 신청인의 채권자들은 신청인의 향후 소득 및 재산에 대하여 강제집행·가압류 또는 가처분 행위, 채권을 변제받거나 변제를 요구하는 행위를 하려 하고 있습니다.</span></td>
            </tr>
          </table>
        </td>
      </tr>
      <tr style='height:43.5px;'>
        <td align='left' style='padding-top:20px;'><span class='text03'>
          <table cellpadding='0' cellspacing='0' border='0' width='100%' align='center'>
            <tr bgcolor='#ffffff'>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='265'>
                <p align='right'><span class='text02' style='line-height:40px'><span class='text03' style='line-height:25px'>&nbsp;&nbsp;${incomeSourceTitle} &nbsp;상 &nbsp;&nbsp;호 : </span></span></p>
              </td>
              <td><span class='text02' style='line-height:40px'><span class='text03' style='line-height:25px'>&nbsp;&nbsp;${htmlOrBlank(workplaceName)}</span></span></td>
            </tr>
            <tr bgcolor='#ffffff'>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='265'>
                <p align='right'><span class='text02' style='line-height:40px'><span class='text03' style='line-height:25px'>&nbsp;&nbsp;&nbsp;주 &nbsp;&nbsp;소 : </span></span></p>
              </td>
              <td>
                <p align='left'><span class='text02' style='line-height:40px'><span class='text03' style='line-height:25px'>&nbsp;&nbsp;${htmlOrBlank(workplaceAddress)}</span></span></p>
              </td>
            </tr>
            <tr bgcolor='#ffffff'>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='265'>
                <p align='right'><span class='text02' style='line-height:40px'><span class='text03' style='line-height:25px'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;대표자 : </span></span></p>
              </td>
              <td>
                <p align='left'><span class='text02' style='line-height:40px'><span class='text03' style='line-height:25px'>&nbsp;&nbsp;${htmlOrBlank(representativeName)}</span></span></p>
              </td>
            </tr>
          </table>
        </span><br><br></td>
      </tr>
      <tr style='height:43.5px;'>
        <td align='left'>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
            <tr>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='20' valign='top'><span class='text03' style='line-height:40px'>4.&nbsp;</span></td>
              <td align='left'><span class='text03' style='line-height:40px'>만약, 일부 채권자가 위와 같은 행위를 할 경우 신청인의 ${employmentType === '자영' ? '영업소득' : '급여소득'}을 기준으로 한 가용소득확보가 어렵게 되어 개인회생절차를 수행할 수 없고, 다른 채권자들에게 손해를 가할 염려가 있으므로 채무자 회생 및 파산에 관한 법률 593조 제 1항에 의하여 이 신청에 이른 것입니다.</span></td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    <br><br><br><br>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr style='height:43.5px;'>
        <td><span class='text03' style='line-height:40px'>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${filingDateText}<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;신&nbsp; 청&nbsp; 인&nbsp;&nbsp;&nbsp;&nbsp;${escapeHtml(basics.applicantName)}&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;대&nbsp;&nbsp;리&nbsp;&nbsp;인 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${escapeHtml(basics.lawyerName || '')}&nbsp;&nbsp;&nbsp;(인)
<br>
        </span></td>
      </tr>
    </table>
    <br><br><br><br><br>
  `
}

const buildSuspensionReferenceBody = (context: RecoveryDocumentContext) => {
  const basics = getContextBasics(context)
  const page1 = context.pages['1'] || {}
  const page2 = context.pages['2'] || {}
  const page16 = context.pages['16'] || {}
  const employment = getEmploymentDataFromContext(context)

  const caseYear = String(page1?.caseYear || basics.filingDateParts.year || '').trim()
  const caseNumberOnly = String(page1?.caseNumber || basics.caseNumber || '').trim()
  const caseLabel = [caseYear, '개회', caseNumberOnly].filter(Boolean).join(' ')
  const applicantAddress = basics.applicantAddress || context.caseInfo?.applicant_address || ''
  const filingDateText = formatHtmlDottedDateFromParts(
    basics.filingDateParts.year,
    basics.filingDateParts.month,
    basics.filingDateParts.day
  )

  const proceedingsPages = ['15_1', '15_2', '15_3', '15_4', '15_5']
  const proceedingRows = proceedingsPages
    .flatMap((pageId) => Array.isArray(context.pages[pageId]?.items) ? context.pages[pageId].items : [])
    .filter((item: Record<string, any>) => String(item?.enabled || '').trim() === 'Y')
    .slice(0, 8)

  const proceedingMarkup = proceedingRows.length
    ? proceedingRows.map((item: Record<string, any>, index: number) => `
      <tr style='height:43.5px;'>
        <td class='border_t'><p align='center'><span class='text01'>${index + 1}</span></p></td>
        <td class='border_tl' style='padding-left:5px;'><span class='text01'>${htmlOrBlank(item.proceedingType || item.procedureStatus || item.note)}</span></td>
        <td class='border_tl' style='padding-left:5px;'><span class='text01'>${htmlOrBlank(item.creditorName)}</span></td>
        <td class='border_tl' style='padding-left:5px;'><span class='text01'>${htmlOrBlank(item.court)}</span></td>
        <td class='border_tl' style='padding-left:5px;'><span class='text01'>${htmlOrBlank(item.caseNumber)}</span></td>
      </tr>
    `).join('')
    : `
      <tr style='height:43.5px;'>
        <td class='border_t'><p align='center'><span class='text01'>&nbsp;</span></p></td>
        <td class='border_tl' colspan='4'><p align='center'><span class='text01'>중지명령 대상 진행사건이 입력되지 않았습니다.</span></p></td>
      </tr>
      <tr style='height:43.5px;'>
        <td class='border_t'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl'><span class='text01'>&nbsp;</span></td>
      </tr>
      <tr style='height:43.5px;'>
        <td class='border_t'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl'><span class='text01'>&nbsp;</span></td>
        <td class='border_tl'><span class='text01'>&nbsp;</span></td>
      </tr>
    `

  const incomeSourceTitle = String(employment.employmentType || '').trim() === '자영' ? '영업장' : '급여처'
  const workplaceName = employment.workplaceName || employment.companyName || ''
  const workplaceAddress = [employment.companyZipCode ? `[${employment.companyZipCode}]` : '', employment.companyAddress || '']
    .filter(Boolean)
    .join(' ')
    .trim()
  const representativeName = employment.companyRepresentative || employment.ceoName || employment.representativeName || ''

  return `
    <br><br><br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr>
        <td align='center'><span class='text11_b'>중&nbsp;&nbsp;지&nbsp;&nbsp;명&nbsp;&nbsp;령&nbsp;&nbsp;신&nbsp;&nbsp;청&nbsp;&nbsp;서</span></td>
      </tr>
    </table>
    <br><br><br>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr bgcolor='#ffffff' style='height:49.5px;'>
        <td width='140' class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>사&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;건</span></td>
        <td class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>${escapeHtml(caseLabel)} 호&nbsp;&nbsp;&nbsp;개인회생</span></td>
      </tr>
      <tr bgcolor='#ffffff' style='height:49.5px;'>
        <td width='140' class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>신청인 (채무자)</span></td>
        <td class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>${escapeHtml(basics.applicantName)}</span></td>
      </tr>
      <tr bgcolor='#ffffff' style='height:49.5px;'>
        <td width='140'></td>
        <td class='text02' style='line-height:30px'><span class='text03' style='line-height:25px'>${htmlOrBlank(applicantAddress)}</span></td>
      </tr>
    </table>
    <br><br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr>
        <td align='center'><span class='text11_b'>신&nbsp;청&nbsp;취&nbsp;지</span></td>
      </tr>
    </table>
    <br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr style='height:43.5px;'>
        <td align='left'>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
            <tr valign='top'>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='20' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td align='left'><span class='text03' style='line-height:40px'>신청인(채무자)에 대한 이 법원 ${escapeHtml(caseLabel)} 호 개인회생사건에 관하여 개인회생절차의 개시신청에 대한 개시결정이 있을 때까지 아래 기재 각 집행·보전처분 및 독촉절차를 중지한다.<br>라는 결정을 구합니다.</span></td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    <br><br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr>
        <td align='center'><span class='text11_b'>신&nbsp;청&nbsp;이&nbsp;유</span></td>
      </tr>
    </table>
    <br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr style='height:43.5px;'>
        <td align='left'>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
            <tr>
              <td width='10' valign='top'><span class='text03' style='line-height:30px'>&nbsp;</span></td>
              <td width='20' valign='top'><span class='text03' style='line-height:30px'>1.&nbsp;</span></td>
              <td align='left'><span class='text03' style='line-height:30px'>신청인(채무자)은 귀원 ${escapeHtml(caseLabel)}호 사건의 채무자입니다.</span></td>
            </tr>
          </table>
        </td>
      </tr>
      <tr style='height:43.5px;'>
        <td align='left'>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
            <tr>
              <td width='10' valign='top'><span class='text03' style='line-height:30px'>&nbsp;</span></td>
              <td width='20' valign='top'><span class='text03' style='line-height:30px'>2.&nbsp;</span></td>
              <td align='left'><span class='text03' style='line-height:30px'>현재 신청인에 대하여 이미 개시되어 있는 강제집행·가압류·가처분·지급명령 등 절차는 아래와 같습니다.</span></td>
            </tr>
          </table>
          <br>
          <table cellpadding='0' cellspacing='0' width='100%' class='docxline'>
            <tr style='height:43.5px;'>
              <td class='border_o' width='52'><p align='center'><span class='text02'>순번</span></p></td>
              <td class='border_l' width='150'><p align='center'><span class='text02'>내역</span></p></td>
              <td class='border_l' width='180'><p align='center'><span class='text02'>채권자</span></p></td>
              <td class='border_l' width='160'><p align='center'><span class='text02'>관할법원</span></p></td>
              <td class='border_l' width='160'><p align='center'><span class='text02'>사건번호</span></p></td>
            </tr>
            ${proceedingMarkup}
          </table>
        </td>
      </tr>
    </table>
    <p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'><br><br><br><br><br>
    <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
      <tr style='height:43.5px;'>
        <td align='left'>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
            <tr>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='20' valign='top'><span class='text03' style='line-height:40px'>3.&nbsp;</span></td>
              <td align='left'><span class='text03' style='line-height:40px'>위와 같은 절차가 계속되면 신청인의 장래 소득 및 재산을 보전할 수 없어 개인회생절차의 수행이 현저히 곤란하게 됩니다.</span></td>
            </tr>
          </table>
        </td>
      </tr>
      <tr style='height:43.5px;'>
        <td align='left' style='padding-top:20px;'><span class='text03'>
          <table cellpadding='0' cellspacing='0' border='0' width='100%' align='center'>
            <tr bgcolor='#ffffff'>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='265'>
                <p align='right'><span class='text02' style='line-height:40px'><span class='text03' style='line-height:25px'>&nbsp;&nbsp;${incomeSourceTitle} &nbsp;상 &nbsp;&nbsp;호 : </span></span></p>
              </td>
              <td><span class='text02' style='line-height:40px'><span class='text03' style='line-height:25px'>&nbsp;&nbsp;${htmlOrBlank(workplaceName)}</span></span></td>
            </tr>
            <tr bgcolor='#ffffff'>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='265'>
                <p align='right'><span class='text02' style='line-height:40px'><span class='text03' style='line-height:25px'>&nbsp;&nbsp;&nbsp;주 &nbsp;&nbsp;소 : </span></span></p>
              </td>
              <td>
                <p align='left'><span class='text02' style='line-height:40px'><span class='text03' style='line-height:25px'>&nbsp;&nbsp;${htmlOrBlank(workplaceAddress)}</span></span></p>
              </td>
            </tr>
            <tr bgcolor='#ffffff'>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='265'>
                <p align='right'><span class='text02' style='line-height:40px'><span class='text03' style='line-height:25px'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;대표자 : </span></span></p>
              </td>
              <td>
                <p align='left'><span class='text02' style='line-height:40px'><span class='text03' style='line-height:25px'>&nbsp;&nbsp;${htmlOrBlank(representativeName)}</span></span></p>
              </td>
            </tr>
          </table>
        </span><br><br></td>
      </tr>
      <tr style='height:43.5px;'>
        <td align='left'>
          <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
            <tr>
              <td width='10' valign='top'><span class='text03' style='line-height:40px'>&nbsp;</span></td>
              <td width='20' valign='top'><span class='text03' style='line-height:40px'>4.&nbsp;</span></td>
              <td align='left'><span class='text03' style='line-height:40px'>따라서 채무자 회생 및 파산에 관한 법률에 따라 위 진행사건들에 대한 중지명령이 필요하여 이 신청에 이른 것입니다.</span></td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    <br><br><br><br>
    <table cellpadding='0' cellspacing='0' width='100%' align='center'>
      <tr style='height:43.5px;'>
        <td><span class='text03' style='line-height:40px'>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${filingDateText}<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;신&nbsp; 청&nbsp; 인&nbsp;&nbsp;&nbsp;&nbsp;${escapeHtml(basics.applicantName)}&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;대&nbsp;&nbsp;리&nbsp;&nbsp;인 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${escapeHtml(basics.lawyerName || '')}&nbsp;&nbsp;&nbsp;(인)
<br>
        </span></td>
      </tr>
    </table>
    <br><br><br><br><br>
  `
}

const buildCopySubmissionReferenceBody = (context: RecoveryDocumentContext) => {
  const basics = getContextBasics(context)
  const page1 = context.pages['1'] || {}
  const page2 = context.pages['2'] || {}

  const caseYear = String(page1?.caseYear || basics.filingDateParts.year || '').trim()
  const caseNumberOnly = String(page1?.caseNumber || '').trim()
  const courtName = String(page2?.court || basics.courtName || '').trim()
  const filingDateText = formatHtmlDottedDateFromParts(
    basics.filingDateParts.year || '20',
    basics.filingDateParts.month,
    basics.filingDateParts.day
  )

  return `
<table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
 <tr>
  <td>
   <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
    <tr>
     <td width='350' style='padding-top:5px;'>
      <table width='100%' cellpadding='0' cellspacing='0' style='border-left:4px double #000000; border-top:4px double #000000;'>
       <tr><td>&nbsp;</td></tr>
      </table>
     </td>
     <td width='350' style='padding-top:5px;'>
      <table width='100%' cellpadding='0' cellspacing='0' style='border-right:4px double #000000; border-top:4px double #000000;'>
       <tr><td>&nbsp;</td></tr>
      </table>
     </td>
    </tr>
   </table>
  </td>
 </tr>
 <tr>
  <td>
   <table border='0' cellpadding='0' width='700' cellspacing='0' align='center' style='border:4px double #000000;border-top:0;margin: 0;padding: 16px;width: 100%;'>
    <tr>
     <td>
      <table border='0' cellpadding='0' cellspacing='0' width='100%'>
       <tr>
        <td align='right'>
          <table border='0' cellpadding='0' cellspacing='0' width='240' style='height:49.5px;'>
           <tr>
             <td align='right'><b><span class='text01_b'></span></b></td>
           </tr>
          </table>
        </td>
       </tr>
      </table>
      <br><br>
      <p align='center'><b><span class='text10_b'>부 본 제 출 서</span></b></p>
      <br><br><br><br>
      <p align='justify'>
        <span class='text03'>&nbsp;&nbsp;사&nbsp;&nbsp;건&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <u>${escapeHtml(caseYear || '')}</u></span>
        <span class='text03'>&nbsp;개회</span>&nbsp;<span class='text03'><u>${escapeHtml(caseNumberOnly || basics.caseNumber || '')}</u></span>
        <span class='text03'>개인회생</span>
        <br><br><br>
        <span class='text03'>&nbsp;&nbsp;채 무 자&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${escapeHtml(basics.applicantName)}${basics.residentNumber ? ` (${escapeHtml(basics.residentNumber)})` : ''}</span>
      </p>
      <br><br><br><br>
      <p align='center'>
        <span class='text03'>채무자는 개시결정으로 인하여 채권자목록과 변제계획안을 제출합니다.</span>
      </p>
      <br><br><br><br>
      <p align='center'>
        <span class='text04'><b>첨 부 서 류</b></span>
      </p>
      <span class='text03'>1. 채권자목록 &nbsp;&nbsp;&nbsp;&nbsp;</span><br><br>
      <span class='text03'>2. 변제계획안 &nbsp;&nbsp;&nbsp;&nbsp;</span>
      <br><br><br><br>
      <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
       <tr>
        <td>
          <span class='text03'>${filingDateText}</span><br><br>
          <span class='text02'>채 &nbsp;무 &nbsp;자&nbsp;&nbsp;&nbsp;&nbsp;${escapeHtml(basics.applicantName)}&nbsp;&nbsp;&nbsp;</span><br><br>
          <span class='text02'>대&nbsp;&nbsp;리&nbsp;&nbsp;인&nbsp;&nbsp;&nbsp;${escapeHtml(basics.lawyerName || '')}&nbsp;&nbsp;&nbsp;(인)</span>
        </td>
       </tr>
      </table>
      <br><br>
      <br><br><br><br>
      <p align='justify'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b><span style='span-size:15pt;'>${escapeHtml(courtName)}&nbsp;&nbsp;귀중</span></b></p>
      <br><br>
     </td>
    </tr>
   </table>
  </td>
 </tr>
</table>
  `
}

const buildFilingCertificateReferenceBody = (context: RecoveryDocumentContext) => {
  const basics = getContextBasics(context)
  const page1 = context.pages['1'] || {}
  const page2 = context.pages['2'] || {}

  const caseYear = String(page1?.caseYear || basics.filingDateParts.year || '').trim()
  const caseNumberOnly = String(page1?.caseNumber || '').trim()
  const courtName = String(page2?.court || basics.courtName || '').trim()
  const filingYearShort = basics.filingDateParts.year ? basics.filingDateParts.year.slice(-2) : '&nbsp;&nbsp;&nbsp;&nbsp;'
  const filingMonth = basics.filingDateParts.month || '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'
  const filingDay = basics.filingDateParts.day || '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'
  const filingDateLine = formatHtmlDottedDateFromParts(
    basics.filingDateParts.year,
    basics.filingDateParts.month,
    basics.filingDateParts.day
  )

  const buildReceiptSheet = (label: string, includeReceiptBlock: boolean) => `
<br><br><br><br>
<table border='0' cellpadding='0' cellspacing='0' width='100%'>
<tr>
   <td><span class='text03'>&lt;${label}&gt;</span></td>
</tr>
</table>
<table border='0' cellpadding='0' cellspacing='0' width='100%'>
 <tr>
  <td align='right'>
   <table border='1' cellpadding='2' cellspacing='0' bordercolordark='#ffffff' bordercolorlight='#000000' width='95' height='80'>
    <tr>
     <td align='center'><span class='text01' style='line-height:25px;'>인 &nbsp;&nbsp;지<br>500원</span></td>
    </tr>
   </table>
  </td>
 </tr>
</table><br>
<table border='0' cellpadding='0' cellspacing='0' width='100%'>
<tr>
   <td align='center'><span class='text11_b'>접 수&nbsp;&nbsp;증 명 원</span></td>
</tr>
</table>
<br><br><br><br>
    <table border='0' cellpadding='0' cellspacing='0' width='100%'>
     <tr bgcolor='#ffffff' style='height:12px;'>
      <td class='text03'></td>
     </tr>
     <tr bgcolor='#ffffff' style='height:43.5px;'>
      <td width='10' valign='top'><span class='text03' style='line-height:43px;'>&nbsp;</span></td>
      <td width='350' valign='top'><span class='text03' style='line-height:43px;'>채무자(신청인)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${escapeHtml(basics.applicantName)}</span></td>
      <td class='text03'></td>
     </tr>
     <tr bgcolor='#ffffff'>
      <td colspan='4' valign='top' ${includeReceiptBlock ? "style='height:49.5px;'" : "height='80'"}></td>
     </tr>
     <tr bgcolor='#ffffff' style='height:43.5px;'>
      <td colspan='4' class='text03' style='line-height:40px; padding-left:40px;'>개인회생절차개시 신청서가  다음과 같이 접수되었음을 증명하여 주시기 바랍니다.<br><br>접수일자&nbsp; :&nbsp;&nbsp; 20<U>${filingYearShort}</U>년 &nbsp;<U>${filingMonth}</U>월 &nbsp;<U>${filingDay}</U>일 <br>사건번호&nbsp; :&nbsp;&nbsp; ${escapeHtml(caseYear || '20')}&nbsp;&nbsp;개회&nbsp; <U>${escapeHtml(caseNumberOnly || basics.caseNumber || '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;')}</U><br></td>
     </tr>
     <tr bgcolor='#ffffff'>
      <td colspan='4' valign='top' ${includeReceiptBlock ? "style='height:70.5px;'" : "height='100'"}></td>
     </tr>
     <tr bgcolor='#ffffff' style='height:43.5px;'>
      <td valign='top'><span class='text03' style='line-height:43px;'>&nbsp;</span></td>
      <td colspan='2' valign='top'><span class='text03' style='line-height:43px;'>&nbsp;</span></td>
      <td class='text03' align='${includeReceiptBlock ? 'right' : 'center'}'>
         <table border='0' cellpadding='0' cellspacing='0' width='100%'>
            <tr style='height:${includeReceiptBlock ? '36.5' : '59'}px;'>
               <td class='text03' colspan='2'>${filingDateLine}</td>
            </tr>
            <tr style='height:82.5px;'>
               <td class='text03' width='150'>채무자(신청인)</td>
               <td class='text03' width='300'>${escapeHtml(basics.applicantName)} &nbsp;&nbsp;(인)</td>
            </tr>
            ${includeReceiptBlock ? `
            <tr style='height:59px;' valign='bottom'>
               <td colspan='2' class='text03'>이를 영수합니다.</td>
            </tr>
            <tr style='height:59px;' valign='bottom'>
               <td class='text03'>영수인 </td>
               <td class='text03'>${escapeHtml(basics.applicantName)} &nbsp;&nbsp;(인)</td>
            </tr>
            ` : ''}
          </table>
      </td>
     </tr>
    </table>
<br><br><br><br><br><br>${includeReceiptBlock ? '' : '<br>'}
<span class='text08_b'>${escapeHtml(courtName)}&nbsp;&nbsp;&nbsp;&nbsp;귀&nbsp;중</span>
`

  return `
${buildReceiptSheet('법원 보관용', true)}
<p style='page-break-before:always'><b style='color:#ff0000;text-align:center;'>------------------- [수정하거나 지우지 마세요] 이 부분은 인쇄할때 페이지를 구분합니다. -------------------</b><br style='height:0; line-height:0'>
${buildReceiptSheet('신청인 반송용', false)}
  `
}

const buildPowerOfAttorneyReferenceBody = (context: RecoveryDocumentContext) => {
  const basics = getContextBasics(context)
  const page1 = context.pages['1'] || {}
  const page2 = context.pages['2'] || {}

  const caseYear = String(page1?.caseYear || basics.filingDateParts.year || '').trim()
  const caseNumberOnly = String(page1?.caseNumber || '').trim()
  const courtName = String(page2?.court || basics.courtName || '').trim()
  const filingDateText = formatHtmlDottedDateFromParts(
    basics.filingDateParts.year,
    basics.filingDateParts.month,
    basics.filingDateParts.day
  )
  const applicantAddress = basics.applicantAddress || context.caseInfo?.applicant_address || ''

  return `
<br><br>
  <br><br><br>
 <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
  <tr>
     <td align='center'><span class='text11_b'>소&nbsp;&nbsp;송&nbsp;&nbsp;위&nbsp;&nbsp;임&nbsp;&nbsp;장</span></td>
  </tr>
  </table>
  <br>
  <br>
  <table cellpadding='0' cellspacing='0' width='100%' align='center'>
    <tr bgcolor='#ffffff' style='height:49.5px;'>
      <td width='24'>
        <p align='center'><span class='text02' style='line-height:22px;'><span class='text03'></span></span></p>
      </td>
      <td>
        <p align='left'><br>
          <span class='text02' style='line-height:22px;'><span class='text03'>&nbsp;사&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;건&nbsp; :&nbsp; ${escapeHtml(caseYear)} 개회 ${escapeHtml(caseNumberOnly || basics.caseNumber || '')} 개인회생</span></span>
        </p>
      </td>
    </tr>
    <tr bgcolor='#ffffff' style='height:49.5px;'>
      <td width='24'>
        <p align='center'><span class='text03'></span></p>
      </td>
      <td>
        <p align='left'>
          <span class='text02'><span class='text03'>&nbsp;신청인(채무자)&nbsp; :&nbsp; ${escapeHtml(basics.applicantName)} </span></span>
        </p>
      </td>
    </tr>
  </table>
  <br>
  <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
    <tr>
      <td align='left'><span class='text03' style='line-height:35px;'>위 개인회생 사건에 관하여 아래 아래 수임인을 소송대리인으로 선임하고, 아래에서 정한 권한을 수여합니다.</span></td>
    </tr>
  </table>
  <br>
  <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
    <tr>
      <td>
        <span class='text04_b' style='line-height:30px;'>수&nbsp;임&nbsp;인&nbsp; :&nbsp; ${escapeHtml(basics.lawyerName || '')}</span>
        <span class='text03' style='line-height:43px;'><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${htmlOrBlank(basics.lawyerAddress || '')}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(전 화 &nbsp;:&nbsp; ${escapeHtml(basics.lawyerPhone || '')}  &nbsp;&nbsp;팩 스 &nbsp;:&nbsp; ${escapeHtml(basics.lawyerFax || '')})</span>
      </td>
      <td align='right'>
        <table border='0' cellpadding='2' cellspacing='0' width='100' height='110'>
          <tr style='height:43.5px;'>
            <td align='center'><span class='text01' style='line-height:25px;'>&nbsp;</span></td>
          </tr>
          <tr style='height:59px;'>
            <td align='center'><span class='text01' style='line-height:25px;'>&nbsp;&nbsp;</span></td>
          </tr>
        </table>
      </td>
    </tr>
  </table>
  <br>
  <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
    <tr>
      <td align='center'><span class='text07_b'>수&nbsp;권&nbsp;사&nbsp;항</span></td>
    </tr>
  </table>
  <br>
  <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
    <tr height='30'><td align='left'><span class='text03'>1. 일체의 소송행위</span></td></tr>
    <tr height='30'><td align='left'><span class='text03'>2. 소의 취하</span></td></tr>
    <tr height='30'><td align='left'><span class='text03'>3. 항고의 제기 또는 포기</span></td></tr>
    <tr height='30'><td align='left'><span class='text03'>4. 복대리인의 선임</span></td></tr>
    <tr height='30'><td align='left'><span class='text03'>5. 채권자에 대한 본인채무자료 열람, 송부에 관한 일체의 권한</span></td></tr>
    <tr height='30'><td align='left'><span class='text03'>6. 소송행위에 부수하는 행위</span></td></tr>
  </table>
  <br>
  <table cellpadding='0' cellspacing='0' width='100%' align='center'>
    <tr style='height:43.5px;'>
      <td><br><span class='text03' style='line-height:43px;'>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${filingDateText}<br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;위임인 신청인(채무자) : ${escapeHtml(basics.applicantName)}${basics.residentNumber ? ` (${escapeHtml(basics.residentNumber)})` : ''}&nbsp;(인) <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${htmlOrBlank(applicantAddress)}
      </span></td>
    </tr>
  </table>
  <br><br><br>
  <table border='0' cellpadding='0' cellspacing='0' align='center' width='100%'>
    <tr>
      <td valign='bottom'>
        <span class='text08_b' style='line-height:30px;'>${escapeHtml(courtName)}&nbsp;&nbsp;&nbsp;&nbsp;귀&nbsp;중</span>
      </td>
      <td align='right'>
        <table border='0' cellpadding='2' cellspacing='0' width='100' style='height:70.5px;'>
          <tr style='height:43.5px;'>
            <td align='center'><span class='text01' style='line-height:18px;'>&nbsp;</span></td>
          </tr>
        </table>
      </td>
    </tr>
  </table>
  `
}

const buildTrusteeDebtReferenceBody = (context: RecoveryDocumentContext) => {
  let result = extractBodyInnerHtml(recoveryTrusteeDebtBaseHtml)
  return scrubReferenceSeeds(result, context, 'trustee_debt')
}

const PAGE_BREAK = PAGE_BREAK_MARKER_HTML

const DEFAULT_ALL_APPLICATION_DOCUMENT_KEYS: RecoveryDocumentKey[] = [
  'creditors',
  'assets',
  'income_expense',
  'statement',
  'repayment_plan',
  'prohibition',
  'power_of_attorney',
  'confirmation'
]

const allApplicationDocumentKeySet = new Set<RecoveryDocumentKey>(DEFAULT_ALL_APPLICATION_DOCUMENT_KEYS)

const normalizeDocumentOrder = (value: unknown): RecoveryDocumentKey[] => {
  const preferred = Array.isArray(value)
    ? value.filter((key): key is RecoveryDocumentKey => DEFAULT_DOCUMENT_NAV_ORDER.includes(key as RecoveryDocumentKey))
    : []
  const seen = new Set<RecoveryDocumentKey>()
  const ordered = preferred.filter((key) => {
    if (seen.has(key)) return false
    seen.add(key)
    return true
  })

  DEFAULT_DOCUMENT_NAV_ORDER.forEach((key) => {
    if (!seen.has(key)) {
      ordered.push(key)
      seen.add(key)
    }
  })

  const allApplicationIndex = ordered.indexOf('all_application')
  if (allApplicationIndex > 0) {
    ordered.splice(allApplicationIndex, 1)
    ordered.unshift('all_application')
  } else if (allApplicationIndex === -1) {
    ordered.unshift('all_application')
  }

  return ordered
}

const stableSerializeDocumentValue = (value: unknown): string => {
  if (Array.isArray(value)) {
    return `[${value.map((item) => stableSerializeDocumentValue(item)).join(',')}]`
  }

  if (value && typeof value === 'object') {
    const entries = Object.entries(value as Record<string, unknown>)
      .sort(([left], [right]) => left.localeCompare(right))
      .map(([key, item]) => `${JSON.stringify(key)}:${stableSerializeDocumentValue(item)}`)
    return `{${entries.join(',')}}`
  }

  return JSON.stringify(value ?? null)
}

const hashDocumentFingerprint = (value: string) => {
  let hash = 5381
  for (let index = 0; index < value.length; index += 1) {
    hash = ((hash << 5) + hash) ^ value.charCodeAt(index)
  }
  return (hash >>> 0).toString(36)
}

const buildDocumentFingerprint = (key: RecoveryDocumentKey, context: RecoveryDocumentContext) => {
  const definition = RECOVERY_DOCUMENT_DEFINITIONS.find((item) => item.key === key) || RECOVERY_DOCUMENT_DEFINITIONS[0]
  const pageSnapshot = definition.pageIds.reduce<Record<string, unknown>>((acc, pageId) => {
    acc[String(pageId)] = context.pages?.[String(pageId)] ?? null
    return acc
  }, {})

  const fingerprintSource = stableSerializeDocumentValue({
    key,
    layoutVersion: DOCUMENT_LAYOUT_VERSIONS[key] || 'v1',
    caseInfo: {
      applicantName: context.caseInfo?.applicant_name || '',
      caseNumber: context.caseInfo?.case_number || '',
      residentNumber: context.caseInfo?.resident_number || '',
      registrationDate: context.caseInfo?.registration_date || ''
    },
    pages: pageSnapshot,
    documentOrder: key === 'all_application'
      ? normalizeDocumentOrder(context.manualDrafts?.all_application?.documentOrder)
      : undefined
  })

  return hashDocumentFingerprint(fingerprintSource)
}

const extractDocumentFingerprint = (html: string | undefined, key: RecoveryDocumentKey) => {
  if (!html) return ''

  const match = String(html).match(
    new RegExp(
      `<div(?=[^>]*data-fincure-document-key=(['"])${key}\\1)(?=[^>]*data-fincure-document-fingerprint=(['"])([^'"]+)\\2)[^>]*><\\/div>`,
      'i'
    )
  )

  return match?.[3] || ''
}

const resolveAllApplicationDocumentKeys = (context: RecoveryDocumentContext) =>
  normalizeDocumentOrder(context.manualDrafts.all_application?.documentOrder)
    .filter((key): key is RecoveryDocumentKey => allApplicationDocumentKeySet.has(key))

const buildAllDocumentsConcatenated = (sections: string[]) => sections.join(PAGE_BREAK)

const trimSectionPageBreakEdges = (html: string) => {
  let result = sanitizeManualDocumentHtml(String(html || '')).trim()

  result = result.replace(/^(?:<br\s*\/?>|\s|&nbsp;)+/gi, '')
  result = result.replace(/(?:<br\s*\/?>|\s|&nbsp;)+$/gi, '')
  result = result.replace(new RegExp(`^(?:${PAGE_BREAK_BOUNDARY_PATTERN.source})(?:<br\\s*\\/?>|\\s|&nbsp;)*`, 'i'), '')
  result = result.replace(new RegExp(`(?:<br\\s*\\/?>|\\s|&nbsp;)*(?:${PAGE_BREAK_BOUNDARY_PATTERN.source})$`, 'i'), '')

  return result.trim()
}

const shouldIncludeConcatenatedSection = (key: RecoveryDocumentKey, html: string) => {
  const body = extractBodyInnerHtml(html)
  const text = stripHtml(body)

  if (!text) return false
  if (key === 'confirmation' && isConfirmationPlaceholderHtml(html)) return false

  if (
    key === 'confirmation' &&
    (
      text.includes('전용 레퍼런스 템플릿 연결 작업이 진행 중입니다.') ||
      text.includes('저장된 데이터에 따라 이 문서 양식을 채워야 합니다.')
    )
  ) {
    return false
  }

  return true
}

const hasMeaningfulAssetData = (context: RecoveryDocumentContext) => {
  const page11 = context.pages['11'] || {}
  const page11_1 = context.pages['11_1'] || {}
  const page11_3 = context.pages['11_3'] || {}
  const page12 = context.pages['12'] || {}
  const page12_1 = context.pages['12_1'] || {}
  const page10 = context.pages['10'] || {}
  const page7 = context.pages['7'] || {}

  const accounts = Array.isArray(page11.accounts) ? page11.accounts : []
  const insurances = Array.isArray(page11_1.insuranceItems) ? page11_1.insuranceItems : []
  const leases = Array.isArray(page12.leases) ? page12.leases : []
  const vehicles = Array.isArray(page10.vehicles) ? page10.vehicles : []
  const spouseVehicles = Array.isArray(page10.spouseVehicles) ? page10.spouseVehicles : []
  const otherAssets = Array.isArray(page11_3.assetItems) ? page11_3.assetItems : []
  const facilities = Array.isArray(page12_1.facilities) ? page12_1.facilities : []
  const receivables = Array.isArray(page12.receivables) ? page12.receivables : []
  const retirementAmount = parseNumeric(page12_1?.formData?.retirementAmount)
  const cashAmount = parseNumeric(page11?.formData?.cashAmount)
  const realEstateCount = [
    parseNumeric(page7.apartmentCount),
    parseNumeric(page7.houseCount),
    parseNumeric(page7.landCount),
    parseNumeric(page7.spouseApartmentCount),
    parseNumeric(page7.spouseHouseCount),
    parseNumeric(page7.spouseLandCount)
  ].reduce((sum, value) => sum + value, 0)

  return (
    cashAmount > 0 ||
    retirementAmount > 0 ||
    realEstateCount > 0 ||
    accounts.length > 0 ||
    insurances.length > 0 ||
    leases.length > 0 ||
    vehicles.length > 0 ||
    spouseVehicles.length > 0 ||
    otherAssets.length > 0 ||
    facilities.length > 0 ||
    receivables.length > 0
  )
}

const trimAssetAnnexSections = (html: string) => {
  const firstMarkerIndex = getPageBreakStartIndices(html)[0] ?? -1
  return firstMarkerIndex >= 0 ? html.slice(0, firstMarkerIndex).trim() : html
}

const shouldPreferGeneratedHtml = (
  key: RecoveryDocumentKey,
  manualHtml: string | undefined,
  context: RecoveryDocumentContext
) => {
  if (!manualHtml) return true
  if (isLikelyReferenceSeedHtml(manualHtml)) return true
  if (key === 'confirmation' && isConfirmationPlaceholderHtml(manualHtml)) return true

  const currentFingerprint = buildDocumentFingerprint(key, context)
  const savedFingerprint = extractDocumentFingerprint(manualHtml, key)
  if (!savedFingerprint || savedFingerprint !== currentFingerprint) {
    return true
  }

  if (key === 'creditors') {
    const entries = collectCreditorEntries(context.pages)
    if (!entries.length) return false

    if (manualHtml.includes('저장된 데이터가 없습니다.')) {
      return true
    }

    const hasAnyCreditorName = entries.some((entry) => entry.creditorName && manualHtml.includes(entry.creditorName))
    return !hasAnyCreditorName
  }

  if (key === 'assets') {
    if (!manualHtml.includes(`data-fincure-asset-layout="${ASSET_LAYOUT_VERSION}"`)) {
      return true
    }

    const page11 = context.pages['11'] || {}
    const page11_1 = context.pages['11_1'] || {}
    const page11_3 = context.pages['11_3'] || {}
    const page12 = context.pages['12'] || {}
    const page12_1 = context.pages['12_1'] || {}
    const page10 = context.pages['10'] || {}
    const page7 = context.pages['7'] || {}

    const accounts = Array.isArray(page11.accounts) ? page11.accounts : []
    const insurances = Array.isArray(page11_1.insuranceItems) ? page11_1.insuranceItems : []
    const leases = Array.isArray(page12.leases) ? page12.leases : []
    const vehicles = Array.isArray(page10.vehicles) ? page10.vehicles : []
    const spouseVehicles = Array.isArray(page10.spouseVehicles) ? page10.spouseVehicles : []
    const facilities = Array.isArray(page12_1.facilities) ? page12_1.facilities : []
    const receivables = Array.isArray(page12.receivables) ? page12.receivables : []
    const otherAssets = Array.isArray(page11_3.assetItems) ? page11_3.assetItems : []
    const retirementAmount = parseNumeric(page12_1?.formData?.retirementAmount)
    const cashAmount = parseNumeric(page11?.formData?.cashAmount)
    const realEstateMarkers = [
      page7.apartmentCount,
      page7.houseCount,
      page7.landCount,
      page7.spouseApartmentCount,
      page7.spouseHouseCount,
      page7.spouseLandCount
    ].filter(Boolean)

    if (!hasMeaningfulAssetData(context)) return false
    if (manualHtml.includes('저장된 데이터가 없습니다.')) return true

    const categoryChecks = [
      !cashAmount || manualHtml.includes('보유 현금') || manualHtml.includes(cashAmount.toLocaleString('ko-KR')),
      !accounts.length || accounts.every((item: any) => !item.bankName || manualHtml.includes(String(item.bankName))),
      !insurances.length || insurances.every((item: any) => !item.companyName || manualHtml.includes(String(item.companyName))),
      !leases.length || leases.every((item: any) => !item.address || manualHtml.includes(String(item.address))),
      !vehicles.length || vehicles.every((item: any) => [item.type, item.price, item.securedDebtAmount, item.name, item.number].filter(Boolean).some((marker: any) => manualHtml.includes(String(marker)))),
      !spouseVehicles.length || spouseVehicles.every((item: any) => [item.type, item.price, item.securedDebtAmount].filter(Boolean).some((marker: any) => manualHtml.includes(String(marker)))),
      !realEstateMarkers.length || realEstateMarkers.every((marker: any) => manualHtml.includes(String(marker))),
      !facilities.length || facilities.every((item: any) => [item.assetName, item.estimatedValue, item.note].filter(Boolean).some((marker: any) => manualHtml.includes(String(marker)))),
      !receivables.length || receivables.every((item: any) => [item.debtorName, item.amount, item.relationship, item.dueDate].filter(Boolean).some((marker: any) => manualHtml.includes(String(marker)))),
      !retirementAmount || manualHtml.includes(String(page12_1?.formData?.retirementInstitution || '')) || manualHtml.includes(retirementAmount.toLocaleString('ko-KR')),
      !otherAssets.length || otherAssets.every((item: any) => !item.description || manualHtml.includes(String(item.description)))
    ]

    return categoryChecks.includes(false)
  }

  return false
}

const findNearestPageBreakStart = (source: string, index: number) => {
  const indices = getPageBreakStartIndices(source)
  const found = indices.filter((value) => value <= index).pop()
  return typeof found === 'number' ? found : 0
}

const findNextPageBreakStart = (source: string, index: number) => {
  const indices = getPageBreakStartIndices(source)
  const found = indices.find((value) => value >= index)
  return typeof found === 'number' ? found : source.length
}

const findMarkerIndex = (source: string, marker: string, occurrence = 1) => {
  let fromIndex = 0
  let markerIndex = -1
  for (let index = 0; index < occurrence; index += 1) {
    markerIndex = source.indexOf(marker, fromIndex)
    if (markerIndex < 0) return -1
    fromIndex = markerIndex + marker.length
  }
  return markerIndex
}

const extractReferenceSection = (source: string, marker: string, nextMarkers: string[] = [], occurrence = 1) => {
  const markerIndex = findMarkerIndex(source, marker, occurrence)
  if (markerIndex < 0) return ''

  const start = findNearestPageBreakStart(source, markerIndex)
  let end = source.length

  const nextPageBreak = findNextPageBreakStart(source, markerIndex + marker.length)
  end = Math.min(end, nextPageBreak)

  for (const nextMarker of nextMarkers) {
    const nextMarkerIndex = source.indexOf(nextMarker, markerIndex + marker.length)
    if (nextMarkerIndex > markerIndex) {
      end = Math.min(end, findNearestPageBreakStart(source, nextMarkerIndex))
    }
  }

  return source.slice(start, end).trim()
}

const formatDate = (value?: string | null) => {
  if (!value) return '-'
  const parsed = new Date(value)
  if (Number.isNaN(parsed.getTime())) return String(value)
  const year = parsed.getFullYear()
  const month = String(parsed.getMonth() + 1).padStart(2, '0')
  const day = String(parsed.getDate()).padStart(2, '0')
  return `${year}.${month}.${day}`
}

const parseNumeric = (value: unknown) => {
  const normalized = String(value ?? '').replace(/[^0-9.-]/g, '')
  return Number(normalized) || 0
}

const resolveLeaseAssetAmount = (lease: Record<string, any> | null | undefined) => {
  const recognizedValue = parseNumeric(lease?.recognizedValue)
  if (recognizedValue > 0) return recognizedValue
  return parseNumeric(lease?.depositAmount ?? lease?.deposit)
}

const formatHouseholdSizeText = (value: number) => {
  const normalized = Number(value.toFixed(2))
  return Number.isInteger(normalized) ? String(normalized) : String(normalized)
}

const formatVehicleEntryText = (item: any) => {
  const currentFormat = [
    item?.type || '차량',
    parseNumeric(item?.price) > 0 ? `시가 ${String(item.price).trim()}원` : '',
    parseNumeric(item?.securedDebtAmount) > 0 ? `피담보채무 ${String(item.securedDebtAmount).trim()}원` : ''
  ].filter(Boolean).join(' / ')

  if (currentFormat) {
    return currentFormat
  }

  return [item?.type || item?.name, item?.number, item?.year ? `${item.year}년식` : '']
    .filter(Boolean)
    .join(' / ')
}

const getDisplayHouseholdSize = (dependentsCount: unknown) => {
  const total = Math.max(parseNumeric(dependentsCount) + 1, 1)
  return formatHouseholdSizeText(total)
}

const getMedianIncomeHouseholdSize = (dependentsCount: unknown) => {
  return Math.min(Math.max(Math.round(parseNumeric(dependentsCount) + 1), 1), 5)
}

const flattenEntries = (input: unknown, prefix = ''): Array<[string, string]> => {
  if (input === null || input === undefined || input === '') return []

  if (Array.isArray(input)) {
    return input.flatMap((item, index) =>
      flattenEntries(item, prefix ? `${prefix} ${index + 1}` : `${index + 1}`)
    )
  }

  if (typeof input === 'object') {
    return Object.entries(input as Record<string, unknown>).flatMap(([key, value]) => {
      const nextPrefix = prefix ? `${prefix} > ${key}` : key
      if (value && typeof value === 'object') {
        return flattenEntries(value, nextPrefix)
      }

      if (value === null || value === undefined || value === '') {
        return []
      }

      return [[nextPrefix, String(value)]]
    })
  }

  return [[prefix || '값', String(input)]]
}

const buildRowsTable = (title: string, rows: Array<[string, string]>) => {
  const body = rows.length
    ? rows
        .map(
          ([label, value]) => `
            <tr>
              <th>${escapeHtml(label)}</th>
              <td>${escapeHtml(value)}</td>
            </tr>
          `
        )
        .join('')
    : `
        <tr>
          <th>안내</th>
          <td>저장된 데이터가 없습니다.</td>
        </tr>
      `

  return `
    <section class="doc-section">
      <h2>${escapeHtml(title)}</h2>
      <table class="doc-table">
        <tbody>${body}</tbody>
      </table>
    </section>
  `
}

const buildIncomeExpenseTable = (page5: Record<string, any>) => {
  const allMonthColumns = getPage5MonthColumns(page5)
  const enabledMonthColumns = getEnabledPage5MonthColumns(page5)
  const incomeRows = Array.isArray(page5?.incomeRows) ? page5.incomeRows : []
  const deductionRows = Array.isArray(page5?.deductionRows) ? page5.deductionRows : []

  const createHeader = () =>
    `
      <tr>
        <th>항목</th>
        ${allMonthColumns.map((column) => `<th>${getPage5MonthLabel(column)}</th>`).join('')}
        <th>합계</th>
      </tr>
    `

  const createBody = (rows: Array<Record<string, any>>) =>
    rows
      .map((row) => {
        const amounts = row?.amounts || {}
        const sum = enabledMonthColumns.reduce((acc, column) => acc + parseNumeric(amounts[column.key]), 0)
        const sourceText = String(row?.source || '').trim()
        const hasSource = sourceText.length > 0
        return `
          <tr>
            <th>${hasSource ? escapeHtml(sourceText) : '&nbsp;'}</th>
            ${allMonthColumns.map((column) => `<td>${hasSource ? (isDocumentMonthColumnEnabled(column) ? escapeHtml(amounts[column.key] || '0') : '0') : '&nbsp;'}</td>`).join('')}
            <td>${hasSource ? sum.toLocaleString('ko-KR') : '&nbsp;'}</td>
          </tr>
        `
      })
      .join('')

  return `
    <section class="doc-section">
      <h2>월별 수입 및 공제 내역</h2>
      <p class="doc-note">선택된 월: ${enabledMonthColumns.map((column) => getPage5MonthLabel(column)).join(', ') || '없음'}</p>
      <table class="doc-grid-table">
        <thead>${createHeader()}</thead>
        <tbody>${createBody(incomeRows)}</tbody>
      </table>
      <table class="doc-grid-table">
        <thead>${createHeader()}</thead>
        <tbody>${createBody(deductionRows)}</tbody>
      </table>
    </section>
  `
}

const buildDocumentShell = (title: string, caseInfo: Record<string, any>, bodyHtml: string) => `
  <!DOCTYPE html>
  <html lang="ko">
  <head>
    <meta charset="UTF-8">
    <title>${escapeHtml(title)}</title>
    <style>
      body {
        font-family: "Batang", "BatangChe", "Nanum Myeongjo", serif;
        color: #111827;
        line-height: 1.7;
        margin: 0;
        background: #f8fafc;
      }
      .document-shell,
      .document-shell *,
      .document-paper,
      .document-paper * {
        font-family: "Batang", "BatangChe", "Nanum Myeongjo", serif !important;
      }
      .document-shell {
        max-width: 980px;
        margin: 0 auto;
        padding: 40px 28px 72px;
      }
      .document-paper {
        background: #ffffff;
        border: 1px solid #e5e7eb;
        border-radius: 24px;
        box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
        padding: 40px;
      }
      .doc-cover {
        border-bottom: 2px solid #dbeafe;
        padding-bottom: 24px;
        margin-bottom: 28px;
      }
      .doc-eyebrow {
        display: inline-block;
        padding: 6px 12px;
        border-radius: 999px;
        background: #eef2ff;
        color: #4f46e5;
        font-size: 13px;
        font-weight: 700;
      }
      h1 {
        margin: 16px 0 10px;
        font-size: 32px;
        color: #0f172a;
      }
      .doc-meta {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 10px 24px;
        margin-top: 20px;
      }
      .doc-meta-item strong {
        display: inline-block;
        min-width: 90px;
        color: #475569;
      }
      .doc-section {
        margin-top: 28px;
      }
      .doc-section h2 {
        margin: 0 0 14px;
        font-size: 20px;
        color: #1e3a8a;
      }
      .doc-table,
      .doc-grid-table {
        width: 100%;
        border-collapse: collapse;
      }
      .doc-table th,
      .doc-table td,
      .doc-grid-table th,
      .doc-grid-table td {
        border: 1px solid #dbe2ea;
        padding: 10px 12px;
        font-size: 14px;
        text-align: left;
        vertical-align: top;
      }
      .doc-table th,
      .doc-grid-table th {
        background: #f8fafc;
        color: #334155;
        width: 28%;
      }
      .doc-grid-table + .doc-grid-table {
        margin-top: 16px;
      }
      .doc-note {
        margin: 0 0 12px;
        color: #64748b;
        font-size: 13px;
      }
      .doc-paragraph {
        padding: 18px 20px;
        background: #f8fafc;
        border: 1px solid #e2e8f0;
        border-radius: 16px;
        white-space: pre-wrap;
      }
      @media print {
        body {
          background: #ffffff;
        }
        .document-shell {
          padding: 0;
          max-width: none;
        }
        .document-paper {
          border: none;
          box-shadow: none;
          border-radius: 0;
          padding: 0;
        }
      }
    </style>
  </head>
  <body>
    <div class="document-shell">
      <div class="document-paper">
        <header class="doc-cover">
          <span class="doc-eyebrow">FineCure 개인회생 문서</span>
          <h1>${escapeHtml(title)}</h1>
          <div class="doc-meta">
            <div class="doc-meta-item"><strong>신청인</strong> ${escapeHtml(caseInfo.applicant_name || '-')}</div>
            <div class="doc-meta-item"><strong>사건번호</strong> ${escapeHtml(caseInfo.case_number || '-')}</div>
            <div class="doc-meta-item"><strong>등록일자</strong> ${escapeHtml(formatDate(caseInfo.registration_date))}</div>
            <div class="doc-meta-item"><strong>진행상태</strong> ${escapeHtml(caseInfo.recovery_status || caseInfo.bankruptcy_status || '-')}</div>
          </div>
        </header>
        ${bodyHtml}
      </div>
    </div>
  </body>
  </html>
`

export const useRecoveryDocumentForms = () => {
  const config = useRuntimeConfig()
  const authStore = useAuthStore()
  const caseInfoCache = new Map<number, any>()
  const pageDraftCache = new Map<string, Record<string, any>>()
  const manualDraftCache = new Map<string, any>()

  const isNotFoundError = (error: unknown) => {
    const statusCode = (error as { statusCode?: number })?.statusCode
    const responseStatus = (error as { response?: { status?: number } })?.response?.status
    return statusCode === 404 || responseStatus === 404
  }

  const dummyDraftEntries = getRecoveryDummyDraftEntries()
  const dummyDraftPages = dummyDraftEntries.reduce<Record<string, any>>((acc, [pageId, payload]) => {
    acc[String(pageId)] = payload
    return acc
  }, {})

  const readLocalPageDrafts = (pageIds?: Array<number | string>) => {
    if (!process.client) return {}

    return (pageIds || Object.keys(dummyDraftPages)).reduce<Record<string, any>>((acc, pageId) => {
      const normalizedPageId = String(pageId)
      const storageKeys = [`recovery_page${normalizedPageId}`, `recovery_page${normalizedPageId}_draft`]

      for (const storageKey of storageKeys) {
        const raw = window.localStorage.getItem(storageKey)
        if (!raw) continue

        try {
          acc[normalizedPageId] = JSON.parse(raw)
          break
        } catch {
          // Ignore malformed local storage values and continue checking fallbacks.
        }
      }

      return acc
    }, {})
  }

  const buildFallbackCaseInfo = (caseId: number) => {
    const localPage1 = readLocalPageDrafts(['1'])['1'] || {}
    const dummyPage1 = dummyDraftPages['1'] || {}
    const page1 = Object.keys(localPage1).length ? localPage1 : dummyPage1
    const residentNumber = [page1.juminFront, page1.juminBack].filter(Boolean).join('')
    const applicantPhone = [page1.mobilePart1, page1.mobilePart2, page1.mobilePart3].filter(Boolean).join('-')

    return {
      id: caseId,
      applicant_name: page1.applicantName || '박민준',
      applicant_phone: applicantPhone,
      case_number: buildRecoveryCaseNumber(page1.caseYear, page1.caseNumber),
      resident_number: residentNumber,
      registration_date: page1.filingDate || '',
      recovery_status: 'before_filing'
    }
  }

  const buildFallbackPageDrafts = (pageIds?: Array<number | string>) => {
    const localDrafts = readLocalPageDrafts(pageIds)

    return (pageIds || Object.keys(dummyDraftPages)).reduce<Record<string, any>>((acc, pageId) => {
      const normalizedPageId = String(pageId)
      acc[normalizedPageId] = localDrafts[normalizedPageId] || dummyDraftPages[normalizedPageId] || {}
      return acc
    }, {})
  }

  const getLocalManualDraftKey = (caseId: number, key: RecoveryDocumentKey) =>
    `finecure:recovery:manual:${caseId}:${key}`

  const readLocalManualDraft = (caseId: number, key: RecoveryDocumentKey) => {
    if (!process.client) return null

    try {
      const raw = window.localStorage.getItem(getLocalManualDraftKey(caseId, key))
      if (!raw) return null
      const parsed = JSON.parse(raw)
      if (parsed?.html) {
        parsed.html = sanitizeManualDocumentHtml(parsed.html)
      }
      if (Array.isArray(parsed?.documentOrder)) {
        parsed.documentOrder = normalizeDocumentOrder(parsed.documentOrder)
      }
      return parsed as ManualDraftData
    } catch {
      return null
    }
  }

  const writeLocalManualDraft = (caseId: number, key: RecoveryDocumentKey, draftData: ManualDraftData) => {
    if (!process.client) return

    try {
      window.localStorage.setItem(
        getLocalManualDraftKey(caseId, key),
        JSON.stringify(draftData)
      )
    } catch {
      // Ignore localStorage write failures and keep in-memory cache only.
    }
  }

  const documentDefinitions = RECOVERY_DOCUMENT_DEFINITIONS

  const getDocumentDefinition = (key: RecoveryDocumentKey) =>
    documentDefinitions.find((item) => item.key === key) || documentDefinitions[0]

  const buildDocumentFingerprintMarker = (key: RecoveryDocumentKey, context: RecoveryDocumentContext) =>
    `<div data-fincure-document-key="${key}" data-fincure-document-fingerprint="${buildDocumentFingerprint(key, context)}" aria-hidden="true" style="display:none !important;width:0 !important;height:0 !important;overflow:hidden !important;"></div>`

  const withDocumentFingerprintMarker = (
    key: RecoveryDocumentKey,
    context: RecoveryDocumentContext,
    html: string
  ) => `${buildDocumentFingerprintMarker(key, context)}${stripDocumentFingerprintMarkers(html)}`

  const fetchCaseInfo = async (caseId: number) => {
    if (caseInfoCache.has(caseId)) {
      return caseInfoCache.get(caseId)
    }

    let response: CaseInfoResponse
    try {
      response = await $fetch<CaseInfoResponse>(`/cases/${caseId}`, {
        baseURL: config.public.apiBase,
        headers: {
          Authorization: `Bearer ${authStore.token}`
        }
      })
    } catch (error) {
      if (!isNotFoundError(error)) {
        throw error
      }

      const fallbackCase = {
        ...buildFallbackCaseInfo(caseId)
      }
      caseInfoCache.set(caseId, fallbackCase)
      return fallbackCase
    }

    if (!response.success) {
      throw new Error('사건 정보를 불러오지 못했습니다.')
    }

    caseInfoCache.set(caseId, response.case)
    return response.case
  }

  const fetchPageDrafts = async (caseId: number, pageIds?: Array<number | string>) => {
    const cacheKey = `${caseId}:${(pageIds || []).map((pageId) => String(pageId)).sort().join(',') || 'all'}`

    let response: PageDraftListResponse
    try {
      response = await $fetch<PageDraftListResponse>('/documents/page-drafts', {
        baseURL: config.public.apiBase,
        headers: {
          Authorization: `Bearer ${authStore.token}`
        },
        query: {
          caseId,
          documentType: 'recovery',
          pageIds: pageIds?.map((pageId) => String(pageId)).join(',')
        }
      })
    } catch (error) {
      if (!isNotFoundError(error)) {
        throw error
      }

      const fallbackDrafts = buildFallbackPageDrafts(pageIds)
      pageDraftCache.set(cacheKey, fallbackDrafts)
      return fallbackDrafts
    }

    if (!response.success || !Array.isArray(response.drafts)) {
      return {}
    }

    const drafts = response.drafts.reduce<Record<string, any>>((acc: Record<string, any>, draft: PageDraftRecord) => {
      acc[String(draft.pageId)] = draft.formData
      return acc
    }, {})

    pageDraftCache.set(cacheKey, drafts)
    return drafts
  }

  const fetchManualDraft = async (caseId: number, key: RecoveryDocumentKey) => {
    const cacheKey = `${caseId}:${key}`
    if (manualDraftCache.has(cacheKey)) {
      return manualDraftCache.get(cacheKey)
    }

    let response: PageDraftSingleResponse
    try {
      response = await $fetch<PageDraftSingleResponse>('/documents/page-drafts', {
        baseURL: config.public.apiBase,
        headers: {
          Authorization: `Bearer ${authStore.token}`
        },
        query: {
          caseId,
          documentType: 'recovery',
          pageId: `${manualPageIdPrefix}${key}`
        }
      })
    } catch (error) {
      if (!isNotFoundError(error)) {
        throw error
      }

      const localDraft = readLocalManualDraft(caseId, key)
      manualDraftCache.set(cacheKey, localDraft)
      return localDraft
    }

    const draft = response.success ? (response.draft?.formData || null) as ManualDraftData | null : null
    if (draft?.html) {
      draft.html = sanitizeManualDocumentHtml(draft.html)
    }
    if (Array.isArray(draft?.documentOrder)) {
      draft.documentOrder = normalizeDocumentOrder(draft.documentOrder)
    }
    manualDraftCache.set(cacheKey, draft)
    return draft
  }

  const fetchManualDrafts = async (caseId: number) => {
    const manualPageIds = documentDefinitions.map((item) => `${manualPageIdPrefix}${item.key}`)
    let response: PageDraftListResponse
    try {
      response = await $fetch<PageDraftListResponse>('/documents/page-drafts', {
        baseURL: config.public.apiBase,
        headers: {
          Authorization: `Bearer ${authStore.token}`
        },
        query: {
          caseId,
          documentType: 'recovery',
          pageIds: manualPageIds.join(',')
        }
      })
    } catch (error) {
      if (!isNotFoundError(error)) {
        throw error
      }

      return documentDefinitions.reduce<Record<string, ManualDraftData>>((acc, item) => {
        const localDraft = readLocalManualDraft(caseId, item.key)
        if (localDraft) {
          acc[item.key] = localDraft
          manualDraftCache.set(`${caseId}:${item.key}`, localDraft)
        }
        return acc
      }, {})
    }

    if (!response.success || !Array.isArray(response.drafts)) {
      return {}
    }

    return response.drafts.reduce<Record<string, ManualDraftData>>((acc: Record<string, ManualDraftData>, draft: PageDraftRecord) => {
      const key = String(draft.pageId).replace(manualPageIdPrefix, '')
      const formData = (draft.formData || {}) as ManualDraftData
      if (formData.html) {
        formData.html = sanitizeManualDocumentHtml(formData.html)
      }
      if (Array.isArray(formData.documentOrder)) {
        formData.documentOrder = normalizeDocumentOrder(formData.documentOrder)
      }
      acc[key] = formData
      manualDraftCache.set(`${caseId}:${key}`, formData)
      return acc
    }, {})
  }

  const persistManualDraftData = async (caseId: number, key: RecoveryDocumentKey, draftData: ManualDraftData) => {
    const caseInfo = await fetchCaseInfo(caseId)
    let savedToApi = false
    const normalizedDraftData: ManualDraftData = {
      ...draftData,
      updatedAt: draftData.updatedAt || new Date().toISOString()
    }
    if (normalizedDraftData.html) {
      normalizedDraftData.html = sanitizeManualDocumentHtml(normalizedDraftData.html)
    }
    if (Array.isArray(normalizedDraftData.documentOrder)) {
      normalizedDraftData.documentOrder = normalizeDocumentOrder(normalizedDraftData.documentOrder)
    }

    try {
      await $fetch('/documents/page-drafts', {
        method: 'POST',
        baseURL: config.public.apiBase,
        headers: {
          Authorization: `Bearer ${authStore.token}`
        },
        body: {
          caseId,
          documentType: 'recovery',
          pageId: `${manualPageIdPrefix}${key}`,
          formData: normalizedDraftData,
          summary: {
            applicantName: caseInfo.applicant_name,
            applicantPhone: caseInfo.applicant_phone,
            caseNumber: caseInfo.case_number,
            registrationDate: caseInfo.registration_date
          },
          title: `${caseInfo.applicant_name || '개인회생'} 개인회생`
        }
      })
      savedToApi = true
    } catch (error) {
      if (!isNotFoundError(error)) {
        throw error
      }
    }

    if (savedToApi) {
      try {
        await $fetch(`/cases/${caseId}`, {
          method: 'PUT',
          baseURL: config.public.apiBase,
          headers: {
            Authorization: `Bearer ${authStore.token}`
          },
          body: {
            hasManualEdit: true
          }
        })
      } catch (error) {
        if (!isNotFoundError(error)) {
          throw error
        }
      }
    } else {
      writeLocalManualDraft(caseId, key, normalizedDraftData)
    }

    manualDraftCache.set(`${caseId}:${key}`, normalizedDraftData)
    return normalizedDraftData
  }

  const getManualDocumentOrder = async (caseId: number) => {
    const draft = await fetchManualDraft(caseId, 'all_application')
    return normalizeDocumentOrder(draft?.documentOrder)
  }

  const saveManualDocumentOrder = async (caseId: number, documentOrder: RecoveryDocumentKey[]) => {
    const existingDraft = (await fetchManualDraft(caseId, 'all_application')) || {}
    return persistManualDraftData(caseId, 'all_application', {
      ...existingDraft,
      documentOrder: normalizeDocumentOrder(documentOrder)
    })
  }

  const saveManualDraft = async (caseId: number, key: RecoveryDocumentKey, html: string) => {
    const existingDraft = manualDraftCache.get(`${caseId}:${key}`) || readLocalManualDraft(caseId, key) || {}
    return persistManualDraftData(caseId, key, {
      ...existingDraft,
      html: sanitizeManualDocumentHtml(html)
    })
  }

  const createCreditorsCsv = (pages: Record<string, any>) => {
    const page4 = pages['4'] || {}
    const creditors = Array.isArray(page4?.creditors)
      ? page4.creditors
      : Array.isArray(page4?.creditorList)
        ? page4.creditorList
        : []

    const rows = creditors.map((item: Record<string, any>) => [
      item.creditorName || item.name || '',
      item.claimAmount || item.amount || '',
      item.category || item.type || '',
      item.note || ''
    ])

    const csv = [
      ['채권자명', '채권금액', '구분', '비고'],
      ...rows
    ]
      .map((row) => row.map((cell: string) => `"${String(cell ?? '').replace(/"/g, '""')}"`).join(','))
      .join('\n')

    return `\uFEFF${csv}`
  }

  const buildBodyHtml = (key: RecoveryDocumentKey, context: RecoveryDocumentContext) => {
    const { pages } = context
    const page1 = pages['1'] || {}
    const page2 = pages['2'] || {}
    const page3 = pages['3'] || {}
    const page4 = pages['4'] || {}
    const page5 = pages['5'] || {}
    const page7 = pages['7'] || {}
    const page10 = pages['10'] || {}
    const page11 = pages['11'] || {}

    const sectionMap: Record<Exclude<RecoveryDocumentKey, 'creditors_csv'>, string> = {
      all_application: [
        buildRowsTable('기본 정보', flattenEntries(page1).slice(0, 25)),
        buildRowsTable('주거 및 가족사항', flattenEntries(page2).slice(0, 25)),
        buildRowsTable('생계비 및 변제기준', flattenEntries(page3).slice(0, 25)),
        buildRowsTable('채권자 목록', flattenEntries(page4).slice(0, 40)),
        buildIncomeExpenseTable(page5),
        buildRowsTable('재산 사항', flattenEntries(page7).slice(0, 30)),
        buildRowsTable('추가 재산사항', flattenEntries(page10).slice(0, 30)),
        buildRowsTable('기타 재산/증빙', flattenEntries(page11).slice(0, 30))
      ].join(''),
      creditors: buildRowsTable('채권자 목록', flattenEntries(page4).slice(0, 80)),
      assets: [
        buildRowsTable('재산 사항', flattenEntries(page7).slice(0, 50)),
        buildRowsTable('추가 재산사항', flattenEntries(page10).slice(0, 50)),
        buildRowsTable('기타 재산/증빙', flattenEntries(page11).slice(0, 50))
      ].join(''),
      income_expense: [
        buildRowsTable('생계비 산정 기준', flattenEntries(page3).slice(0, 30)),
        buildIncomeExpenseTable(page5)
      ].join(''),
      statement: [
        buildRowsTable('신청인 기본 정보', flattenEntries(page1).slice(0, 25)),
        buildRowsTable('가족 및 주거', flattenEntries(page2).slice(0, 30)),
        buildRowsTable('현재 생활상황', flattenEntries(page3).slice(0, 30)),
        `<section class="doc-section"><h2>진술 요약</h2><div class="doc-paragraph">${escapeHtml(
          page3?.statementDetail || page2?.livingMemo || '저장된 진술 내용이 없습니다.'
        )}</div></section>`
      ].join(''),
      repayment_plan: [
        buildRowsTable('변제 기준', flattenEntries(page3).slice(0, 30)),
        buildRowsTable('채무 및 이자 현황', flattenEntries(page4).slice(0, 35)),
        buildIncomeExpenseTable(page5)
      ].join(''),
      prohibition: [
        buildRowsTable('신청인 정보', flattenEntries(page1).slice(0, 20)),
        buildRowsTable('채권자 현황', flattenEntries(page4).slice(0, 30)),
        `<section class="doc-section"><h2>신청 취지</h2><div class="doc-paragraph">채권자의 강제집행, 가압류, 독촉절차를 금지하여 신청인의 생계와 회생절차 진행을 보호해 주시기 바랍니다.</div></section>`
      ].join(''),
      suspension: [
        buildRowsTable('신청인 정보', flattenEntries(page1).slice(0, 20)),
        buildRowsTable('채권자 현황', flattenEntries(page4).slice(0, 30)),
        `<section class="doc-section"><h2>신청 취지</h2><div class="doc-paragraph">기존 진행 중인 집행 및 보전처분 절차를 중지하여 회생절차 진행이 가능하도록 허가해 주시기 바랍니다.</div></section>`
      ].join(''),
      copy_submission: [
        buildRowsTable('신청인 정보', flattenEntries(page1).slice(0, 18)),
        buildRowsTable('제출 대상 채권자', flattenEntries(page4).slice(0, 24)),
        `<section class="doc-section"><h2>제출 문구</h2><div class="doc-paragraph">개인회생 관련 부본을 제출하오니 기록에 편철하여 주시기 바랍니다.</div></section>`
      ].join(''),
      filing_certificate: [
        buildRowsTable('접수 정보', [
          ['신청인', page1?.applicantName || context.caseInfo?.applicant_name || '-'],
          ['사건번호', context.caseInfo?.case_number || '-'],
          ['접수예정일', page1?.filingDate || '-'],
          ['관할법원', page2?.courtName || page2?.court || '-']
        ]),
        `<section class="doc-section"><h2>증명 내용</h2><div class="doc-paragraph">위 사건에 관한 개인회생절차 개시신청서가 접수되었음을 증명하여 주시기 바랍니다.</div></section>`
      ].join(''),
      power_of_attorney: [
        buildRowsTable('위임인 정보', flattenEntries(page1).slice(0, 20)),
        `<section class="doc-section"><h2>위임 내용</h2><div class="doc-paragraph">개인회생 사건에 관한 서류 작성, 제출 및 보정 대응 등 일체의 절차를 대리인에게 위임합니다.</div></section>`
      ].join(''),
      trustee_debt: [
        buildRowsTable('채권 및 변제 기준', flattenEntries(page4).slice(0, 35)),
        buildIncomeExpenseTable(page5),
        `<section class="doc-section"><h2>재단채권 변제 요약</h2><div class="doc-paragraph">재단채권은 절차 비용 및 우선 변제 필요성을 고려하여 변제계획에 따라 순차 변제합니다.</div></section>`
      ].join(''),
      confirmation: [
        buildRowsTable('확인 대상 정보', flattenEntries(page1).slice(0, 20)),
        buildRowsTable('주거 및 재산 정보', flattenEntries({ ...page2, ...page7 }).slice(0, 30)),
        `<section class="doc-section"><h2>확인 문구</h2><div class="doc-paragraph">본인은 제출한 개인회생 관련 자료가 사실과 다름없음을 확인합니다.</div></section>`
      ].join('')
    }

    return sectionMap[key as Exclude<RecoveryDocumentKey, 'creditors_csv'>] || buildRowsTable('문서 내용', [['안내', '지원되지 않는 문서입니다.']])
  }

  const buildEditorHtml = (key: RecoveryDocumentKey, context: RecoveryDocumentContext) => {
    let editorHtml: string

    switch (key) {
      case 'creditors':
        editorHtml = buildCreditorsReferenceBody(context)
        break
      case 'all_application':
        {
          const orderedDocumentKeys = resolveAllApplicationDocumentKeys(context)
        editorHtml = buildAllDocumentsConcatenated(
          [
            buildAllApplicationSummaryBody(context),
            buildAllApplicationCoverBody(context),
            buildAllApplicationOpeningBody(context),
              ...orderedDocumentKeys.map((documentKey) => {
              const manualHtml = context.manualDrafts[documentKey]?.html
              const generatedHtml = buildEditorHtml(documentKey, context)
              const resolvedHtml = shouldPreferGeneratedHtml(documentKey, manualHtml, context)
                ? generatedHtml
                : manualHtml || generatedHtml
              const trimmedHtml = trimSectionPageBreakEdges(resolvedHtml)
              return shouldIncludeConcatenatedSection(documentKey, trimmedHtml) ? trimmedHtml : ''
            })
          ].filter(Boolean)
        )
        break
        }
      case 'assets':
        editorHtml = buildAssetsReferenceBody(context)
        break
      case 'income_expense':
        editorHtml = buildIncomeExpenseReferenceBody(context)
        break
      case 'statement':
        editorHtml = buildStatementReferenceBody(context)
        break
      case 'repayment_plan':
        editorHtml = buildRepaymentPlanReferenceBody(context)
        break
      case 'prohibition':
        editorHtml = buildProhibitionReferenceBody(context)
        break
      case 'trustee_debt':
        editorHtml = buildTrusteeDebtReferenceBody(context)
        break
      case 'power_of_attorney':
        editorHtml = buildPowerOfAttorneyReferenceBody(context)
        break
      case 'suspension':
        editorHtml = buildSuspensionReferenceBody(context)
        break
      case 'copy_submission':
        editorHtml = buildCopySubmissionReferenceBody(context)
        break
      case 'filing_certificate':
        editorHtml = buildFilingCertificateReferenceBody(context)
        break
      case 'confirmation':
        editorHtml = buildConfirmationReferenceBody(context)
        break
      case 'creditors_csv':
        editorHtml = buildBodyHtml(key, context)
        break
      default:
        editorHtml = buildBlankDocumentBody(getDocumentDefinition(key).manualLabel, context)
    }

    return withDocumentFingerprintMarker(key, context, normalizePageBreakMarkup(editorHtml))
  }

  const buildDocumentHtml = (key: RecoveryDocumentKey, context: RecoveryDocumentContext) => {
    const definition = getDocumentDefinition(key)
    const manualHtml = context.manualDrafts[key]?.html
    const editorHtml = shouldPreferGeneratedHtml(key, manualHtml, context)
      ? buildEditorHtml(key, context)
      : manualHtml || buildEditorHtml(key, context)

    if (referenceDocumentKeys.has(key)) {
      return buildReferenceDocumentShell(definition.manualLabel, editorHtml)
    }

    return buildDocumentShell(definition.manualLabel, context.caseInfo, editorHtml)
  }

  const buildGeneratedDocumentHtml = (key: RecoveryDocumentKey, context: RecoveryDocumentContext) => {
    const definition = getDocumentDefinition(key)
    const editorHtml = buildEditorHtml(key, context)

    if (referenceDocumentKeys.has(key)) {
      return buildReferenceDocumentShell(definition.manualLabel, editorHtml)
    }

    return buildDocumentShell(definition.manualLabel, context.caseInfo, editorHtml)
  }

  const resolveGeneratedEditorHtml = async (key: RecoveryDocumentKey, context: RecoveryDocumentContext) => {
    if (key === 'assets') {
      return withDocumentFingerprintMarker(key, context, await buildAssetsReferenceBodyAsync(context))
    }

    return buildEditorHtml(key, context)
  }

  const resolveDocumentEditorHtml = async (
    key: RecoveryDocumentKey,
    context: RecoveryDocumentContext,
    preferManualDraft: boolean
  ) => {
    const generatedHtml = await resolveGeneratedEditorHtml(key, context)
    if (!preferManualDraft) {
      return generatedHtml
    }

    const manualHtml = context.manualDrafts[key]?.html
    return shouldPreferGeneratedHtml(key, manualHtml, context)
      ? generatedHtml
      : manualHtml || generatedHtml
  }

  const buildAllApplicationEditorHtml = async (
    context: RecoveryDocumentContext,
    preferManualDraft: boolean
  ) => {
  const summarySection = trimSectionPageBreakEdges(buildAllApplicationSummaryBody(context))
  const coverSection = trimSectionPageBreakEdges(buildAllApplicationCoverBody(context))
  const openingSection = trimSectionPageBreakEdges(buildAllApplicationOpeningBody(context))
  const orderedDocumentKeys = resolveAllApplicationDocumentKeys(context)
  const sections = (await Promise.all(
      orderedDocumentKeys.map(async (documentKey) => {
        const sectionHtml = await resolveDocumentEditorHtml(documentKey, context, preferManualDraft)
        const trimmedHtml = trimSectionPageBreakEdges(sectionHtml)
        return shouldIncludeConcatenatedSection(documentKey, trimmedHtml) ? trimmedHtml : ''
      })
    )).filter(Boolean)

    return buildAllDocumentsConcatenated([
      summarySection,
      coverSection,
      openingSection,
      ...sections
    ].filter(Boolean))
  }

  const loadDocumentContext = async (caseId: number, key?: RecoveryDocumentKey) => {
    const definition = key ? getDocumentDefinition(key) : null
    const shouldLoadAllManualDrafts = key === 'all_application'
    const pageIdsToLoad = shouldLoadAllManualDrafts
      ? Array.from(
          new Set(
            DEFAULT_ALL_APPLICATION_DOCUMENT_KEYS.flatMap((documentKey) => getDocumentDefinition(documentKey).pageIds.map(String))
          )
        )
      : definition?.pageIds
    const [caseInfo, pages, manualDraft, manualDrafts] = await Promise.all([
      fetchCaseInfo(caseId),
      fetchPageDrafts(caseId, pageIdsToLoad),
      key && !shouldLoadAllManualDrafts ? fetchManualDraft(caseId, key) : Promise.resolve(null),
      shouldLoadAllManualDrafts ? fetchManualDrafts(caseId) : Promise.resolve({})
    ])

    return {
      caseInfo,
      pages,
      manualDrafts: shouldLoadAllManualDrafts
        ? manualDrafts
        : key && manualDraft
          ? { [key]: manualDraft }
          : {}
    }
  }

  const getDocumentHtml = async (caseId: number, key: RecoveryDocumentKey) => {
    const context = await loadDocumentContext(caseId, key)
    if (key === 'all_application') {
      const definition = getDocumentDefinition(key)
      const editorHtml = await buildAllApplicationEditorHtml(context, true)
      return buildReferenceDocumentShell(definition.manualLabel, editorHtml)
    }
    if (key === 'assets') {
      const definition = getDocumentDefinition(key)
      const editorHtml = await resolveGeneratedEditorHtml(key, context)
      return buildReferenceDocumentShell(definition.manualLabel, editorHtml)
    }
    return buildDocumentHtml(key, context)
  }

  const getGeneratedDocumentHtml = async (caseId: number, key: RecoveryDocumentKey) => {
    const context = await loadDocumentContext(caseId, key)
    if (key === 'all_application') {
      const definition = getDocumentDefinition(key)
      const editorHtml = await buildAllApplicationEditorHtml(context, false)
      return buildReferenceDocumentShell(definition.manualLabel, editorHtml)
    }
    if (key === 'assets') {
      const definition = getDocumentDefinition(key)
      const editorHtml = await resolveGeneratedEditorHtml(key, context)
      return buildReferenceDocumentShell(definition.manualLabel, editorHtml)
    }
    return buildGeneratedDocumentHtml(key, context)
  }

  const getDocumentEditorHtml = async (caseId: number, key: RecoveryDocumentKey) => {
    const context = await loadDocumentContext(caseId, key)
    if (key === 'all_application') {
      return buildAllApplicationEditorHtml(context, true)
    }
    const manualHtml = context.manualDrafts[key]?.html
    const generatedHtml = key === 'assets'
      ? await resolveGeneratedEditorHtml(key, context)
      : buildEditorHtml(key, context)
    return shouldPreferGeneratedHtml(key, manualHtml, context)
      ? generatedHtml
      : manualHtml || generatedHtml
  }

  const getGeneratedDocumentEditorHtml = async (caseId: number, key: RecoveryDocumentKey) => {
    const context = await loadDocumentContext(caseId, key)
    if (key === 'all_application') {
      return buildAllApplicationEditorHtml(context, false)
    }
    if (key === 'assets') {
      return resolveGeneratedEditorHtml(key, context)
    }
    return buildEditorHtml(key, context)
  }

  const loadDocumentEditorState = async (caseId: number, key: RecoveryDocumentKey) => {
    const context = await loadDocumentContext(caseId, key)
    if (key === 'all_application') {
      return {
        caseInfo: context.caseInfo,
        html: await buildAllApplicationEditorHtml(context, true)
      }
    }
    const manualHtml = context.manualDrafts[key]?.html
    const shouldIgnoreManualDraft = shouldPreferGeneratedHtml(key, manualHtml, context)
    const generatedHtml = key === 'assets'
      ? await resolveGeneratedEditorHtml(key, context)
      : buildEditorHtml(key, context)

    return {
      caseInfo: context.caseInfo,
      html: shouldIgnoreManualDraft ? generatedHtml : manualHtml || generatedHtml
    }
  }

  return {
    createCreditorsCsv,
    documentDefinitions,
    fetchCaseInfo,
    fetchManualDraft,
    getManualDocumentOrder,
    getDocumentDefinition,
    getDocumentHtml,
    getDocumentEditorHtml,
    getGeneratedDocumentHtml,
    getGeneratedDocumentEditorHtml,
    loadDocumentEditorState,
    loadDocumentContext,
    manualPageIdPrefix,
    saveManualDocumentOrder,
    sanitizeFileName,
    saveManualDraft,
    stripHtml
  }
}

