import react from "@vitejs/plugin-react";
import {
  copyFileSync,
  cpSync,
  existsSync,
  mkdirSync,
  readFileSync,
  rmSync,
  writeFileSync,
} from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { build } from "vite";

const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const localBlueprintBuild = process.argv.includes("--local-blueprint");
const outputName = localBlueprintBuild ? "windows-dist" : "strato-dist";
const outputDirectory = resolve(projectRoot, outputName);
const serverDirectory = resolve(projectRoot, `.${outputName}-ssr`);
const blueprintRoot = resolve(projectRoot, "app/blueprint-public");
const localeMeta = JSON.parse(
  readFileSync(resolve(projectRoot, "app/data/public-locales.json"), "utf8"),
);
const locales = Object.keys(localeMeta);

function escapeHtml(value) {
  return value
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;");
}

function replaceRequired(html, pattern, replacement, label) {
  if (!pattern.test(html)) throw new Error(`Cannot find ${label} in HTML template.`);
  return html.replace(pattern, replacement);
}

function localizedCalculatorPage(template, locale, rendered) {
  const meta = localeMeta[locale];
  const canonicalUrl = `https://factoriocalculatorapp.com${meta.path}`;
  const alternateLinks = locales
    .map(
      (candidate) =>
        `    <link rel="alternate" hreflang="${candidate}" href="https://factoriocalculatorapp.com${localeMeta[candidate].path}" />`,
    )
    .concat(
      `    <link rel="alternate" hreflang="x-default" href="https://factoriocalculatorapp.com/en/" />`,
    )
    .join("\n");
  const alternateOgLocales = locales
    .filter((candidate) => candidate !== locale)
    .map(
      (candidate) =>
        `    <meta property="og:locale:alternate" content="${localeMeta[candidate].ogLocale}" />`,
    )
    .join("\n");

  let html = template
    .replace(/\s*<link rel="alternate"[^>]+\/>/g, "")
    .replace(/\s*<meta property="og:locale:alternate"[^>]+\/>/g, "");
  html = replaceRequired(
    html,
    /<html lang="[^"]+"/,
    `<html lang="${locale}"`,
    "html language",
  );
  html = replaceRequired(
    html,
    /<title>[\s\S]*?<\/title>/,
    `<title>${escapeHtml(meta.title)}</title>`,
    "title",
  );
  html = replaceRequired(
    html,
    /<meta name="description" content="[^"]*"\s*\/>/,
    `<meta name="description" content="${escapeHtml(meta.description)}" />`,
    "description",
  );
  html = replaceRequired(
    html,
    /<link rel="canonical" href="[^"]*"\s*\/>/,
    `<link rel="canonical" href="${canonicalUrl}" />\n${alternateLinks}`,
    "canonical URL",
  );
  html = replaceRequired(
    html,
    /<meta property="og:title" content="[^"]*"\s*\/>/,
    `<meta property="og:title" content="${escapeHtml(meta.title)}" />`,
    "Open Graph title",
  );
  html = replaceRequired(
    html,
    /<meta property="og:description" content="[^"]*"\s*\/>/,
    `<meta property="og:description" content="${escapeHtml(meta.description)}" />`,
    "Open Graph description",
  );
  html = replaceRequired(
    html,
    /<meta property="og:url" content="[^"]*"\s*\/>/,
    `<meta property="og:url" content="${canonicalUrl}" />`,
    "Open Graph URL",
  );
  html = replaceRequired(
    html,
    /<meta property="og:locale" content="[^"]*"\s*\/>/,
    `<meta property="og:locale" content="${meta.ogLocale}" />\n${alternateOgLocales}`,
    "Open Graph locale",
  );
  html = replaceRequired(
    html,
    /<meta name="twitter:title" content="[^"]*"\s*\/>/,
    `<meta name="twitter:title" content="${escapeHtml(meta.title)}" />`,
    "Twitter title",
  );
  html = replaceRequired(
    html,
    /<meta name="twitter:description" content="[^"]*"\s*\/>/,
    `<meta name="twitter:description" content="${escapeHtml(meta.description)}" />`,
    "Twitter description",
  );
  html = replaceRequired(
    html,
    /<div id="root"><\/div>/,
    `<div id="root">${rendered}</div>`,
    "render target",
  );
  return html.replace(
    /<noscript>[\s\S]*?<\/noscript>/,
    `<noscript>${escapeHtml(meta.noscript)}</noscript>`,
  );
}

