import { execFile } from "node:child_process";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { promisify } from "node:util";
import type { TextSection } from "./pdf-documents.js";

const execFileAsync = promisify(execFile);

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

function buildParagraphXml(input: {
  text: string;
  bold?: boolean;
  sizeHalfPoints?: number;
  spacingAfterTwips?: number;
}) {
  const normalizedText = String(input.text ?? "");

  if (!normalizedText.trim()) {
    return "<w:p/>";
  }

  const boldXml = input.bold ? "<w:b/>" : "";
  const sizeXml = input.sizeHalfPoints ? `<w:sz w:val="${input.sizeHalfPoints}"/>` : "";
  const spacingXml = input.spacingAfterTwips
    ? `<w:pPr><w:spacing w:after="${input.spacingAfterTwips}"/></w:pPr>`
    : "";

  return [
    "<w:p>",
    spacingXml,
    "<w:r>",
    `<w:rPr>${boldXml}${sizeXml}</w:rPr>`,
    `<w:t xml:space="preserve">${escapeXml(normalizedText)}</w:t>`,
    "</w:r>",
    "</w:p>",
  ].join("");
}

function buildDocumentXml(input: {
  title: string;
  subtitle?: string | null;
  sections: TextSection[];
}) {
  const paragraphs: string[] = [];

  paragraphs.push(
    buildParagraphXml({
      text: input.title,
      bold: true,
      sizeHalfPoints: 32,
      spacingAfterTwips: 220,
    }),
  );

  if (String(input.subtitle ?? "").trim()) {
    paragraphs.push(
      buildParagraphXml({
        text: String(input.subtitle).trim(),
        sizeHalfPoints: 22,
        spacingAfterTwips: 200,
      }),
    );
  }

  for (const section of input.sections) {
    if (String(section.heading ?? "").trim()) {
      paragraphs.push(
        buildParagraphXml({
          text: section.heading.trim(),
          bold: true,
          sizeHalfPoints: 26,
          spacingAfterTwips: 160,
        }),
      );
    }

    for (const line of section.lines) {
      paragraphs.push(
        buildParagraphXml({
          text: String(line ?? "").trim(),
          sizeHalfPoints: 22,
          spacingAfterTwips: 120,
        }),
      );
    }
  }

  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document
  xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
  xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
  <w:body>
    ${paragraphs.join("")}
    <w:sectPr>
      <w:pgSz w:w="11906" w:h="16838"/>
      <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708" w:footer="708" w:gutter="0"/>
    </w:sectPr>
  </w:body>
</w:document>`;
}

export async function buildTextDocx(input: {
  title: string;
  subtitle?: string | null;
  sections: TextSection[];
}) {
  const tempDirectory = await mkdtemp(join(tmpdir(), "neurav2-docx-"));
  const wordDirectory = resolve(tempDirectory, "word");
  const rootRelsDirectory = resolve(tempDirectory, "_rels");
  const docPropsDirectory = resolve(tempDirectory, "docProps");
  const outputPath = resolve(
    tmpdir(),
    `neurav2-docx-${Date.now()}-${Math.random().toString(36).slice(2)}.docx`,
  );

  try {
    await mkdir(wordDirectory, { recursive: true });
    await mkdir(rootRelsDirectory, { recursive: true });
    await mkdir(docPropsDirectory, { recursive: true });

    await writeFile(
      resolve(tempDirectory, "[Content_Types].xml"),
      `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
  <Default Extension="xml" ContentType="application/xml"/>
  <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
  <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
  <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
</Types>`,
      "utf8",
    );

    await writeFile(
      resolve(rootRelsDirectory, ".rels"),
      `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
  <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
</Relationships>`,
      "utf8",
    );

    await writeFile(
      resolve(docPropsDirectory, "core.xml"),
      `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties
  xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:dcterms="http://purl.org/dc/terms/"
  xmlns:dcmitype="http://purl.org/dc/dcmitype/"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <dc:title>${escapeXml(input.title)}</dc:title>
  <dc:creator>NeuraV2</dc:creator>
  <cp:lastModifiedBy>NeuraV2</cp:lastModifiedBy>
  <dcterms:created xsi:type="dcterms:W3CDTF">${new Date().toISOString()}</dcterms:created>
  <dcterms:modified xsi:type="dcterms:W3CDTF">${new Date().toISOString()}</dcterms:modified>
</cp:coreProperties>`,
      "utf8",
    );

    await writeFile(
      resolve(docPropsDirectory, "app.xml"),
      `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
  xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
  <Application>NeuraV2</Application>
</Properties>`,
      "utf8",
    );

    await writeFile(
      resolve(wordDirectory, "document.xml"),
      buildDocumentXml(input),
      "utf8",
    );

    await execFileAsync("zip", ["-qr", outputPath, "."], {
      cwd: tempDirectory,
    });

    return {
      bytes: Buffer.from(await readFile(outputPath)),
    };
  } finally {
    await rm(tempDirectory, { recursive: true, force: true }).catch(() => undefined);
    await rm(outputPath, { force: true }).catch(() => undefined);
  }
}
