const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');

const root = path.resolve(__dirname, '..');
const patentDir = path.join(root, 'docs', 'patent-4-ecosystem-reward-finality');
const specPath = path.join(patentDir, 'specification_draft.md');
const outDir = path.join(patentDir, 'integrated-writer');
const imageDir = path.join(outDir, 'images');
const xmlName = '제4특허_복수서비스기여사건_전자적가치확정_명세서등.xml';

fs.mkdirSync(imageDir, { recursive: true });

const source = fs.readFileSync(specPath, 'utf8').replace(/\r\n/g, '\n');

function xmlEscape(value) {
  return String(value)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}

function cleanMarkdown(value) {
  const lines = value
    .split('\n')
    .map((line) => line.trim())
    .filter((line) => line && !line.startsWith('```'))
    .map((line) => line.replace(/^[-*]\s+/, '').replace(/`([^`]+)`/g, '$1'));
  return lines.map(xmlEscape).join('<br />');
}

function sectionText(startHeading, endHeading) {
  const start = source.indexOf(startHeading);
  if (start < 0) throw new Error(`Missing heading: ${startHeading}`);
  const from = start + startHeading.length;
  const end = endHeading ? source.indexOf(endHeading, from) : source.length;
  if (endHeading && end < 0) throw new Error(`Missing heading: ${endHeading}`);
  return source.slice(from, end);
}

function paragraphsIn(value) {
  const matches = [...value.matchAll(/^\[(\d{4})\]\s+([\s\S]*?)(?=^\[\d{4}\]\s+|^#{1,3}\s|\s*$)/gm)];
  return matches.map((match) => ({ num: match[1], text: match[2].trim() }));
}

function pXml(paragraph) {
  return `<p num="${paragraph.num}">${cleanMarkdown(paragraph.text)}</p>`;
}

function rangeXml(first, last) {
  const body = sectionText('## 【기술분야】', '# 【청구범위】');
  return paragraphsIn(body)
    .filter(({ num }) => Number(num) >= first && Number(num) <= last)
    .map(pXml)
    .join('');
}

const titleBlock = sectionText('## 【발명의 명칭】', '## 【기술분야】')
  .split('\n')
  .map((line) => line.trim())
  .filter(Boolean);
const koreanTitle = titleBlock[0];
const englishTitle = titleBlock.slice(1).join(' ');

if (!koreanTitle || !englishTitle) throw new Error('Korean or English invention title is missing');

const citations = paragraphsIn(sectionText('## 【선행기술문헌】', '## 【발명의 내용】'));
const citationXml = citations.map((paragraph, index) =>
  `<p num="${paragraph.num}"><patcit num="${String(index + 1).padStart(4, '0')}"><text>${cleanMarkdown(paragraph.text)}</text></patcit></p>`
).join('');

const referenceParagraph = paragraphsIn(sectionText('## 【부호의 설명】', '# 【청구범위】'))[0];
if (!referenceParagraph || referenceParagraph.num !== '0095') {
  throw new Error('Reference-sign paragraph [0095] is missing');
}

const claimsBlock = sectionText('# 【청구범위】', '# 【요약서】');
const claimHeadings = [...claimsBlock.matchAll(/^## 【청구항 (\d+)】\s*$/gm)];
const claims = claimHeadings.map((heading, index) => {
  const bodyStart = heading.index + heading[0].length;
  const bodyEnd = index + 1 < claimHeadings.length
    ? claimHeadings[index + 1].index
    : claimsBlock.length;
  return {
    num: Number(heading[1]),
    text: claimsBlock.slice(bodyStart, bodyEnd).trim(),
  };
});
if (claims.length !== 10 || claims.some((claim, index) => claim.num !== index + 1)) {
  throw new Error(`Expected claims 1-10, found: ${claims.map(({ num }) => num).join(',')}`);
}

const minimumClaimLengths = [600, 250, 180, 180, 250, 250, 180, 180, 600, 50];
for (const claim of claims) {
  if (claim.text.length < minimumClaimLengths[claim.num - 1]) {
    throw new Error(`Claim ${claim.num} is unexpectedly short (${claim.text.length} characters)`);
  }
}

const requiredClaimTerms = {
  1: ['서비스 내부 중복키', '서비스 간 기여사건지문', '잠정기록', '최종성 증명', '재제출'],
  2: ['결정적 직렬화', '서비스 간 기여사건지문'],
  3: ['사건 관계 그래프', '하나의 기여'],
  4: ['일회성 값', '인증값'],
  5: ['최종성 조건', '한 번만 수행'],
  6: ['영속적 제출대기', '기존 원장 트랜잭션'],
  7: ['보상거래', '사용 가능 잔액'],
  8: ['경쟁적 해시연산량', '실제 응용서비스'],
  9: ['교차 중복검증부', '실패복구·재조정부'],
  10: ['비일시적 컴퓨터 판독가능 기록매체'],
};
for (const claim of claims) {
  for (const term of requiredClaimTerms[claim.num]) {
    if (!claim.text.includes(term)) {
      throw new Error(`Claim ${claim.num} is missing required text: ${term}`);
    }
  }
}
const claimsXml = claims.map(({ num, text }) => {
  const claimText = text
    .split(/\n\s*\n/)
    .map((part) => cleanMarkdown(part))
    .filter(Boolean)
    .join('<br/>');
  return `<claim num="${num}"><claim-text>${claimText}</claim-text></claim>`;
}).join('');

const abstractBlock = sectionText('## 【요약】', '## 【대표도】').trim();
const representative = sectionText('## 【대표도】', null).match(/도\s*(\d+)/);
if (!representative) throw new Error('Representative figure is missing');
const representativeFigure = Number(representative[1]);

const figuresXml = Array.from({ length: 8 }, (_, index) => {
  const num = index + 1;
  const id = String(num).padStart(4, '0');
  const file = `pat${String(num).padStart(5, '0')}.jpg`;
  return `<figure num="${num}"><img id="i${id}" he="219" wi="164" file="${file}" img-format="jpg"></img></figure>`;
}).join('');

const xml = `<?xml version="1.0" encoding="utf-8"?>\n` +
  `<KIPO keapsVersion="1.102" editorKind="A" pageCount="38" imgApply="N" specId="SD00000001" xmlns="http://www.kipo.go.kr">\n` +
  `  <PatentCAFDOC docflag="1.0" documentID="202608030">` +
  `<description>` +
  `<invention-title>${xmlEscape(koreanTitle)}{${xmlEscape(englishTitle)}}</invention-title>` +
  `<technical-field>${rangeXml(1, 2)}</technical-field>` +
  `<background-art>${rangeXml(3, 8)}</background-art>` +
  `<citation-list><patent-literature>${citationXml}</patent-literature></citation-list>` +
  `<summary-of-invention>` +
  `<tech-problem>${rangeXml(16, 20)}</tech-problem>` +
  `<tech-solution>${rangeXml(21, 43)}</tech-solution>` +
  `<advantageous-effects>${rangeXml(44, 49)}</advantageous-effects>` +
  `</summary-of-invention>` +
  `<description-of-drawings>${rangeXml(50, 57)}</description-of-drawings>` +
  `<description-of-embodiments>${rangeXml(58, 93)}</description-of-embodiments>` +
  `<industrial-applicability>${rangeXml(94, 94)}</industrial-applicability>` +
  `<reference-signs-list>${pXml(referenceParagraph)}</reference-signs-list>` +
  `</description>` +
  `<claims>${claimsXml}</claims>` +
  `<abstract><summary><p num="0001a">${cleanMarkdown(abstractBlock)}</p></summary>` +
  `<abstract-figure><p num="0002a"><figref num="${representativeFigure}"></figref></p></abstract-figure></abstract>` +
  `<drawings>${figuresXml}</drawings>` +
  `</PatentCAFDOC>\n</KIPO>\n`;

fs.writeFileSync(path.join(outDir, xmlName), xml, 'utf8');

const hlzName = '제4특허_복수서비스기여사건_전자적가치확정_명세서등.hlz';
const hlzPath = path.join(outDir, hlzName);
const figureFileNames = Array.from(
  { length: 8 },
  (_, index) => `pat${String(index + 1).padStart(5, '0')}.jpg`,
);
for (const figureFileName of figureFileNames) {
  const figurePath = path.join(imageDir, figureFileName);
  if (!fs.existsSync(figurePath)) throw new Error(`Missing figure file: ${figurePath}`);
}

// PKEAPS expects drawing entries first and the same-named XML entry last.
// A differently ordered ZIP can be opened by MOI Viewer but is rejected as a
// submission attachment by the integrated form writer.
const temporaryHlzZip = path.join(outDir, '_generated-patent4-hlz.zip');
const archiveResult = spawnSync('tar', [
  '-a', '-cf', temporaryHlzZip,
  '-C', imageDir,
  ...figureFileNames,
  '-C', outDir,
  xmlName,
], { encoding: 'utf8' });
if (archiveResult.error || archiveResult.status !== 0) {
  throw new Error(`Failed to build HLZ: ${archiveResult.error?.message || archiveResult.stderr}`);
}
if (!fs.existsSync(temporaryHlzZip) || fs.statSync(temporaryHlzZip).size === 0) {
  throw new Error('Generated HLZ archive is missing or empty');
}
fs.copyFileSync(temporaryHlzZip, hlzPath);
fs.unlinkSync(temporaryHlzZip);

const bib = `<Version=KeapsP1.2 InCode=100010 OutCode=1011101 FeeRightType=온라인 ApplicationDate=20260803 ForSimulation=False detailVer=P1.2.3.133>\n` +
`【서류명】 특허출원서\n` +
`【출원구분】 특허출원\n` +
`【출원인】 \n` +
`    【성명】 최관홍\n` +
`    【특허고객번호】 4-1998-051565-5\n` +
`【발명의 국문명칭】${koreanTitle}\n` +
`【발명의 영문명칭】${englishTitle}\n` +
`【발명자】 \n` +
`    【성명】 최관홍\n` +
`    【특허고객번호】 4-1998-051565-5\n` +
`【출원언어】 국어\n` +
`【심사청구】 청구\n` +
`【취지】 위와 같이 지식재산처장에게 제출합니다.\n` +
`         출원인   최관홍                       (서명 또는 인)\n` +
`【수수료】 \n` +
`    【기본출원료】             0    면           46,000  원\n` +
`    【가산출원료】            38    면                0  원\n` +
`    【우선권주장료】           0    건                0  원\n` +
`    【심사청구료】            10    항          676,000  원\n` +
`    【합계】                       722,000원\n` +
`    【감면사유】           개인(70%감면)[2]\n` +
`    【감면후 수수료】                           216,600  원\n` +
`\n=# ${hlzPath}\n`;
fs.writeFileSync(path.join(outDir, '제4특허_전자적가치확정_특허출원서_작성본.bib'), bib, 'utf8');

const manifest = {
  generatedAt: new Date().toISOString(),
  source: path.relative(root, specPath).replace(/\\/g, '/'),
  xml: xmlName,
  title: koreanTitle,
  englishTitle,
  paragraphCount: paragraphsIn(sectionText('## 【기술분야】', '# 【청구범위】')).length,
  claimCount: claims.length,
  claimCharacterCounts: claims.map(({ num, text }) => ({ num, characters: text.length })),
  figureCount: 8,
  representativeFigure,
  hlz: hlzName,
};
fs.writeFileSync(path.join(outDir, 'package-manifest.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8');

console.log(JSON.stringify(manifest, null, 2));
