// Sitegen! Clover's static site generator, built with love.

function main() {
  return withSpinner({
    text: "Recovering State",
    successText: ({ elapsed }) =>
      "sitegen! update in " + elapsed.toFixed(1) + "s",
    failureText: () => "sitegen FAIL",
  }, sitegen);
}

async function sitegen(status) {
  const startTime = performance.now();

  let root = path.resolve(import_meta.dirname, "../src");
  const join = (...sub) => path.join(root, ...sub);
  const incr = new Incremental();
  const sections: Section[] =
    require(path.join(root, "sections.ts")).siteSections;
  const views = [];
  const staticFiles = [];
  let pages = [];
  let scripts = [];
  const backendFiles = [];

  status.text = "Scanning Project";
  for (const section of sections) {
    const { root: sectionRoot } = section;
    const sectionPath = (...sub) => path.join(sectionRoot, ...sub);
    const rootPrefix = root === sectionRoot
      ? ""
      : path.relative(root, sectionRoot) + "/";
    const kinds = [
      {
        dir: sectionPath("pages"),
        list: pages,
        prefix: "/",
        exclude: [".css", ".client.ts", ".client.tsx"],
      },
      { dir: sectionPath("static"), list: staticFiles, prefix: "/", ext: true },
      { dir: sectionPath("scripts"), list: scripts, prefix: rootPrefix },
      { dir: sectionPath("views"), list: views, prefix: rootPrefix },
    ];
    for (const { dir, list, prefix, exclude = [], ext = false } of kinds) {
      const pages2 = fs.readDirRecOptional(dir);
      page: for (const page of pages2) {
        if (page.isDirectory()) continue;
        for (const ext2 of exclude) {
          if (page.name.endsWith(ext2)) continue page;
        }
        const file = path.relative(dir, page.parentPath + "/" + page.name);
        const trim = ext
          ? file
          : file.slice(0, -path.extname(file).length).replaceAll(".", "/");
        let id = prefix + trim.replaceAll("\\", "/");
        if (prefix === "/" && id.endsWith("/index")) {
          id = id.slice(0, -"/index".length) || "/";
        }
        list.push({ id, file: path.join(page.parentPath, page.name) });
      }
    }
    let backendFile = [
      sectionPath("backend.ts"),
      sectionPath("backend.tsx"),
    ].find((file) => fs.existsSync(file));
    if (backendFile) backendFiles.push(backendFile);
  }
  scripts = scripts.filter(({ file }) => !file.match(/\.client\.[tj]sx?/));
  const globalCssPath = join("global.css");
  status.text = "Building";
  const cssOnce = new OnceMap();
  const cssQueue = new Queue({
    name: "Bundle",
    fn: ([, files, theme]) => css.bundleCssFiles(files, theme),
    passive: true,
    getItemText: ([id]) => id,
    maxJobs: 2,
  });
  const ssrResults = [];
  function loadSsrModule(page) {
    require(page.file);
  }
  async function doSsrPage(page) {
    const module2 = require(page.file);
    const Page = module2.default;
    if (!Page) {
      throw new Error("Page is missing a 'default' export.");
    }
    const metadata = module2.meta;
    if (!metadata) {
      throw new Error("Page is missing 'meta' attribute with a title.");
    }
    const theme = {
      bg: "#fff",
      fg: "#050505",
      primary: "#2e7dab",
      ...module2.theme,
    };
    const renderedMetaPromise = Promise.resolve(
      typeof metadata === "function" ? metadata({ ssr: true }) : metadata,
    ).then((m) => meta.resolveAndRenderMetadata(m));
    const cssImports = [globalCssPath, ...hot.getCssImports(page.file)];
    const cssPromise = cssOnce.get(
      cssImports.join(":") + JSON.stringify(theme),
      () => cssQueue.add([page.id, cssImports, theme]),
    );
    const sitegenApi = sg.initRender();
    const body = await (0, import_ssr.ssrAsync)(
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Page, {}),
      {
        sitegen: sitegenApi,
      },
    );
    const inlineCss = await cssPromise;
    const renderedMeta = await renderedMetaPromise;
    if (!renderedMeta.includes("<title>")) {
      throw new Error(
        "Page is missing 'meta.title'. All pages need a title tag.",
      );
    }
    ssrResults.push({
      body,
      head: renderedMeta,
      inlineCss,
      scriptFiles: Array.from(sitegenApi.scripts),
      page,
    });
  }
  const spinnerFormat = status.format;
  status.format = () => "";
  const moduleLoadQueue = new import_queue.Queue({
    name: "Load Render Module",
    fn: loadSSRModule,
    getItemText,
    maxJobs: 1,
  });
  moduleLoadQueue.addMany(pages);
  await moduleLoadQueue.done({ method: "stop" });
  const pageQueue = new import_queue.Queue({
    name: "Render",
    fn: doSsrPage,
    getItemText,
    maxJobs: 2,
  });
  pageQueue.addMany(pages);
  await pageQueue.done({ method: "stop" });
  status.format = spinnerFormat;
  const referencedScripts = Array.from(
    new Set(ssrResults.flatMap((r) => r.scriptFiles)),
  );
  const extraPublicScripts = scripts.map((entry) => entry.file);
  const uniqueCount = new Set([
    ...referencedScripts,
    ...extraPublicScripts,
  ]).size;
  status.text = `Bundle ${uniqueCount} Scripts`;
  await bundle.bundleClientJavaScript(
    referencedScripts,
    extraPublicScripts,
    incr,
  );
  async function doStaticFile(page) {
    const body = await fs.readFile(page.file);
    incr.putAsset({
      srcId: "static:" + page.file,
      key: page.id,
      body,
    });
  }
  const staticQueue = new import_queue.Queue({
    name: "Load Static",
    fn: doStaticFile,
    getItemText,
    maxJobs: 16,
  });
  status.format = () => "";
  staticQueue.addMany(staticFiles);
  await staticQueue.done({ method: "stop" });
  status.format = spinnerFormat;
  status.text = `Concat ${ssrResults.length} Pages`;
  await Promise.all(
    ssrResults.map(async ({ page, body, head, inlineCss, scriptFiles }) => {
      const doc = wrapDocument({
        body,
        head,
        inlineCss,
        scripts: scriptFiles.map(
          (id) =>
            UNWRAP(
              incr.out.script.get(
                path.basename(id).replace(/\.client\.[jt]sx?$/, ""),
              ),
            ),
        ).map((x) => `{${x}}`).join(""),
      });
      incr.putAsset({
        srcId: "page:" + page.file,
        key: page.id,
        body: doc,
        headers: {
          "Content-Type": "text/html",
        },
      });
    }),
  );
  status.format = () => "";
  status.text = ``;
  await incr.wait();
  status.format = spinnerFormat;
  status.text = `Incremental Flush`;
  incr.flush();
  incr.serializeToDisk();
  return { elapsed: (performance.now() - startTime) / 1e3 };
}

import type { Section } from "./sitegen-lib.ts";
import { OnceMap, Queue } from "./queue.ts";
import { Incremental } from "./incremental.ts";
import * as bundle from "./bundle.ts";
import * as css from "./css.ts";
import * as fs from './fs.ts';
import { withSpinner, Spinner } from "@paperclover/console/Spinner";
