const {
   lstatSync,
   readdirSync,
   mkdirSync,
   copyFileSync,
   writeFileSync,
   readFileSync,
   exists,
} = require("fs");
const { join, basename, dirname } = require("path");

const isDirectory = (source) => lstatSync(source).isDirectory();
const getDirectories = (source) =>
   readdirSync(source)
      .map((name) => join(source, name))
      .filter(isDirectory);

function ensureDir(folder) {
   try {
      if (!isDirectory(folder)) mkdirSync(folder);
   } catch (e) {
      mkdirSync(folder);
   }
}

const fileExists = (filename) =>
   new Promise((yes, no) => exists(filename, (exi) => yes(exi)));
ensureDir("./out");

const sass = require("sass");

function findHead(elm) {
   if (elm.tagName === "head") return elm;
   for (let i = 0; i < elm.childNodes.length; i++) {
      let res = findHead(elm.childNodes[i]);
      if (res) return res;
   }
   return undefined;
}

const rollup = require("rollup");
const includepaths = require("rollup-plugin-includepaths");
const typescript = require("rollup-plugin-typescript2");
const resolve = require("rollup-plugin-node-resolve");
const minify = require("html-minifier").minify;
const gzipSize = require("gzip-size");

async function file_name(folder, name, exts) {
   for (let ext of exts) {
      let basefile = `${folder}/${name}.${ext}`;
      if (await fileExists(basefile)) return basefile;
   }
   return null;
}

async function buildPage(folder) {
   const pagename = basename(folder);
   const outpath = "./out/" + pagename;

   ensureDir(outpath);

   const basefile = await file_name(folder, pagename, ["tsx", "ts", "js"]);

   let bundle = await rollup.rollup({
      input: basefile,
      plugins: [
         includepaths({
            paths: ["shared", "node_modules"],
         }),
         typescript(),
         resolve({
            // not all files you want to resolve are .js files
            extensions: [".mjs", ".js", ".jsx", ".json"], // Default: [ '.mjs', '.js', '.json', '.node' ]

            // whether to prefer built-in modules (e.g. `fs`, `path`) or
            // local ones with the same names
            preferBuiltins: false, // Default: true
         }),
      ],
      treeshake: true,
   });

   let { output } = await bundle.generate({
      format: "iife",
      compact: true,
   });
   let { code } = output[0];

   let sass_res = sass.renderSync({
      file: folder + `/${pagename}.scss`,
      includePaths: ["./node_modules", folder, "./shared", "../node_modules"],
      outputStyle: "compressed",
   });

   let css = "<style>\n" + sass_res.css.toString("utf8") + "\n</style>\n";
   let script = "<script>\n" + code + "\n</script>\n";
   let html = readFileSync(`${folder}/${pagename}.hbs`).toString("utf8");

   let idx = html.indexOf("</head>");
   if (idx < 0) throw new Error("No head element found");
   let idx2 = html.indexOf("</body>");
   if (idx2 < 0) throw new Error("No body element found");

   if (idx < idx2) {
      let part1 = html.slice(0, idx);
      let part2 = html.slice(idx, idx2);
      let part3 = html.slice(idx2, html.length);
      html = part1 + css + part2 + script + part3;
   } else {
      let part1 = html.slice(0, idx2);
      let part2 = html.slice(idx2, idx);
      let part3 = html.slice(idx, html.length);
      html = part1 + script + part2 + css + part3;
   }

   let result = minify(html, {
      removeAttributeQuotes: true,
      collapseWhitespace: true,
      html5: true,
      keepClosingSlash: true,
      minifyCSS: false,
      minifyJS: false,
      removeComments: true,
      useShortDoctype: true,
   });

   let gzips = await gzipSize(result);
   writeFileSync(`${outpath}/${pagename}.html`, result);
   let stats = {
      sass: sass_res.stats,
      js: {
         chars: code.length,
      },
      css: {
         chars: css.length,
      },
      bundle_size: result.length,
      gzip_size: gzips,
   };

   writeFileSync(outpath + `/stats.json`, JSON.stringify(stats, null, "   "));
}

async function run() {
   console.log("Start compiling!");
   let pages = getDirectories("./src");
   await Promise.all(
      pages.map(async (e) => {
         try {
            await buildPage(e);
         } catch (er) {
            console.error("Failed compiling", basename(e));
            console.log(er);
            process.exitCode = 1;
         }
      })
   );
   console.log("Finished compiling!");
}

const chokidar = require("chokidar");
if (process.argv.join(" ").toLowerCase().indexOf("watch") >= 0)
   chokidar
      .watch(
         ["./src", "./node_modules", "./package.json", "./package-lock.json"],
         {
            ignoreInitial: true,
         }
      )
      .on("all", () => run());
run();
