#!/usr/bin/env node

import fs from "node:fs";
import path from "node:path";

import copy from "esbuild-plugin-copy";

import { cleanPlugin } from "./pkg/lib/esbuild-cleanup-plugin.js";
import { cockpitCompressPlugin } from "./pkg/lib/esbuild-compress-plugin.js";
import { cockpitPoEsbuildPlugin } from "./pkg/lib/cockpit-po-plugin.js";
import { cockpitRsyncEsbuildPlugin } from "./pkg/lib/cockpit-rsync-plugin.js";
import { esbuildStylesPlugins } from "./pkg/lib/esbuild-common.js";

const esbuild = (await import("esbuild")).default;

const production = process.env.NODE_ENV === "production";
const watchMode = process.env.ESBUILD_WATCH === "true";
// List of directories to use when using import statements
const nodePaths = ["pkg/lib"];
const outdir = "dist";

// Obtain package name from package.json
const packageJson = JSON.parse(fs.readFileSync("package.json"));

function notifyEndPlugin() {
	return {
		name: "notify-end",
		setup(build) {
			let startTime;

			build.onStart(() => {
				startTime = new Date();
			});

			build.onEnd(() => {
				const endTime = new Date();
				const timeStamp = endTime.toTimeString().split(" ")[0];
				console.log(
					`${timeStamp}: Build finished in ${endTime - startTime} ms`,
				);
			});
		},
	};
}

const cwd = process.cwd();

// similar to fs.watch(), but recursively watches all subdirectories
function watch_dirs(dir, on_change) {
	const callback = (ev, dir, fname) => {
		// only listen for "change" events, as renames are noisy
		// ignore hidden files
		const isHidden = /^\./.test(fname);
		if (ev !== "change" || isHidden) {
			return;
		}
		on_change(path.join(dir, fname));
	};

	fs.watch(dir, {}, (ev, path) => callback(ev, dir, path));

	// watch all subdirectories in dir
	const d = fs.opendirSync(dir);
	let dirent;

	while ((dirent = d.readSync()) !== null) {
		if (dirent.isDirectory())
			watch_dirs(path.join(dir, dirent.name), on_change);
	}
	d.closeSync();
}

const context = await esbuild.context({
	...(!production ? { sourcemap: "linked" } : {}),
	bundle: true,
	entryPoints: ["./src/index.ts"],
	external: ["*.woff", "*.woff2", "*.jpg", "*.svg", "../../assets*"], // Allow external font files which live in ../../static/fonts
	legalComments: "external", // Move all legal comments to a .LEGAL.txt file
	loader: { ".ts": "tsx" },
	minify: production,
	nodePaths,
	outdir,
	target: ["es2020"],
	plugins: [
		cleanPlugin(),
		// Esbuild will only copy assets that are explicitly imported and used
		// in the code. This is a problem for index.html and manifest.json which are not imported
		copy({
			assets: [
				{ from: ["./src/manifest.json"], to: ["./manifest.json"] },
				{ from: ["./src/index.html"], to: ["./index.html"] },
			],
		}),
		...esbuildStylesPlugins,
		cockpitPoEsbuildPlugin(),
		...(production ? [cockpitCompressPlugin()] : []),
		cockpitRsyncEsbuildPlugin({ dest: packageJson.name }),
		notifyEndPlugin(),
	],
});

try {
	await context.rebuild();
} catch (e) {
	if (!watchMode) process.exit(1);
	// ignore errors in watch mode
}

if (watchMode) {
	const on_change = async (path) => {
		console.log("change detected:", path);
		await context.cancel();

		try {
			await context.rebuild();
		} catch (e) {} // ignore in watch mode
	};

	watch_dirs("src", on_change);

	// wait forever until Control-C
	await new Promise(() => {});
}

context.dispose();
