import JSZip from 'jszip';

// ─── XML 이스케이프 ──────────────────────────────────────────
function esc(str: string): string {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

// ─── ID 생성 ─────────────────────────────────────────────────
let _idSeq = 1000000000;
function nextId(): number {
  return _idSeq++;
}

// ─── 타입 ────────────────────────────────────────────────────
interface ReportSection {
  title: string;
  fields: { key: string; label: string }[];
}

interface ReportMeta {
  writeTime: string;
  author: string;
  jurisdiction: string;
}

// ─── 정적 파일 (Skeleton.hwpx 기반) ─────────────────────────

/**
 * mimetype: STORE (무압축)로 추가해야 함
 */
const MIMETYPE = 'application/hwp+zip';

/**
 * version.xml: Skeleton.hwpx 그대로
 */
const VERSION_XML =
  '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>' +
  '<hv:HCFVersion xmlns:hv="http://www.hancom.co.kr/hwpml/2011/version" ' +
  'tagetApplication="WORDPROCESSOR" major="5" minor="1" micro="1" buildNumber="0" ' +
  'os="1" xmlVersion="1.5" application="Hancom Office Hangul" ' +
  'appVersion="13, 0, 0, 1408 WIN32LEWindows_10"/>';

/**
 * settings.xml: Skeleton.hwpx 그대로
 */
const SETTINGS_XML =
  '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>' +
  '<ha:HWPApplicationSetting xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app" ' +
  'xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0">' +
  '<ha:CaretPosition listIDRef="0" paraIDRef="0" pos="16"/>' +
  '</ha:HWPApplicationSetting>';

/**
 * META-INF/container.xml: Skeleton.hwpx 그대로
 */
const CONTAINER_XML =
  '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>' +
  '<ocf:container xmlns:ocf="urn:oasis:names:tc:opendocument:xmlns:container" ' +
  'xmlns:hpf="http://www.hancom.co.kr/schema/2011/hpf">' +
  '<ocf:rootfiles>' +
  '<ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/>' +
  '<ocf:rootfile full-path="Preview/PrvText.txt" media-type="text/plain"/>' +
  '<ocf:rootfile full-path="META-INF/container.rdf" media-type="application/rdf+xml"/>' +
  '</ocf:rootfiles>' +
  '</ocf:container>';

/**
 * META-INF/container.rdf: Skeleton.hwpx 그대로
 */
const CONTAINER_RDF =
  '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>' +
  '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">' +
  '<rdf:Description rdf:about="">' +
  '<ns0:hasPart xmlns:ns0="http://www.hancom.co.kr/hwpml/2016/meta/pkg#" rdf:resource="Contents/header.xml"/>' +
  '</rdf:Description>' +
  '<rdf:Description rdf:about="Contents/header.xml">' +
  '<rdf:type rdf:resource="http://www.hancom.co.kr/hwpml/2016/meta/pkg#HeaderFile"/>' +
  '</rdf:Description>' +
  '<rdf:Description rdf:about="">' +
  '<ns0:hasPart xmlns:ns0="http://www.hancom.co.kr/hwpml/2016/meta/pkg#" rdf:resource="Contents/section0.xml"/>' +
  '</rdf:Description>' +
  '<rdf:Description rdf:about="Contents/section0.xml">' +
  '<rdf:type rdf:resource="http://www.hancom.co.kr/hwpml/2016/meta/pkg#SectionFile"/>' +
  '</rdf:Description>' +
  '<rdf:Description rdf:about="">' +
  '<rdf:type rdf:resource="http://www.hancom.co.kr/hwpml/2016/meta/pkg#Document"/>' +
  '</rdf:Description>' +
  '</rdf:RDF>';

/**
 * META-INF/manifest.xml: Skeleton.hwpx 그대로
 */
const MANIFEST_XML =
  '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>' +
  '<odf:manifest xmlns:odf="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"/>';

/**
 * Contents/content.hpf: Skeleton.hwpx 기반, 날짜만 동적 생성
 */
function buildContentHpf(extraManifestItems = ''): string {
  const now = new Date().toISOString();
  return (
    '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>' +
    '<opf:package xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app" ' +
    'xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" ' +
    'xmlns:hp10="http://www.hancom.co.kr/hwpml/2016/paragraph" ' +
    'xmlns:hs="http://www.hancom.co.kr/hwpml/2011/section" ' +
    'xmlns:hc="http://www.hancom.co.kr/hwpml/2011/core" ' +
    'xmlns:hh="http://www.hancom.co.kr/hwpml/2011/head" ' +
    'xmlns:hhs="http://www.hancom.co.kr/hwpml/2011/history" ' +
    'xmlns:hm="http://www.hancom.co.kr/hwpml/2011/master-page" ' +
    'xmlns:hpf="http://www.hancom.co.kr/schema/2011/hpf" ' +
    'xmlns:dc="http://purl.org/dc/elements/1.1/" ' +
    'xmlns:opf="http://www.idpf.org/2007/opf/" ' +
    'xmlns:ooxmlchart="http://www.hancom.co.kr/hwpml/2016/ooxmlchart" ' +
    'xmlns:hwpunitchar="http://www.hancom.co.kr/hwpml/2016/HwpUnitChar" ' +
    'xmlns:epub="http://www.idpf.org/2007/ops" ' +
    'xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" ' +
    'version="" unique-identifier="" id="">' +
    '<opf:metadata>' +
    '<opf:title/>' +
    '<opf:language>ko</opf:language>' +
    '<opf:meta name="creator" content="text">WING-OPS</opf:meta>' +
    '<opf:meta name="subject" content="text"/>' +
    '<opf:meta name="description" content="text"/>' +
    '<opf:meta name="lastsaveby" content="text">WING-OPS</opf:meta>' +
    `<opf:meta name="CreatedDate" content="text">${now}</opf:meta>` +
    `<opf:meta name="ModifiedDate" content="text">${now}</opf:meta>` +
    '</opf:metadata>' +
    '<opf:manifest>' +
    '<opf:item id="header" href="Contents/header.xml" media-type="application/xml"/>' +
    '<opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/>' +
    '<opf:item id="settings" href="settings.xml" media-type="application/xml"/>' +
    extraManifestItems +
    '</opf:manifest>' +
    '<opf:spine>' +
    '<opf:itemref idref="header" linear="yes"/>' +
    '<opf:itemref idref="section0" linear="yes"/>' +
    '</opf:spine>' +
    '</opf:package>'
  );
}

/**
 * Contents/header.xml: Skeleton.hwpx 그대로 (완전 복사)
 * charPr 정의:
 *   id=0: 10pt, 검정 (기본 본문)
 *   id=1: 10pt, 검정 (바탕체 변형 - 현재 동일)
 *   id=2: 9pt, 검정
 *   id=3: 9pt, 검정 (함초롬돋움)
 *   id=4: 9pt, 검정 (자간 -5)
 *   id=5: 16pt, 파란색 (#2E74B5)
 *   id=6: 11pt, 검정
 */
const HEADER_XML =
  '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>' +
  '<hh:head xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app" ' +
  'xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" ' +
  'xmlns:hp10="http://www.hancom.co.kr/hwpml/2016/paragraph" ' +
  'xmlns:hs="http://www.hancom.co.kr/hwpml/2011/section" ' +
  'xmlns:hc="http://www.hancom.co.kr/hwpml/2011/core" ' +
  'xmlns:hh="http://www.hancom.co.kr/hwpml/2011/head" ' +
  'xmlns:hhs="http://www.hancom.co.kr/hwpml/2011/history" ' +
  'xmlns:hm="http://www.hancom.co.kr/hwpml/2011/master-page" ' +
  'xmlns:hpf="http://www.hancom.co.kr/schema/2011/hpf" ' +
  'xmlns:dc="http://purl.org/dc/elements/1.1/" ' +
  'xmlns:opf="http://www.idpf.org/2007/opf/" ' +
  'xmlns:ooxmlchart="http://www.hancom.co.kr/hwpml/2016/ooxmlchart" ' +
  'xmlns:hwpunitchar="http://www.hancom.co.kr/hwpml/2016/HwpUnitChar" ' +
  'xmlns:epub="http://www.idpf.org/2007/ops" ' +
  'xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" ' +
  'version="1.5" secCnt="1">' +
  '<hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>' +
  '<hh:refList>' +
  // ── 폰트 (함초롬돋움/함초롬바탕, 7개 언어 그룹)
  '<hh:fontfaces itemCnt="7">' +
  '<hh:fontface lang="HANGUL" fontCnt="2">' +
  '<hh:font id="0" face="함초롬돋움" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '<hh:font id="1" face="함초롬바탕" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '</hh:fontface>' +
  '<hh:fontface lang="LATIN" fontCnt="2">' +
  '<hh:font id="0" face="함초롬돋움" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '<hh:font id="1" face="함초롬바탕" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '</hh:fontface>' +
  '<hh:fontface lang="HANJA" fontCnt="2">' +
  '<hh:font id="0" face="함초롬돋움" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '<hh:font id="1" face="함초롬바탕" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '</hh:fontface>' +
  '<hh:fontface lang="JAPANESE" fontCnt="2">' +
  '<hh:font id="0" face="함초롬돋움" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '<hh:font id="1" face="함초롬바탕" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '</hh:fontface>' +
  '<hh:fontface lang="OTHER" fontCnt="2">' +
  '<hh:font id="0" face="함초롬돋움" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '<hh:font id="1" face="함초롬바탕" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '</hh:fontface>' +
  '<hh:fontface lang="SYMBOL" fontCnt="2">' +
  '<hh:font id="0" face="함초롬돋움" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '<hh:font id="1" face="함초롬바탕" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '</hh:fontface>' +
  '<hh:fontface lang="USER" fontCnt="2">' +
  '<hh:font id="0" face="함초롬돋움" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '<hh:font id="1" face="함초롬바탕" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font>' +
  '</hh:fontface>' +
  '</hh:fontfaces>' +
  // ── borderFill
  // id=1: 기본 (테두리 없음)
  // id=2: 기본 + fillBrush 없음
  // id=3: 테이블 값 셀 (실선 테두리)
  // id=4: 테이블 라벨 셀 (실선 테두리 + 배경색 #f0f4f8)
  '<hh:borderFills itemCnt="4">' +
  '<hh:borderFill id="1" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">' +
  '<hh:slash type="NONE" Crooked="0" isCounter="0"/>' +
  '<hh:backSlash type="NONE" Crooked="0" isCounter="0"/>' +
  '<hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>' +
  '<hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>' +
  '<hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>' +
  '<hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/>' +
  '<hh:diagonal type="SOLID" width="0.1 mm" color="#000000"/>' +
  '</hh:borderFill>' +
  '<hh:borderFill id="2" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">' +
  '<hh:slash type="NONE" Crooked="0" isCounter="0"/>' +
  '<hh:backSlash type="NONE" Crooked="0" isCounter="0"/>' +
  '<hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>' +
  '<hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>' +
  '<hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>' +
  '<hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/>' +
  '<hh:diagonal type="SOLID" width="0.1 mm" color="#000000"/>' +
  '<hc:fillBrush><hc:winBrush faceColor="none" hatchColor="#999999" alpha="0"/></hc:fillBrush>' +
  '</hh:borderFill>' +
  // id=3: 값 셀 — 실선 테두리, 배경 없음
  '<hh:borderFill id="3" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">' +
  '<hh:slash type="NONE" Crooked="0" isCounter="0"/>' +
  '<hh:backSlash type="NONE" Crooked="0" isCounter="0"/>' +
  '<hh:leftBorder type="SOLID" width="0.12 mm" color="#d1d5db"/>' +
  '<hh:rightBorder type="SOLID" width="0.12 mm" color="#d1d5db"/>' +
  '<hh:topBorder type="SOLID" width="0.12 mm" color="#d1d5db"/>' +
  '<hh:bottomBorder type="SOLID" width="0.12 mm" color="#d1d5db"/>' +
  '<hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>' +
  '</hh:borderFill>' +
  // id=4: 라벨 셀 — 실선 테두리 + 연한 파란 배경
  '<hh:borderFill id="4" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">' +
  '<hh:slash type="NONE" Crooked="0" isCounter="0"/>' +
  '<hh:backSlash type="NONE" Crooked="0" isCounter="0"/>' +
  '<hh:leftBorder type="SOLID" width="0.12 mm" color="#d1d5db"/>' +
  '<hh:rightBorder type="SOLID" width="0.12 mm" color="#d1d5db"/>' +
  '<hh:topBorder type="SOLID" width="0.12 mm" color="#d1d5db"/>' +
  '<hh:bottomBorder type="SOLID" width="0.12 mm" color="#d1d5db"/>' +
  '<hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>' +
  '<hc:fillBrush><hc:winBrush faceColor="#f0f4f8" hatchColor="#999999" alpha="0"/></hc:fillBrush>' +
  '</hh:borderFill>' +
  '</hh:borderFills>' +
  // ── charProperties (Skeleton 7개 그대로)
  '<hh:charProperties itemCnt="7">' +
  '<hh:charPr id="0" height="1000" textColor="#000000" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="2">' +
  '<hh:fontRef hangul="1" latin="1" hanja="1" japanese="1" other="1" symbol="1" user="1"/>' +
  '<hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:underline type="NONE" shape="SOLID" color="#000000"/>' +
  '<hh:strikeout shape="NONE" color="#000000"/>' +
  '<hh:outline type="NONE"/>' +
  '<hh:shadow type="NONE" color="#C0C0C0" offsetX="10" offsetY="10"/>' +
  '</hh:charPr>' +
  '<hh:charPr id="1" height="1000" textColor="#000000" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="2">' +
  '<hh:fontRef hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:underline type="NONE" shape="SOLID" color="#000000"/>' +
  '<hh:strikeout shape="NONE" color="#000000"/>' +
  '<hh:outline type="NONE"/>' +
  '<hh:shadow type="NONE" color="#C0C0C0" offsetX="10" offsetY="10"/>' +
  '</hh:charPr>' +
  '<hh:charPr id="2" height="900" textColor="#000000" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="2">' +
  '<hh:fontRef hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:underline type="NONE" shape="SOLID" color="#000000"/>' +
  '<hh:strikeout shape="NONE" color="#000000"/>' +
  '<hh:outline type="NONE"/>' +
  '<hh:shadow type="NONE" color="#C0C0C0" offsetX="10" offsetY="10"/>' +
  '</hh:charPr>' +
  '<hh:charPr id="3" height="900" textColor="#000000" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="2">' +
  '<hh:fontRef hangul="1" latin="1" hanja="1" japanese="1" other="1" symbol="1" user="1"/>' +
  '<hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:underline type="NONE" shape="SOLID" color="#000000"/>' +
  '<hh:strikeout shape="NONE" color="#000000"/>' +
  '<hh:outline type="NONE"/>' +
  '<hh:shadow type="NONE" color="#C0C0C0" offsetX="10" offsetY="10"/>' +
  '</hh:charPr>' +
  '<hh:charPr id="4" height="900" textColor="#000000" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="2">' +
  '<hh:fontRef hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:spacing hangul="-5" latin="-5" hanja="-5" japanese="-5" other="-5" symbol="-5" user="-5"/>' +
  '<hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:underline type="NONE" shape="SOLID" color="#000000"/>' +
  '<hh:strikeout shape="NONE" color="#000000"/>' +
  '<hh:outline type="NONE"/>' +
  '<hh:shadow type="NONE" color="#C0C0C0" offsetX="10" offsetY="10"/>' +
  '</hh:charPr>' +
  // charPr id=5: 16pt 파란색 (제목용으로 활용)
  '<hh:charPr id="5" height="1600" textColor="#2E74B5" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="2">' +
  '<hh:fontRef hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:underline type="NONE" shape="SOLID" color="#000000"/>' +
  '<hh:strikeout shape="NONE" color="#000000"/>' +
  '<hh:outline type="NONE"/>' +
  '<hh:shadow type="NONE" color="#C0C0C0" offsetX="10" offsetY="10"/>' +
  '</hh:charPr>' +
  // charPr id=6: 11pt 검정
  '<hh:charPr id="6" height="1100" textColor="#000000" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="2">' +
  '<hh:fontRef hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>' +
  '<hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>' +
  '<hh:underline type="NONE" shape="SOLID" color="#000000"/>' +
  '<hh:strikeout shape="NONE" color="#000000"/>' +
  '<hh:outline type="NONE"/>' +
  '<hh:shadow type="NONE" color="#C0C0C0" offsetX="10" offsetY="10"/>' +
  '</hh:charPr>' +
  '</hh:charProperties>' +
  // ── tabProperties
  '<hh:tabProperties itemCnt="3">' +
  '<hh:tabPr id="0" autoTabLeft="0" autoTabRight="0"/>' +
  '<hh:tabPr id="1" autoTabLeft="1" autoTabRight="0"/>' +
  '<hh:tabPr id="2" autoTabLeft="0" autoTabRight="1"/>' +
  '</hh:tabProperties>' +
  // ── numberings (Skeleton 그대로)
  '<hh:numberings itemCnt="1">' +
  '<hh:numbering id="1" start="0">' +
  '<hh:paraHead start="1" level="1" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="DIGIT" charPrIDRef="4294967295" checkable="0">^1.</hh:paraHead>' +
  '<hh:paraHead start="1" level="2" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="HANGUL_SYLLABLE" charPrIDRef="4294967295" checkable="0">^2.</hh:paraHead>' +
  '<hh:paraHead start="1" level="3" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="DIGIT" charPrIDRef="4294967295" checkable="0">^3)</hh:paraHead>' +
  '<hh:paraHead start="1" level="4" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="HANGUL_SYLLABLE" charPrIDRef="4294967295" checkable="0">^4)</hh:paraHead>' +
  '<hh:paraHead start="1" level="5" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="DIGIT" charPrIDRef="4294967295" checkable="0">(^5)</hh:paraHead>' +
  '<hh:paraHead start="1" level="6" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="HANGUL_SYLLABLE" charPrIDRef="4294967295" checkable="0">(^6)</hh:paraHead>' +
  '<hh:paraHead start="1" level="7" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="CIRCLED_DIGIT" charPrIDRef="4294967295" checkable="1">^7</hh:paraHead>' +
  '<hh:paraHead start="1" level="8" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="CIRCLED_HANGUL_SYLLABLE" charPrIDRef="4294967295" checkable="1">^8</hh:paraHead>' +
  '<hh:paraHead start="1" level="9" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="HANGUL_JAMO" charPrIDRef="4294967295" checkable="0"/>' +
  '<hh:paraHead start="1" level="10" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="ROMAN_SMALL" charPrIDRef="4294967295" checkable="1"/>' +
  '</hh:numbering>' +
  '</hh:numberings>' +
  // ── paraProperties (Skeleton id=0만 사용)
  '<hh:paraProperties itemCnt="1">' +
  '<hh:paraPr id="0" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="1" suppressLineNumbers="0" checked="0" textDir="LTR">' +
  '<hh:align horizontal="JUSTIFY" vertical="BASELINE"/>' +
  '<hh:heading type="NONE" idRef="0" level="0"/>' +
  '<hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="BREAK_WORD" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>' +
  '<hh:autoSpacing eAsianEng="0" eAsianNum="0"/>' +
  '<hp:switch>' +
  '<hp:case hp:required-namespace="http://www.hancom.co.kr/hwpml/2016/HwpUnitChar">' +
  '<hh:margin><hc:intent value="0" unit="HWPUNIT"/><hc:left value="0" unit="HWPUNIT"/><hc:right value="0" unit="HWPUNIT"/><hc:prev value="0" unit="HWPUNIT"/><hc:next value="0" unit="HWPUNIT"/></hh:margin>' +
  '<hh:lineSpacing type="PERCENT" value="160" unit="HWPUNIT"/>' +
  '</hp:case>' +
  '<hp:default>' +
  '<hh:margin><hc:intent value="0" unit="HWPUNIT"/><hc:left value="0" unit="HWPUNIT"/><hc:right value="0" unit="HWPUNIT"/><hc:prev value="0" unit="HWPUNIT"/><hc:next value="0" unit="HWPUNIT"/></hh:margin>' +
  '<hh:lineSpacing type="PERCENT" value="160" unit="HWPUNIT"/>' +
  '</hp:default>' +
  '</hp:switch>' +
  '<hh:border borderFillIDRef="2" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>' +
  '</hh:paraPr>' +
  '</hh:paraProperties>' +
  // ── styles (Skeleton 바탕글 스타일만)
  '<hh:styles itemCnt="1">' +
  '<hh:style id="0" type="PARA" name="바탕글" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langID="1042" lockForm="0"/>' +
  '</hh:styles>' +
  '</hh:refList>' +
  '<hh:compatibleDocument targetProgram="HWP201X"><hh:layoutCompatibility/></hh:compatibleDocument>' +
  '<hh:docOption><hh:linkinfo path="" pageInherit="0" footnoteInherit="0"/></hh:docOption>' +
  '<hh:metaTag>{"name":""}</hh:metaTag>' +
  '<hh:trackchageConfig flags="56"/>' +
  '</hh:head>';

// ─── 공통 네임스페이스 선언 (section0.xml 루트 속성) ─────────
const SEC_NS =
  'xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app" ' +
  'xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" ' +
  'xmlns:hp10="http://www.hancom.co.kr/hwpml/2016/paragraph" ' +
  'xmlns:hs="http://www.hancom.co.kr/hwpml/2011/section" ' +
  'xmlns:hc="http://www.hancom.co.kr/hwpml/2011/core" ' +
  'xmlns:hh="http://www.hancom.co.kr/hwpml/2011/head" ' +
  'xmlns:hhs="http://www.hancom.co.kr/hwpml/2011/history" ' +
  'xmlns:hm="http://www.hancom.co.kr/hwpml/2011/master-page" ' +
  'xmlns:hpf="http://www.hancom.co.kr/schema/2011/hpf" ' +
  'xmlns:dc="http://purl.org/dc/elements/1.1/" ' +
  'xmlns:opf="http://www.idpf.org/2007/opf/" ' +
  'xmlns:ooxmlchart="http://www.hancom.co.kr/hwpml/2016/ooxmlchart" ' +
  'xmlns:hwpunitchar="http://www.hancom.co.kr/hwpml/2016/HwpUnitChar" ' +
  'xmlns:epub="http://www.idpf.org/2007/ops" ' +
  'xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0"';

// ─── section0.xml 본문 빌더 ──────────────────────────────────

/**
 * 첫 번째 단락: secPr (페이지 속성) + colPr 포함
 * Skeleton.hwpx의 첫 단락을 그대로 재현
 */
function buildSecPrParagraph(): string {
  const id = nextId();
  return (
    `<hp:p id="${id}" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">` +
    '<hp:run charPrIDRef="0">' +
    '<hp:secPr id="" textDirection="HORIZONTAL" spaceColumns="1134" tabStop="8000" ' +
    'tabStopVal="4000" tabStopUnit="HWPUNIT" outlineShapeIDRef="1" memoShapeIDRef="0" ' +
    'textVerticalWidthHead="0" masterPageCnt="0">' +
    '<hp:grid lineGrid="0" charGrid="0" wonggojiFormat="0"/>' +
    '<hp:startNum pageStartsOn="BOTH" page="0" pic="0" tbl="0" equation="0"/>' +
    '<hp:visibility hideFirstHeader="0" hideFirstFooter="0" hideFirstMasterPage="0" ' +
    'border="SHOW_ALL" fill="SHOW_ALL" hideFirstPageNum="0" hideFirstEmptyLine="0" showLineNumber="0"/>' +
    '<hp:lineNumberShape restartType="0" countBy="0" distance="0" startNumber="0"/>' +
    '<hp:pagePr landscape="WIDELY" width="59528" height="84186" gutterType="LEFT_ONLY">' +
    '<hp:margin header="4252" footer="4252" gutter="0" left="8504" right="8504" top="5668" bottom="4252"/>' +
    '</hp:pagePr>' +
    '<hp:footNotePr>' +
    '<hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/>' +
    '<hp:noteLine length="-1" type="SOLID" width="0.12 mm" color="#000000"/>' +
    '<hp:noteSpacing betweenNotes="283" belowLine="567" aboveLine="850"/>' +
    '<hp:numbering type="CONTINUOUS" newNum="1"/>' +
    '<hp:placement place="EACH_COLUMN" beneathText="0"/>' +
    '</hp:footNotePr>' +
    '<hp:endNotePr>' +
    '<hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/>' +
    '<hp:noteLine length="14692344" type="SOLID" width="0.12 mm" color="#000000"/>' +
    '<hp:noteSpacing betweenNotes="0" belowLine="567" aboveLine="850"/>' +
    '<hp:numbering type="CONTINUOUS" newNum="1"/>' +
    '<hp:placement place="END_OF_DOCUMENT" beneathText="0"/>' +
    '</hp:endNotePr>' +
    '<hp:pageBorderFill type="BOTH" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER">' +
    '<hp:offset left="1417" right="1417" top="1417" bottom="1417"/>' +
    '</hp:pageBorderFill>' +
    '<hp:pageBorderFill type="EVEN" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER">' +
    '<hp:offset left="1417" right="1417" top="1417" bottom="1417"/>' +
    '</hp:pageBorderFill>' +
    '<hp:pageBorderFill type="ODD" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER">' +
    '<hp:offset left="1417" right="1417" top="1417" bottom="1417"/>' +
    '</hp:pageBorderFill>' +
    '</hp:secPr>' +
    '<hp:ctrl>' +
    '<hp:colPr id="" type="NEWSPAPER" layout="LEFT" colCount="1" sameSz="1" sameGap="0"/>' +
    '</hp:ctrl>' +
    '</hp:run>' +
    '<hp:run charPrIDRef="0"><hp:t/></hp:run>' +
    '<hp:linesegarray>' +
    '<hp:lineseg textpos="0" vertpos="0" vertsize="1000" textheight="1000" baseline="850" ' +
    'spacing="600" horzpos="0" horzsize="42520" flags="393216"/>' +
    '</hp:linesegarray>' +
    '</hp:p>'
  );
}

/**
 * 텍스트 단락 생성 (charPrIDRef=0 기본 10pt)
 */
function buildPara(text: string, charPrId = 0): string {
  const id = nextId();
  return (
    `<hp:p id="${id}" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">` +
    `<hp:run charPrIDRef="${charPrId}">` +
    `<hp:t>${esc(text)}</hp:t>` +
    '</hp:run>' +
    '</hp:p>'
  );
}

/**
 * 빈 단락 (줄바꿈용)
 */
function buildEmptyPara(): string {
  const id = nextId();
  return (
    `<hp:p id="${id}" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">` +
    '<hp:run charPrIDRef="0"><hp:t/></hp:run>' +
    '</hp:p>'
  );
}

/**
 * 이미지(인라인 그림) 단락 생성
 * binDataId: hh:binData id 값, widthHwp/heightHwp: HWPUNIT 크기
 */
function buildPicParagraph(binDataId: number, widthHwp: number, heightHwp: number): string {
  const pId = nextId();
  const picId = nextId();
  const fileId = `image${binDataId}`;
  const halfW = Math.round(widthHwp / 2);
  const halfH = Math.round(heightHwp / 2);
  return (
    `<hp:p id="${pId}" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">` +
    '<hp:run charPrIDRef="0">' +
    `<hp:pic id="${picId}" zOrder="0" numberingType="PICTURE" textWrap="TOP_AND_BOTTOM" ` +
    `textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" href="" groupLevel="0" instid="${picId}" reverse="0">` +
    '<hp:offset x="0" y="0"/>' +
    `<hp:orgSz width="${widthHwp}" height="${heightHwp}"/>` +
    `<hp:curSz width="${widthHwp}" height="${heightHwp}"/>` +
    '<hp:flip horizontal="0" vertical="0"/>' +
    `<hp:rotationInfo angle="0" centerX="${halfW}" centerY="${halfH}" rotateimage="1"/>` +
    '<hp:renderingInfo>' +
    '<hc:transMatrix e1="1" e2="0" e3="0" e4="0" e5="1" e6="0"/>' +
    '<hc:scaMatrix e1="1" e2="0" e3="0" e4="0" e5="1" e6="0"/>' +
    '<hc:rotMatrix e1="1" e2="0" e3="0" e4="0" e5="1" e6="0"/>' +
    '</hp:renderingInfo>' +
    '<hp:imgRect>' +
    `<hc:pt0 x="0" y="0"/>` +
    `<hc:pt1 x="${widthHwp}" y="0"/>` +
    `<hc:pt2 x="${widthHwp}" y="${heightHwp}"/>` +
    `<hc:pt3 x="0" y="${heightHwp}"/>` +
    '</hp:imgRect>' +
    `<hp:imgClip left="0" right="${widthHwp}" top="0" bottom="${heightHwp}"/>` +
    '<hp:inMargin left="0" right="0" top="0" bottom="0"/>' +
    `<hp:imgDim dimwidth="${widthHwp}" dimheight="${heightHwp}"/>` +
    `<hc:img binaryItemIDRef="${fileId}" bright="0" contrast="0" effect="REAL_PIC" alpha="0"/>` +
    '<hp:effects/>' +
    `<hp:sz width="${widthHwp}" widthRelTo="ABSOLUTE" height="${heightHwp}" heightRelTo="ABSOLUTE" protect="0"/>` +
    '<hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" ' +
    'holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" ' +
    'vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/>' +
    '<hp:outMargin left="0" right="0" top="0" bottom="0"/>' +
    '</hp:pic>' +
    '</hp:run>' +
    '</hp:p>'
  );
}

/**
 * 테이블 셀 내 단락 (subList 내부용)
 */
function buildCellPara(text: string): string {
  const id = nextId();
  return (
    `<hp:p id="${id}" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">` +
    '<hp:run charPrIDRef="0">' +
    `<hp:t>${esc(text)}</hp:t>` +
    '</hp:run>' +
    '</hp:p>'
  );
}

/**
 * 테이블 셀 생성
 * OWPML 스펙: <hp:tc> 자식 순서: subList → cellAddr → cellSpan → cellSz → cellMargin
 */
function buildCell(
  text: string,
  colAddr: number,
  rowAddr: number,
  colSpan: number,
  rowSpan: number,
  width: number,
  isLabel = false,
): string {
  // 라벨 셀: borderFill id=4 (테두리 + 배경), 값 셀: id=3 (테두리만)
  const bfId = isLabel ? 4 : 3;
  return (
    `<hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="${bfId}">` +
    `<hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" ` +
    `linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">` +
    buildCellPara(text) +
    '</hp:subList>' +
    `<hp:cellAddr colAddr="${colAddr}" rowAddr="${rowAddr}"/>` +
    `<hp:cellSpan colSpan="${colSpan}" rowSpan="${rowSpan}"/>` +
    `<hp:cellSz width="${width}" height="564"/>` +
    '<hp:cellMargin left="510" right="510" top="141" bottom="141"/>' +
    '</hp:tc>'
  );
}

/**
 * 2열 테이블 (라벨 | 값) 구조를 가진 섹션 테이블 생성
 * A4 컨텐츠 너비 42520 HWPUNIT 기준:
 *   라벨 열: 33% = 14032, 값 열: 67% = 28488
 */
const CONTENT_WIDTH = 42520;
const LABEL_WIDTH = Math.round(CONTENT_WIDTH * 0.33);
const VALUE_WIDTH = CONTENT_WIDTH - LABEL_WIDTH;

/**
 * HTML 콘텐츠 여부 판별 (reportUtils의 __tide, __weather 등 키가 HTML 테이블 반환)
 */
function isHtmlContent(text: string): boolean {
  return text.trimStart().startsWith('<');
}

/**
 * 파싱된 HTML <table> Element → HWPX hp:tbl XML 변환
 */
function buildHwpxFromHtmlTableElement(table: Element): string {
  const rows = Array.from(table.querySelectorAll('tr'));
  if (rows.length === 0) return '';

  // 최대 열 수 산출 (colspan 고려)
  let colCount = 0;
  for (const row of rows) {
    let rowCols = 0;
    for (const cell of Array.from(row.children)) {
      const span = parseInt((cell as HTMLElement).getAttribute('colspan') || '1', 10) || 1;
      rowCols += span;
    }
    if (rowCols > colCount) colCount = rowCols;
  }
  if (colCount === 0) colCount = 1;

  const colWidth = Math.floor(CONTENT_WIDTH / colCount);
  const rowCnt = rows.length;

  let rowsXml = '';
  rows.forEach((row, rowIdx) => {
    let colAddr = 0;
    let cells = '';
    Array.from(row.children).forEach((cell) => {
      const isLabel = cell.tagName.toLowerCase() === 'th';
      const colSpan = parseInt((cell as HTMLElement).getAttribute('colspan') || '1', 10) || 1;
      const text = ((cell as HTMLElement).textContent || '').trim();
      const cellWidth = colWidth * colSpan;
      cells += buildCell(text, colAddr, rowIdx, colSpan, 1, cellWidth, isLabel);
      colAddr += colSpan;
    });
    rowsXml += '<hp:tr>' + cells + '</hp:tr>';
  });

  const pId = nextId();
  const tblId = nextId();
  const tblHeight = rowCnt * 564;

  return (
    `<hp:p id="${pId}" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">` +
    '<hp:run charPrIDRef="0">' +
    `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" ` +
    `textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" ` +
    `repeatHeader="0" rowCnt="${rowCnt}" colCnt="${colCount}" cellSpacing="0" ` +
    `borderFillIDRef="2" noAdjust="0">` +
    `<hp:sz width="${CONTENT_WIDTH}" height="${tblHeight}" widthRelTo="ABSOLUTE" heightRelTo="ABSOLUTE" protect="0"/>` +
    '<hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" ' +
    'holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="COLUMN" ' +
    'vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/>' +
    '<hp:outMargin left="0" right="0" top="0" bottom="0"/>' +
    '<hp:inMargin left="0" right="0" top="0" bottom="0"/>' +
    rowsXml +
    '</hp:tbl>' +
    '</hp:run>' +
    '</hp:p>'
  );
}

/**
 * HTML 문자열 → HWPX XML 변환
 * <table> → hp:tbl, <p> → buildPara, 복합 구조도 처리
 */
function htmlContentToHwpx(html: string): string {
  // <img> 태그 제거: HWPX 이미지는 buildPicParagraph로 별도 처리됨
  // DOMParser에 대용량 base64 data URL 전달 방지
  const cleanHtml = html.replace(/<img\b[^>]*>/gi, '');
  const parser = new DOMParser();
  const doc = parser.parseFromString(`<div>${cleanHtml}</div>`, 'text/html');
  const container = doc.body.firstElementChild;
  if (!container) return buildPara('-', 0);

  let xml = '';
  for (const child of Array.from(container.childNodes)) {
    if (child.nodeType === Node.ELEMENT_NODE) {
      const el = child as Element;
      const tag = el.tagName.toLowerCase();
      if (tag === 'table') {
        xml += buildHwpxFromHtmlTableElement(el);
      } else {
        const text = ((el as HTMLElement).textContent || '').trim();
        if (text) xml += buildPara(text, 0);
      }
    } else if (child.nodeType === Node.TEXT_NODE) {
      const text = (child.textContent || '').trim();
      if (text) xml += buildPara(text, 0);
    }
  }

  return xml || buildPara('-', 0);
}

function buildFieldTable(
  fields: { key: string; label: string }[],
  getVal: (key: string) => string,
): string {
  const rowCnt = fields.length;
  if (rowCnt === 0) return '';

  // 단일 필드 + 빈 label + HTML 값인 경우 → HTML→HWPX 변환
  if (fields.length === 1 && !fields[0].label) {
    const value = getVal(fields[0].key) || '-';
    if (isHtmlContent(value)) {
      return htmlContentToHwpx(value);
    }
  }

  let rows = '';
  fields.forEach((field, rowIdx) => {
    const value = getVal(field.key) || '-';
    if (field.label) {
      // 2열: 라벨(배경색) | 값
      rows +=
        '<hp:tr>' +
        buildCell(field.label, 0, rowIdx, 1, 1, LABEL_WIDTH, true) +
        buildCell(value, 1, rowIdx, 1, 1, VALUE_WIDTH, false) +
        '</hp:tr>';
    } else {
      // 1열 전체 너비 (textarea 류)
      rows += '<hp:tr>' + buildCell(value, 0, rowIdx, 2, 1, CONTENT_WIDTH, false) + '</hp:tr>';
    }
  });

  const colCnt = 2;
  const pId = nextId();
  const tblId = nextId();
  // 테이블 높이 추정: 각 행 564 HWPUNIT
  const tblHeight = rowCnt * 564;

  // 테이블은 <hp:p> > <hp:run> 안에 감싸야 함
  // OWPML 스펙: hp:tbl 속성 + 자식(sz, pos, outMargin, inMargin) 후 행
  return (
    `<hp:p id="${pId}" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">` +
    '<hp:run charPrIDRef="0">' +
    `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" ` +
    `textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" ` +
    `repeatHeader="0" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" ` +
    `borderFillIDRef="2" noAdjust="0">` +
    `<hp:sz width="${CONTENT_WIDTH}" height="${tblHeight}" widthRelTo="ABSOLUTE" heightRelTo="ABSOLUTE" protect="0"/>` +
    '<hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" ' +
    'holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="COLUMN" ' +
    'vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/>' +
    '<hp:outMargin left="0" right="0" top="0" bottom="0"/>' +
    '<hp:inMargin left="0" right="0" top="0" bottom="0"/>' +
    rows +
    '</hp:tbl>' +
    '</hp:run>' +
    '</hp:p>'
  );
}

/**
 * section0.xml 전체 생성
 */
function buildSection0Xml(
  templateLabel: string,
  meta: ReportMeta,
  sections: ReportSection[],
  getVal: (key: string) => string,
  imageBinIds?: { step3?: number; step6?: number; sensitiveMap?: number; sensitivityMap?: number },
): string {
  // ID 시퀀스 초기화 (재사용 시 충돌 방지)
  _idSeq = 1000000000;

  let body = '';

  // 1) 첫 단락: secPr (필수 - 페이지 설정 포함)
  body += buildSecPrParagraph();

  // 2) 메인 제목 (16pt 파란색 = charPrId 5)
  body += buildPara('해양오염방제지원시스템', 5);

  // 3) 부제목 (11pt = charPrId 6)
  body += buildPara(templateLabel, 6);

  // 4) 메타 정보 (기본 10pt = charPrId 0)
  const metaLine =
    `작성일시: ${meta.writeTime}` +
    `  |  작성자: ${meta.author || '-'}` +
    `  |  관할: ${meta.jurisdiction}`;
  body += buildPara(metaLine, 0);

  // 5) 빈 줄
  body += buildEmptyPara();

  // 6) 각 섹션
  for (const section of sections) {
    // 섹션 제목 (11pt = charPrId 6)
    body += buildPara(section.title, 6);

    // __spreadMaps 필드 포함 섹션: 이미지 단락 삽입 후 나머지 필드 처리
    const hasSpreadMaps = section.fields.some((f) => f.key === '__spreadMaps');
    const hasSensitive = section.fields.some((f) => f.key === '__sensitive');
    if (hasSpreadMaps && imageBinIds) {
      const regularFields = section.fields.filter((f) => f.key !== '__spreadMaps');
      if (imageBinIds.step3) {
        body += buildPara('3시간 후 예측', 0);
        body += buildPicParagraph(imageBinIds.step3, CONTENT_WIDTH, 24000);
      }
      if (imageBinIds.step6) {
        body += buildPara('6시간 후 예측', 0);
        body += buildPicParagraph(imageBinIds.step6, CONTENT_WIDTH, 24000);
      }
      if (regularFields.length > 0) {
        body += buildFieldTable(regularFields, getVal);
      }
    } else if (hasSensitive) {
      // 민감자원 분포 지도 — 테이블 앞
      if (imageBinIds?.sensitiveMap) {
        body += buildPara('민감자원 분포 지도', 0);
        body += buildPicParagraph(imageBinIds.sensitiveMap, CONTENT_WIDTH, 24000);
      }
      body += buildFieldTable(section.fields, getVal);
      // 통합민감도 평가 지도 — 테이블 뒤
      if (imageBinIds?.sensitivityMap) {
        body += buildPara('통합민감도 평가 지도', 0);
        body += buildPicParagraph(imageBinIds.sensitivityMap, CONTENT_WIDTH, 24000);
      }
    } else {
      // 필드 테이블
      const fields = section.fields.filter((f) => f.key !== '__spreadMaps');
      if (hasSpreadMaps) {
        // 이미지 없는 경우 __spreadMaps 필드 제외하고 나머지만 출력
        if (fields.length > 0) body += buildFieldTable(fields, getVal);
      } else if (section.fields.length > 0) {
        body += buildFieldTable(section.fields, getVal);
      }
    }

    // 섹션 후 빈 줄
    body += buildEmptyPara();
  }

  return (
    '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>' +
    `<hs:sec ${SEC_NS}>` +
    body +
    '</hs:sec>'
  );
}

/**
 * Preview/PrvText.txt 생성 (일반 텍스트 미리보기)
 */
function buildPrvText(
  templateLabel: string,
  meta: ReportMeta,
  sections: ReportSection[],
  getVal: (key: string) => string,
): string {
  const lines: string[] = [];
  lines.push('해양오염방제지원시스템');
  lines.push(templateLabel);
  lines.push(
    `작성일시: ${meta.writeTime}  |  작성자: ${meta.author || '-'}  |  관할: ${meta.jurisdiction}`,
  );
  lines.push('');

  for (const section of sections) {
    lines.push(`[${section.title}]`);
    for (const field of section.fields) {
      const raw = getVal(field.key) || '-';
      const value = isHtmlContent(raw)
        ? raw
            .replace(/<[^>]+>/g, ' ')
            .replace(/\s+/g, ' ')
            .trim() || '-'
        : raw;
      if (field.label) {
        lines.push(`  ${field.label}: ${value}`);
      } else {
        lines.push(`  ${value}`);
      }
    }
    lines.push('');
  }

  return lines.join('\n');
}

// ─── 메인 Export 함수 ────────────────────────────────────────

export async function exportAsHWPX(
  templateLabel: string,
  meta: ReportMeta,
  sections: ReportSection[],
  getVal: (key: string) => string,
  filename: string,
  images?: { step3?: string; step6?: string; sensitiveMap?: string; sensitivityMap?: string },
): Promise<void> {
  const zip = new JSZip();

  // mimetype: STORE (무압축) + 첫 번째 항목
  zip.file('mimetype', MIMETYPE, { compression: 'STORE' });

  // 정적 메타 파일
  zip.file('version.xml', VERSION_XML);
  zip.file('settings.xml', SETTINGS_XML);
  zip.file('META-INF/container.xml', CONTAINER_XML);
  zip.file('META-INF/container.rdf', CONTAINER_RDF);
  zip.file('META-INF/manifest.xml', MANIFEST_XML);

  // 이미지 처리
  let imageBinIds:
    | { step3?: number; step6?: number; sensitiveMap?: number; sensitivityMap?: number }
    | undefined;
  let extraManifestItems = '';

  const processImage = (src: string, binId: number, fileId: string) => {
    // 실제 이미지 포맷 감지 (JPEG vs PNG)
    const isJpeg = src.startsWith('data:image/jpeg') || src.startsWith('data:image/jpg');
    const ext = isJpeg ? 'jpg' : 'png';
    const mediaType = isJpeg ? 'image/jpeg' : 'image/png';
    const filePath = `BinData/image${binId}.${ext}`;

    const base64 = src.replace(/^data:image\/\w+;base64,/, '');
    // HWPX 스펙: 이미지는 ZIP 루트의 BinData/에 저장 (Contents/ 아래 아님)
    zip.file(filePath, base64, { base64: true });
    // isEmbeded="1": 한글 HWPX 표준 속성, hc:img binaryItemIDRef가 이 id로 참조
    extraManifestItems += `<opf:item id="${fileId}" href="${filePath}" media-type="${mediaType}" isEmbeded="1"/>`;
  };

  if (images?.step3 || images?.step6) {
    imageBinIds = {};
    if (images.step3) {
      imageBinIds.step3 = 1;
      processImage(images.step3, 1, 'image1');
    }
    if (images.step6) {
      imageBinIds.step6 = 2;
      processImage(images.step6, 2, 'image2');
    }
  }
  if (images?.sensitiveMap) {
    imageBinIds = imageBinIds ?? {};
    imageBinIds.sensitiveMap = 3;
    processImage(images.sensitiveMap, 3, 'image3');
  }
  if (images?.sensitivityMap) {
    imageBinIds = imageBinIds ?? {};
    imageBinIds.sensitivityMap = 4;
    processImage(images.sensitivityMap, 4, 'image4');
  }

  // header.xml: hh:binDataList 불필요 — hc:img binaryItemIDRef → content.hpf manifest 방식 사용
  const headerXml = HEADER_XML;

  // Contents
  zip.file('Contents/content.hpf', buildContentHpf(extraManifestItems));
  zip.file('Contents/header.xml', headerXml);
  zip.file(
    'Contents/section0.xml',
    buildSection0Xml(templateLabel, meta, sections, getVal, imageBinIds),
  );

  // Preview
  zip.file('Preview/PrvText.txt', buildPrvText(templateLabel, meta, sections, getVal));

  // ZIP 생성 및 다운로드
  const blob = await zip.generateAsync({
    type: 'blob',
    mimeType: 'application/hwp+zip',
    compression: 'DEFLATE',
    compressionOptions: { level: 6 },
  });

  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `${filename}.hwpx`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}