function redirectPage() {
  return `<!doctype html>
<html lang="en" class="dark">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="robots" content="noindex, follow" />
    <meta http-equiv="refresh" content="0; url=/en/" />
    <link rel="canonical" href="https://factoriocalculatorapp.com/en/" />
    <title>FactorioCalculatorApp</title>
  </head>
  <body><p><a href="/en/">Open FactorioCalculatorApp</a></p></body>
</html>\n`;
}

function sitemapDocument() {
  const alternateLinks = locales
    .map(
      (locale) =>
        `    <xhtml:link rel="alternate" hreflang="${locale}" href="https://factoriocalculatorapp.com${localeMeta[locale].path}" />`,
    )
    .concat(
      '    <xhtml:link rel="alternate" hreflang="x-default" href="https://factoriocalculatorapp.com/en/" />',
    )
    .join("\n");
  const localizedUrls = locales
    .map(
      (locale) => `  <url>
    <loc>https://factoriocalculatorapp.com${localeMeta[locale].path}</loc>
    <lastmod>2026-09-05</lastmod>
    <changefreq>monthly</changefreq>
    <priority>1.0</priority>
${alternateLinks}
  </url>`,
    )
    .join("\n");
  const documents = [
    "privacy.html",
    "legal.html",
    "license.html",
    "third-party-notices.html",
  ]
    .map(
      (path) => `  <url>
    <loc>https://factoriocalculatorapp.com/${path}</loc>
    <lastmod>2026-09-05</lastmod>
    <priority>0.2</priority>
  </url>`,
    )
    .join("\n");
  return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml">
${localizedUrls}
${documents}
</urlset>\n`;
}

function legalDocumentPage({ path, eyebrow, title, summary, content, rawPath }) {
  return `<!doctype html>
<html lang="en" class="dark">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="robots" content="index, follow" />
    <meta name="description" content="${escapeHtml(summary)}" />
    <link rel="canonical" href="https://factoriocalculatorapp.com/${path}" />
    <link rel="icon" href="/favicon.svg" />
    <link rel="stylesheet" href="/legal-doc.css" />
    <title>${escapeHtml(title)} · FactorioCalculatorApp</title>
  </head>
  <body>
    <header class="legal-topbar">
      <div class="legal-topbar-inner">
        <a class="legal-brand" href="/en/"><img src="/favicon.svg" alt="" />FactorioCalculatorApp</a>
        <a class="legal-back" href="/en/">← Back to calculator / Zurück zum Rechner</a>
      </div>
    </header>
    <main class="legal-shell">
      <section class="legal-hero">
        <p class="legal-eyebrow">Release 1.0.0 · ${escapeHtml(eyebrow)}</p>
        <h1>${escapeHtml(title)}</h1>
        <p class="legal-summary">${escapeHtml(summary)}</p>
      </section>
      <article class="legal-card"><pre class="document-text">${escapeHtml(content.trim())}</pre></article>
      <nav class="legal-links" aria-label="Related documents">
        <a href="/privacy.html">Privacy</a>
        <a href="/legal.html">Legal notice</a>
        <a href="/license.html">GPL-3.0 license</a>
        <a href="/third-party-notices.html">Third-party notices</a>
        <a href="/${rawPath}" download>Download plain text</a>
      </nav>
    </main>
    <footer class="legal-footer">Version 1.0.0 · Contact: factoriocalculatorapp.com@gmail.com</footer>
  </body>
</html>\n`;
}

rmSync(outputDirectory, { recursive: true, force: true });
rmSync(serverDirectory, { recursive: true, force: true });

process.env.FACTORIO_LOCAL_BLUEPRINT = localBlueprintBuild ? "true" : "false";
process.env.FACTORIO_STATIC_OUTPUT = outputName;

await build({
  configFile: resolve(projectRoot, "vite.strato.config.ts"),
});

await build({
  configFile: false,
  plugins: [react()],
  resolve: {
    alias: {
      "@": projectRoot,
      "@blueprint": blueprintRoot,
    },
  },
  build: {
    ssr: resolve(projectRoot, "strato/render.tsx"),
    outDir: serverDirectory,
    emptyOutDir: true,
    minify: false,
    rollupOptions: {
      output: {
        entryFileNames: "render.mjs",
      },
    },
  },
  ssr: {
    noExternal: true,
  },
});

const { render } = await import(
  `${pathToFileURL(resolve(serverDirectory, "render.mjs")).href}?v=${Date.now()}`
);
const templatePath = resolve(outputDirectory, "index.html");
const template = readFileSync(templatePath, "utf8");

for (const locale of locales) {
  const relativePath = `${localeMeta[locale].path.slice(1)}index.html`;
  const htmlPath = resolve(outputDirectory, relativePath);
  mkdirSync(dirname(htmlPath), { recursive: true });
  writeFileSync(
    htmlPath,
    localizedCalculatorPage(template, locale, render(locale)),
  );
}
writeFileSync(templatePath, redirectPage());

cpSync(resolve(projectRoot, "strato/static"), outputDirectory, {
  recursive: true,
  force: true,
});
const operatorPath = resolve(projectRoot, "release-operator.json");
if (existsSync(operatorPath)) {
  const operator = JSON.parse(readFileSync(operatorPath, "utf8"));
  const address = [operator.name, operator.street, operator.city, operator.country];
  if (address.some((value) => typeof value !== "string" || !value.trim())) {
    throw new Error("The release operator configuration needs name, street, city and country.");
  }
  const operatorCard = `<article class="legal-card"><h2>Website operator / Anbieter / Verantwortlicher</h2><p>${address.map(escapeHtml).join("<br />")}</p><p><a href="mailto:factoriocalculatorapp.com@gmail.com">factoriocalculatorapp.com@gmail.com</a></p></article>`;
  for (const name of ["legal.html", "privacy.html"]) {
    const file = resolve(outputDirectory, name);
    const html = readFileSync(file, "utf8");
    writeFileSync(file, html.replace('      <article class="legal-card">', `      ${operatorCard}\n      <article class="legal-card">`));
  }
}
writeFileSync(resolve(outputDirectory, "sitemap.xml"), sitemapDocument());
copyFileSync(
  resolve(projectRoot, "vendor/icons-factorio.GPL-3.0.txt"),
  resolve(outputDirectory, "LICENSE.txt"),
);
const completeNotices = [
  readFileSync(resolve(projectRoot, "THIRD_PARTY_NOTICES.md"), "utf8"),
  readFileSync(resolve(projectRoot, "THIRD_PARTY_LICENSES/ALL.txt"), "utf8"),
].join("\n\n");
writeFileSync(resolve(outputDirectory, "THIRD_PARTY_NOTICES.txt"), completeNotices);
writeFileSync(
  resolve(outputDirectory, "license.html"),
  legalDocumentPage({
    path: "license.html",
    eyebrow: "Open-source license",
    title: "GNU General Public License v3.0",
    summary:
      "The complete GPL-3.0-only license text for the original calculator source code, presented in a readable website view.",
    content: readFileSync(
      resolve(projectRoot, "vendor/icons-factorio.GPL-3.0.txt"),
      "utf8",
    ),
    rawPath: "LICENSE.txt",
  }),
);
writeFileSync(
  resolve(outputDirectory, "third-party-notices.html"),
  legalDocumentPage({
    path: "third-party-notices.html",
    eyebrow: "Attribution",
    title: "Third-party notices",
    summary:
      "Attribution, ownership and license information for Factorio material, icons and runtime packages used by release 1.0.0.",
    content: completeNotices,
    rawPath: "THIRD_PARTY_NOTICES.txt",
  }),
);
const downloadsDirectory = resolve(outputDirectory, "downloads");
mkdirSync(downloadsDirectory, { recursive: true });
const existingSourceArchive = resolve(
  projectRoot,
  "release/factorio-calculator-source-1.0.0.zip",
);
if (localBlueprintBuild && existsSync(existingSourceArchive)) {
  copyFileSync(
    existingSourceArchive,
    resolve(downloadsDirectory, "factorio-calculator-source-1.0.0.zip"),
  );
}

rmSync(serverDirectory, { recursive: true, force: true });
console.log(
  `${localBlueprintBuild ? "Windows local" : "STRATO"} build created at ${outputDirectory}`,
);
