import { execFileSync } from "child_process";
import * as chokidar from "chokidar";
import esbuild from "esbuild";
import fs from "fs";
import { globSync } from "glob";
import path from "path";
import { cwd } from "process";
import process from "process";
import { fileURLToPath } from "url";

// Hack replaces what was lost when using Bun / later Node versions
const __dirname = fileURLToPath(new URL(".", import.meta.url));

const isProdBuild = process.env.NODE_ENV === "production";

const definitions = {
    "process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
    "process.env.CWD": JSON.stringify(cwd()),
};

// If you have assets in your src folder that won't be built/bundled, put them into "otherFiles" to
// copy them. All this is a replacement for rollup-copy-plugin, which I used to use.

const otherFiles = [
    ["./src/*.css", "."],
    ["./src/*.png", "."]
];

const isFile = (filePath) => fs.statSync(filePath).isFile();
function nameCopyTarget(src, dest, strip) {
    const target = path.join(dest, strip ? src.replace(strip, "") : path.parse(src).base);
    return [src, target];
}

function copyOthers() {
    for (const [source, rawdest, strip] of otherFiles) {
        const matchedPaths = globSync(source);
        const dest = path.join("dist", rawdest);
        const copyTargets = matchedPaths.map((path) => nameCopyTarget(path, dest, strip));
        for (const [src, dest] of copyTargets) {
            if (isFile(src)) {
                fs.mkdirSync(path.dirname(dest), { recursive: true });
                fs.copyFileSync(src, dest);
            }
        }
    }
}

// This starts the definitions used for esbuild: Targets, arguments, the function for running a
// build, and the options for building: building or watching. If you're building more than one app,
// order them by the approximately largest project to smallest to build even faster.

const apps = [
    ["index.ts", "."]
];

const baseArgs = {
    bundle: true,
    write: true,
    sourcemap: true,
    minify: isProdBuild,
    splitting: true,
    treeShaking: true,
    external: ["*.woff", "*.woff2"],
    tsconfig: "./tsconfig.json",
    loader: { ".css": "text", ".md": "text" },
    define: definitions,
    format: "esm",
};

async function buildOneSource(source, dest) {
    const DIST = path.join(__dirname, "./dist", dest);
    console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`);

    try {
        const start = Date.now();
        copyOthers();
        await esbuild.build({
            ...baseArgs,
            entryPoints: [`./src/${source}`],
            entryNames: '[dir]/[name]',
            outdir: DIST,
        });
        const end = Date.now();
        console.log(
            `[${new Date(end).toISOString()}] Finished build for target ${source} in ${
                Date.now() - start
            }ms`,
        );
    } catch (exc) {
        console.error(`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`);
    }
}

async function buildAll(apps) {
    await Promise.allSettled(apps.map(([source, dest]) => buildOneSource(source, dest)));
}

let timeoutId = null;
function debouncedBuild() {
    if (timeoutId !== null) {
        clearTimeout(timeoutId);
    }
    timeoutId = setTimeout(() => {
         console.log("\x1bc");
        buildAll(apps);
    }, 250);
}

if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] === "--help")) {
    console.log(`Build:

options:
  -w, --watch: Build all ${apps.length} applications
  -h, --help: This help message
`);
    process.exit(0);
}

if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] === "--watch")) {
    console.log("Watching ./src for changes");
    chokidar.watch("./src").on("all", (event, path) => {
        if (!["add", "change", "unlink"].includes(event)) {
            return;
        }
        if (!/(\.css|\.ts|\.js)$/.test(path)) {
            return;
        }
        debouncedBuild();
    });
} else {
    await buildAll(apps);
}
