import { resolve } from "node:path";
import { defineConfig } from "vite";
import wasm from "vite-plugin-wasm";

// Path to Automerge shim that re-exports from window.__automerge (loaded by shell-offline)
const AUTOMERGE_SHIM = resolve(__dirname, 'shared/automerge-shim.ts');

// WASM-aware build wrapper — ensures every sub-build can handle Automerge WASM imports
// By default, aliases @automerge/automerge to a global shim (loaded by shell-offline)
// to avoid re-bundling ~2.5MB WASM per module. Set _bundleAutomerge = true to bundle
// the real Automerge (only needed for shell-offline and service worker builds).
async function wasmBuild(config: any) {
	const { build } = await import("vite");
	const bundleAutomerge = config._bundleAutomerge ?? false;
	const { _bundleAutomerge, ...restConfig } = config;

	return build({
		...restConfig,
		plugins: [...(restConfig.plugins || []), wasm()],
		resolve: {
			...restConfig.resolve,
			alias: {
				...(restConfig.resolve?.alias || {}),
				'@automerge/automerge': bundleAutomerge
					? resolve(__dirname, 'node_modules/@automerge/automerge')
					: AUTOMERGE_SHIM,
			},
		},
		build: {
			target: "esnext",
			...restConfig.build,
		},
	});
}

export default defineConfig({
	root: "website",
	plugins: [
		wasm(),
		// Build service worker as a separate, unhashed JS file
		{
			name: "build-sw",
			apply: "build",
			closeBundle: {
				sequential: true,
				async handler() {
					// build() calls use wasmBuild() wrapper
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "website"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist"),
							lib: {
								entry: resolve(__dirname, "website/sw.ts"),
								formats: ["es"],
								fileName: () => "sw.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "sw.js",
								},
							},
						},
					});

					// Build shell.ts as a standalone JS bundle (needs wasm() for Automerge via runtime)
					await wasmBuild({
						_bundleAutomerge: true,
						configFile: false,
						root: resolve(__dirname, "website"),
						plugins: [wasm()],
						resolve: {
							alias: {
								"@lib": resolve(__dirname, "./lib"),
								"@shared": resolve(__dirname, "./shared"),
							},
						},
						build: {
							target: "esnext",
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist"),
							lib: {
								entry: resolve(__dirname, "website/shell.ts"),
								formats: ["es"],
								fileName: () => "shell.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "shell.js",
									chunkFileNames: "assets/[name]-[hash].js",
								},
							},
						},
					});

					// Build books module components
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rbooks/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rbooks"),
							lib: {
								entry: resolve(__dirname, "modules/rbooks/components/folk-book-shelf.ts"),
								formats: ["es"],
								fileName: () => "folk-book-shelf.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-book-shelf.js",
								},
							},
						},
					});

					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rbooks/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rbooks"),
							lib: {
								entry: resolve(__dirname, "modules/rbooks/components/folk-book-reader.ts"),
								formats: ["es"],
								fileName: () => "folk-book-reader.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-book-reader.js",
								},
							},
						},
					});

					// Copy books CSS
					const { copyFileSync, mkdirSync } = await import("node:fs");
					mkdirSync(resolve(__dirname, "dist/modules/rbooks"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rbooks/components/books.css"),
						resolve(__dirname, "dist/modules/rbooks/books.css"),
					);

					// Build pubs module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rpubs/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rpubs"),
							lib: {
								entry: resolve(__dirname, "modules/rpubs/components/folk-pubs-editor.ts"),
								formats: ["es"],
								fileName: () => "folk-pubs-editor.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-pubs-editor.js",
								},
							},
						},
					});

					// Build pubs flipbook component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rpubs/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rpubs"),
							lib: {
								entry: resolve(__dirname, "modules/rpubs/components/folk-pubs-flipbook.ts"),
								formats: ["es"],
								fileName: () => "folk-pubs-flipbook.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-pubs-flipbook.js",
								},
							},
						},
					});

					// Build pubs publish panel component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rpubs/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rpubs"),
							lib: {
								entry: resolve(__dirname, "modules/rpubs/components/folk-pubs-publish-panel.ts"),
								formats: ["es"],
								fileName: () => "folk-pubs-publish-panel.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-pubs-publish-panel.js",
								},
							},
						},
					});

					// Copy pubs CSS
					mkdirSync(resolve(__dirname, "dist/modules/rpubs"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rpubs/components/pubs.css"),
						resolve(__dirname, "dist/modules/rpubs/pubs.css"),
					);

					// Build cart module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rcart/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rcart"),
							lib: {
								entry: resolve(__dirname, "modules/rcart/components/folk-cart-shop.ts"),
								formats: ["es"],
								fileName: () => "folk-cart-shop.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-cart-shop.js",
								},
							},
						},
					});

					// Build payment page component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rcart/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rcart"),
							lib: {
								entry: resolve(__dirname, "modules/rcart/components/folk-payment-page.ts"),
								formats: ["es"],
								fileName: () => "folk-payment-page.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-payment-page.js",
								},
							},
						},
					});

					// Build group buy page component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rcart/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rcart"),
							lib: {
								entry: resolve(__dirname, "modules/rcart/components/folk-group-buy-page.ts"),
								formats: ["es"],
								fileName: () => "folk-group-buy-page.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-group-buy-page.js",
								},
							},
						},
					});

					// Build payment request (QR generator) component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rcart/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rcart"),
							lib: {
								entry: resolve(__dirname, "modules/rcart/components/folk-payment-request.ts"),
								formats: ["es"],
								fileName: () => "folk-payment-request.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-payment-request.js",
								},
							},
						},
					});

					// Build payments dashboard component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rcart/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rcart"),
							lib: {
								entry: resolve(__dirname, "modules/rcart/components/folk-payments-dashboard.ts"),
								formats: ["es"],
								fileName: () => "folk-payments-dashboard.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-payments-dashboard.js",
								},
							},
						},
					});

					// Copy cart CSS
					mkdirSync(resolve(__dirname, "dist/modules/rcart"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rcart/components/cart.css"),
						resolve(__dirname, "dist/modules/rcart/cart.css"),
					);

					// Build swag module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rswag/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rswag"),
							lib: {
								entry: resolve(__dirname, "modules/rswag/components/folk-swag-designer.ts"),
								formats: ["es"],
								fileName: () => "folk-swag-designer.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-swag-designer.js",
								},
							},
						},
					});

					// Copy swag CSS
					mkdirSync(resolve(__dirname, "dist/modules/rswag"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rswag/components/swag.css"),
						resolve(__dirname, "dist/modules/rswag/swag.css"),
					);

					// Build choices module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rchoices/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rchoices"),
							lib: {
								entry: resolve(__dirname, "modules/rchoices/components/folk-choices-dashboard.ts"),
								formats: ["es"],
								fileName: () => "folk-choices-dashboard.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-choices-dashboard.js",
								},
							},
						},
					});

					// Copy choices CSS
					mkdirSync(resolve(__dirname, "dist/modules/rchoices"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rchoices/components/choices.css"),
						resolve(__dirname, "dist/modules/rchoices/choices.css"),
					);

					// Build crowdsurf module component (uses Automerge shim from shell-offline)
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/crowdsurf/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/crowdsurf"),
							lib: {
								entry: resolve(__dirname, "modules/crowdsurf/components/folk-crowdsurf-dashboard.ts"),
								formats: ["es"],
								fileName: () => "folk-crowdsurf-dashboard.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-crowdsurf-dashboard.js",
								},
							},
						},
					});

					// Copy crowdsurf CSS
					mkdirSync(resolve(__dirname, "dist/modules/crowdsurf"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/crowdsurf/components/crowdsurf.css"),
						resolve(__dirname, "dist/modules/crowdsurf/crowdsurf.css"),
					);

					// Build rtime module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rtime/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rtime"),
							lib: {
								entry: resolve(__dirname, "modules/rtime/components/folk-timebank-app.ts"),
								formats: ["es"],
								fileName: () => "folk-timebank-app.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-timebank-app.js",
								},
							},
						},
					});

					// Copy rtime CSS
					mkdirSync(resolve(__dirname, "dist/modules/rtime"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rtime/components/rtime.css"),
						resolve(__dirname, "dist/modules/rtime/rtime.css"),
					);

					// Build flows module components
					const flowsAlias = {
						"../lib/types": resolve(__dirname, "modules/rflows/lib/types.ts"),
						"../lib/simulation": resolve(__dirname, "modules/rflows/lib/simulation.ts"),
						"../lib/presets": resolve(__dirname, "modules/rflows/lib/presets.ts"),
						"../lib/map-flow": resolve(__dirname, "modules/rflows/lib/map-flow.ts"),
					};

					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rflows/components"),
						resolve: { alias: flowsAlias },
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rflows"),
							lib: {
								entry: resolve(__dirname, "modules/rflows/components/folk-flow-river.ts"),
								formats: ["es"],
								fileName: () => "folk-flow-river.js",
							},
							rollupOptions: { output: { entryFileNames: "folk-flow-river.js" } },
						},
					});

					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rflows/components"),
						resolve: {
							alias: flowsAlias,
						},
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rflows"),
							lib: {
								entry: resolve(__dirname, "modules/rflows/components/folk-flows-app.ts"),
								formats: ["es"],
								fileName: () => "folk-flows-app.js",
							},
							rollupOptions: { output: { entryFileNames: "folk-flows-app.js" } },
						},
					});

					// Build mortgage simulator component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rflows/components"),
						resolve: {
							alias: {
								"../lib/mortgage-types": resolve(__dirname, "modules/rflows/lib/mortgage-types.ts"),
								"../lib/mortgage-engine": resolve(__dirname, "modules/rflows/lib/mortgage-engine.ts"),
							},
						},
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rflows"),
							lib: {
								entry: resolve(__dirname, "modules/rflows/components/folk-mortgage-simulator.ts"),
								formats: ["es"],
								fileName: () => "folk-mortgage-simulator.js",
							},
							rollupOptions: { output: { entryFileNames: "folk-mortgage-simulator.js" } },
						},
					});

					// Copy flows CSS
					mkdirSync(resolve(__dirname, "dist/modules/rflows"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rflows/components/flows.css"),
						resolve(__dirname, "dist/modules/rflows/flows.css"),
					);

					// Build files module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rfiles/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rfiles"),
							lib: {
								entry: resolve(__dirname, "modules/rfiles/components/folk-file-browser.ts"),
								formats: ["es"],
								fileName: () => "folk-file-browser.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-file-browser.js",
								},
							},
						},
					});

					// Copy files CSS
					mkdirSync(resolve(__dirname, "dist/modules/rfiles"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rfiles/components/files.css"),
						resolve(__dirname, "dist/modules/rfiles/files.css"),
					);

					// Build forum module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rforum/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rforum"),
							lib: {
								entry: resolve(__dirname, "modules/rforum/components/folk-forum-dashboard.ts"),
								formats: ["es"],
								fileName: () => "folk-forum-dashboard.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-forum-dashboard.js",
								},
							},
						},
					});

					// Copy forum CSS
					mkdirSync(resolve(__dirname, "dist/modules/rforum"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rforum/components/forum.css"),
						resolve(__dirname, "dist/modules/rforum/forum.css"),
					);

					// Build wallet module component
					const walletAlias = {
						"../lib/data-transform": resolve(__dirname, "modules/rwallet/lib/data-transform.ts"),
						"../lib/wallet-viz": resolve(__dirname, "modules/rwallet/lib/wallet-viz.ts"),
						"../lib/wallet-demo-data": resolve(__dirname, "modules/rwallet/lib/wallet-demo-data.ts"),
					};

					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rwallet/components"),
						resolve: { alias: walletAlias },
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rwallet"),
							lib: {
								entry: resolve(__dirname, "modules/rwallet/components/folk-wallet-viewer.ts"),
								formats: ["es"],
								fileName: () => "folk-wallet-viewer.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-wallet-viewer.js",
								},
							},
						},
					});

					// Copy wallet CSS
					mkdirSync(resolve(__dirname, "dist/modules/rwallet"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rwallet/components/wallet.css"),
						resolve(__dirname, "dist/modules/rwallet/wallet.css"),
					);

					// Build vote module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rvote/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rvote"),
							lib: {
								entry: resolve(__dirname, "modules/rvote/components/folk-vote-dashboard.ts"),
								formats: ["es"],
								fileName: () => "folk-vote-dashboard.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-vote-dashboard.js",
								},
							},
						},
					});

					// Build vote demo page script
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rvote/components"),
						resolve: {
							alias: {
								"@lib": resolve(__dirname, "./lib"),
							},
						},
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rvote"),
							lib: {
								entry: resolve(__dirname, "modules/rvote/components/vote-demo.ts"),
								formats: ["es"],
								fileName: () => "vote-demo.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "vote-demo.js",
								},
							},
						},
					});

					// Copy vote CSS
					mkdirSync(resolve(__dirname, "dist/modules/rvote"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rvote/components/vote.css"),
						resolve(__dirname, "dist/modules/rvote/vote.css"),
					);

					// Build notes module component (uses Automerge shim from shell-offline)
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rnotes/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rnotes"),
							lib: {
								entry: resolve(__dirname, "modules/rnotes/components/folk-notes-app.ts"),
								formats: ["es"],
								fileName: () => "folk-notes-app.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-notes-app.js",
								},
							},
						},
					});

					// Build docs module component (uses Automerge shim from shell-offline)
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rdocs/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rdocs"),
							lib: {
								entry: resolve(__dirname, "modules/rdocs/components/folk-docs-app.ts"),
								formats: ["es"],
								fileName: () => "folk-docs-app.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-docs-app.js",
									inlineDynamicImports: true,
								},
							},
						},
					});

					// Build voice recorder component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rdocs/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rdocs"),
							lib: {
								entry: resolve(__dirname, "modules/rdocs/components/folk-voice-recorder.ts"),
								formats: ["es"],
								fileName: () => "folk-voice-recorder.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-voice-recorder.js",
								},
							},
						},
					});

					// Copy docs CSS
					mkdirSync(resolve(__dirname, "dist/modules/rdocs"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rdocs/components/notes.css"),
						resolve(__dirname, "dist/modules/rdocs/notes.css"),
					);

					// Build maps module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rmaps/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rmaps"),
							lib: {
								entry: resolve(__dirname, "modules/rmaps/components/folk-map-viewer.ts"),
								formats: ["es"],
								fileName: () => "folk-map-viewer.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-map-viewer.js",
								},
							},
						},
					});

					// Copy maps CSS
					mkdirSync(resolve(__dirname, "dist/modules/rmaps"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rmaps/components/maps.css"),
						resolve(__dirname, "dist/modules/rmaps/maps.css"),
					);

					// Build tasks module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rtasks/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rtasks"),
							lib: {
								entry: resolve(__dirname, "modules/rtasks/components/folk-tasks-board.ts"),
								formats: ["es"],
								fileName: () => "folk-tasks-board.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-tasks-board.js",
								},
							},
						},
					});

					// Copy tasks CSS
					mkdirSync(resolve(__dirname, "dist/modules/rtasks"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rtasks/components/tasks.css"),
						resolve(__dirname, "dist/modules/rtasks/tasks.css"),
					);

					// Build chats module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rchats/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rchats"),
							lib: {
								entry: resolve(__dirname, "modules/rchats/components/folk-chat-app.ts"),
								formats: ["es"],
								fileName: () => "folk-chat-app.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-chat-app.js",
								},
							},
						},
					});

					// Build trips module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rtrips/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rtrips"),
							lib: {
								entry: resolve(__dirname, "modules/rtrips/components/folk-trips-planner.ts"),
								formats: ["es"],
								fileName: () => "folk-trips-planner.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-trips-planner.js",
								},
							},
						},
					});

					// Copy trips CSS
					mkdirSync(resolve(__dirname, "dist/modules/rtrips"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rtrips/components/trips.css"),
						resolve(__dirname, "dist/modules/rtrips/trips.css"),
					);

					// Build trips demo page script
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rtrips/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rtrips"),
							lib: {
								entry: resolve(__dirname, "modules/rtrips/components/trips-demo.ts"),
								formats: ["es"],
								fileName: () => "trips-demo.js",
							},
							rollupOptions: {
								output: { entryFileNames: "trips-demo.js" },
							},
						},
					});

					// Build cal module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rcal/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rcal"),
							lib: {
								entry: resolve(__dirname, "modules/rcal/components/folk-calendar-view.ts"),
								formats: ["es"],
								fileName: () => "folk-calendar-view.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-calendar-view.js",
								},
							},
						},
					});

					// Copy cal CSS
					mkdirSync(resolve(__dirname, "dist/modules/rcal"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rcal/components/cal.css"),
						resolve(__dirname, "dist/modules/rcal/cal.css"),
					);

					// Build rpast module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rpast/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rpast"),
							lib: {
								entry: resolve(__dirname, "modules/rpast/components/rpast-viewer.ts"),
								formats: ["es"],
								fileName: () => "rpast-viewer.js",
							},
							rollupOptions: {
								output: { entryFileNames: "rpast-viewer.js" },
							},
						},
					});

					// Build network module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rnetwork/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rnetwork"),
							lib: {
								entry: resolve(__dirname, "modules/rnetwork/components/folk-graph-viewer.ts"),
								formats: ["es"],
								fileName: () => "folk-graph-viewer.js",
							},
							rollupOptions: {
								external: ["three", "three/addons/", "3d-force-graph"],
								output: {
									entryFileNames: "folk-graph-viewer.js",
								},
							},
						},
					});

					// Build network CRM view component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rnetwork/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rnetwork"),
							lib: {
								entry: resolve(__dirname, "modules/rnetwork/components/folk-crm-view.ts"),
								formats: ["es"],
								fileName: () => "folk-crm-view.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-crm-view.js",
								},
							},
						},
					});

					// Build delegation manager component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rnetwork/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rnetwork"),
							lib: {
								entry: resolve(__dirname, "modules/rnetwork/components/folk-delegation-manager.ts"),
								formats: ["es"],
								fileName: () => "folk-delegation-manager.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-delegation-manager.js",
								},
							},
						},
					});

					// Build trust sankey component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rnetwork/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rnetwork"),
							lib: {
								entry: resolve(__dirname, "modules/rnetwork/components/folk-trust-sankey.ts"),
								formats: ["es"],
								fileName: () => "folk-trust-sankey.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-trust-sankey.js",
								},
							},
						},
					});

					// Build power indices component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rnetwork/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rnetwork"),
							lib: {
								entry: resolve(__dirname, "modules/rnetwork/components/folk-power-indices.ts"),
								formats: ["es"],
								fileName: () => "folk-power-indices.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-power-indices.js",
								},
							},
						},
					});

					// Copy network CSS
					mkdirSync(resolve(__dirname, "dist/modules/rnetwork"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rnetwork/components/network.css"),
						resolve(__dirname, "dist/modules/rnetwork/network.css"),
					);

					// Build socials thread builder component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rsocials/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rsocials"),
							lib: {
								entry: resolve(__dirname, "modules/rsocials/components/folk-thread-builder.ts"),
								formats: ["es"],
								fileName: () => "folk-thread-builder.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-thread-builder.js",
								},
							},
						},
					});

					// Build socials thread gallery component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rsocials/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rsocials"),
							lib: {
								entry: resolve(__dirname, "modules/rsocials/components/folk-thread-gallery.ts"),
								formats: ["es"],
								fileName: () => "folk-thread-gallery.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-thread-gallery.js",
								},
							},
						},
					});

					// Build socials campaign manager component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rsocials/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rsocials"),
							lib: {
								entry: resolve(__dirname, "modules/rsocials/components/folk-campaign-manager.ts"),
								formats: ["es"],
								fileName: () => "folk-campaign-manager.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-campaign-manager.js",
								},
							},
						},
					});

					// Copy socials CSS
					mkdirSync(resolve(__dirname, "dist/modules/rsocials"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rsocials/components/socials.css"),
						resolve(__dirname, "dist/modules/rsocials/socials.css"),
					);

					// Build campaign planner component (uses Automerge shim from shell-offline)
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rsocials/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rsocials"),
							lib: {
								entry: resolve(__dirname, "modules/rsocials/components/folk-campaign-planner.ts"),
								formats: ["es"],
								fileName: () => "folk-campaign-planner.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-campaign-planner.js",
								},
							},
						},
					});

					// Copy campaign planner CSS
					copyFileSync(
						resolve(__dirname, "modules/rsocials/components/campaign-planner.css"),
						resolve(__dirname, "dist/modules/rsocials/campaign-planner.css"),
					);

					// Build campaign workflow builder component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rsocials/components"),
						resolve: {
							alias: {
								"../schemas": resolve(__dirname, "modules/rsocials/schemas.ts"),
							},
						},
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rsocials"),
							lib: {
								entry: resolve(__dirname, "modules/rsocials/components/folk-campaign-workflow.ts"),
								formats: ["es"],
								fileName: () => "folk-campaign-workflow.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-campaign-workflow.js",
								},
							},
						},
					});

					// Copy campaign workflow CSS
					copyFileSync(
						resolve(__dirname, "modules/rsocials/components/campaign-workflow.css"),
						resolve(__dirname, "dist/modules/rsocials/campaign-workflow.css"),
					);

					// Build campaigns dashboard component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rsocials/components"),
						resolve: {
							alias: {
								"../schemas": resolve(__dirname, "modules/rsocials/schemas.ts"),
							},
						},
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rsocials"),
							lib: {
								entry: resolve(__dirname, "modules/rsocials/components/folk-campaigns-dashboard.ts"),
								formats: ["es"],
								fileName: () => "folk-campaigns-dashboard.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-campaigns-dashboard.js",
								},
							},
						},
					});

					// Build newsletter manager component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rsocials/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rsocials"),
							lib: {
								entry: resolve(__dirname, "modules/rsocials/components/folk-newsletter-manager.ts"),
								formats: ["es"],
								fileName: () => "folk-newsletter-manager.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-newsletter-manager.js",
								},
							},
						},
					});

					// Copy newsletter CSS
					copyFileSync(
						resolve(__dirname, "modules/rsocials/components/newsletter.css"),
						resolve(__dirname, "dist/modules/rsocials/newsletter.css"),
					);

					// Build campaign wizard component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rsocials/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rsocials"),
							lib: {
								entry: resolve(__dirname, "modules/rsocials/components/folk-campaign-wizard.ts"),
								formats: ["es"],
								fileName: () => "folk-campaign-wizard.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-campaign-wizard.js",
								},
							},
						},
					});

					// Copy campaign wizard CSS
					copyFileSync(
						resolve(__dirname, "modules/rsocials/components/campaign-wizard.css"),
						resolve(__dirname, "dist/modules/rsocials/campaign-wizard.css"),
					);

					// Build tube module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rtube/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rtube"),
							lib: {
								entry: resolve(__dirname, "modules/rtube/components/folk-video-player.ts"),
								formats: ["es"],
								fileName: () => "folk-video-player.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-video-player.js",
								},
							},
						},
					});

					// Copy tube CSS
					mkdirSync(resolve(__dirname, "dist/modules/rtube"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rtube/components/tube.css"),
						resolve(__dirname, "dist/modules/rtube/tube.css"),
					);

					// Build inbox module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rinbox/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rinbox"),
							lib: {
								entry: resolve(__dirname, "modules/rinbox/components/folk-inbox-client.ts"),
								formats: ["es"],
								fileName: () => "folk-inbox-client.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-inbox-client.js",
								},
							},
						},
					});

					// Copy inbox CSS
					mkdirSync(resolve(__dirname, "dist/modules/rinbox"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rinbox/components/inbox.css"),
						resolve(__dirname, "dist/modules/rinbox/inbox.css"),
					);

					// Build data module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rdata/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rdata"),
							lib: {
								entry: resolve(__dirname, "modules/rdata/components/folk-analytics-view.ts"),
								formats: ["es"],
								fileName: () => "folk-analytics-view.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-analytics-view.js",
								},
							},
						},
					});

					// Build content tree component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rdata/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rdata"),
							lib: {
								entry: resolve(__dirname, "modules/rdata/components/folk-content-tree.ts"),
								formats: ["es"],
								fileName: () => "folk-content-tree.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-content-tree.js",
								},
							},
						},
					});

					// Build data cloud component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rdata/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rdata"),
							lib: {
								entry: resolve(__dirname, "modules/rdata/components/folk-data-cloud.ts"),
								formats: ["es"],
								fileName: () => "folk-data-cloud.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-data-cloud.js",
								},
							},
						},
					});

					// Copy data CSS
					mkdirSync(resolve(__dirname, "dist/modules/rdata"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rdata/components/data.css"),
						resolve(__dirname, "dist/modules/rdata/data.css"),
					);

					// Build route planner component (part of trips module)
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rtrips/components"),
						resolve: {
							alias: {
								"../lib/types": resolve(__dirname, "modules/rtrips/lib/types.ts"),
								"../lib/conic-math": resolve(__dirname, "modules/rtrips/lib/conic-math.ts"),
								"../lib/projection": resolve(__dirname, "modules/rtrips/lib/projection.ts"),
							},
						},
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rtrips"),
							lib: {
								entry: resolve(__dirname, "modules/rtrips/components/folk-route-planner.ts"),
								formats: ["es"],
								fileName: () => "folk-route-planner.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-route-planner.js",
								},
							},
						},
					});

					// Copy route planner CSS
					copyFileSync(
						resolve(__dirname, "modules/rtrips/components/route-planner.css"),
						resolve(__dirname, "dist/modules/rtrips/route-planner.css"),
					);

					// Build splat module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rsplat/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rsplat"),
							lib: {
								entry: resolve(__dirname, "modules/rsplat/components/folk-splat-viewer.ts"),
								formats: ["es"],
								fileName: () => "folk-splat-viewer.js",
							},
							rollupOptions: {
								external: [/^three($|\/)/, "@mkkellogg/gaussian-splats-3d"],
								output: {
									entryFileNames: "folk-splat-viewer.js",
								},
							},
						},
					});

					// Copy splat CSS
					mkdirSync(resolve(__dirname, "dist/modules/rsplat"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rsplat/components/splat.css"),
						resolve(__dirname, "dist/modules/rsplat/splat.css"),
					);

					// Build photos module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rphotos/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rphotos"),
							lib: {
								entry: resolve(__dirname, "modules/rphotos/components/folk-photo-gallery.ts"),
								formats: ["es"],
								fileName: () => "folk-photo-gallery.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-photo-gallery.js",
								},
							},
						},
					});

					// Copy photos CSS
					mkdirSync(resolve(__dirname, "dist/modules/rphotos"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rphotos/components/photos.css"),
						resolve(__dirname, "dist/modules/rphotos/photos.css"),
					);

					// Build schedule module component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rschedule/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rschedule"),
							lib: {
								entry: resolve(__dirname, "modules/rschedule/components/folk-schedule-app.ts"),
								formats: ["es"],
								fileName: () => "folk-schedule-app.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-schedule-app.js",
								},
							},
						},
					});

					// Copy schedule CSS
					mkdirSync(resolve(__dirname, "dist/modules/rschedule"), { recursive: true });
					copyFileSync(
						resolve(__dirname, "modules/rschedule/components/schedule.css"),
						resolve(__dirname, "dist/modules/rschedule/schedule.css"),
					);

					// Build schedule reminders widget component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rschedule/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rschedule"),
							lib: {
								entry: resolve(__dirname, "modules/rschedule/components/folk-reminders-widget.ts"),
								formats: ["es"],
								fileName: () => "folk-reminders-widget.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-reminders-widget.js",
								},
							},
						},
					});

					// Build schedule automation canvas component
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rschedule/components"),
						resolve: {
							alias: {
								"../schemas": resolve(__dirname, "modules/rschedule/schemas.ts"),
							},
						},
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rschedule"),
							lib: {
								entry: resolve(__dirname, "modules/rschedule/components/folk-automation-canvas.ts"),
								formats: ["es"],
								fileName: () => "folk-automation-canvas.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-automation-canvas.js",
								},
							},
						},
					});

					// Copy automation canvas CSS
					copyFileSync(
						resolve(__dirname, "modules/rschedule/components/automation-canvas.css"),
						resolve(__dirname, "dist/modules/rschedule/automation-canvas.css"),
					);

					// ── Demo infrastructure ──

					// Build demo-sync-vanilla library
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "lib"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/lib"),
							lib: {
								entry: resolve(__dirname, "lib/demo-sync-vanilla.ts"),
								formats: ["es"],
								fileName: () => "demo-sync.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "demo-sync.js",
								},
							},
						},
					});

					// Build demo scripts for each module that has one
					const demoModules = ["cart", "vote", "flows", "notes", "cal", "tube", "trips"];
					for (const mod of demoModules) {
						const dir = `r${mod}`;
						const demoEntry = resolve(__dirname, `modules/${dir}/components/${mod}-demo.ts`);
						try {
							const { statSync } = await import("node:fs");
							statSync(demoEntry);
							await wasmBuild({
								configFile: false,
								root: resolve(__dirname, `modules/${dir}/components`),
								resolve: {
									alias: {
										"@lib": resolve(__dirname, "./lib"),
									},
								},
								build: {
									emptyOutDir: false,
									outDir: resolve(__dirname, `dist/modules/${dir}`),
									lib: {
										entry: demoEntry,
										formats: ["es"],
										fileName: () => `${mod}-demo.js`,
									},
									rollupOptions: {
										output: {
											entryFileNames: `${mod}-demo.js`,
										},
									},
								},
							});
						} catch {
							// Demo script not yet created — skip
						}
					}

					// ── Build rGov circuit canvas component ──
					mkdirSync(resolve(__dirname, "dist/modules/rgov"), { recursive: true });
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rgov/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rgov"),
							lib: {
								entry: resolve(__dirname, "modules/rgov/components/folk-gov-circuit.ts"),
								formats: ["es"],
								fileName: () => "folk-gov-circuit.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-gov-circuit.js",
								},
							},
						},
					});

					// ── rCred: folk-cred-dashboard ──
					mkdirSync(resolve(__dirname, "dist/modules/rcred"), { recursive: true });
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rcred/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rcred"),
							lib: {
								entry: resolve(__dirname, "modules/rcred/components/folk-cred-dashboard.ts"),
								formats: ["es"],
								fileName: () => "folk-cred-dashboard.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-cred-dashboard.js",
								},
							},
						},
					});

					// ── rFeeds: folk-feeds-dashboard ──
					mkdirSync(resolve(__dirname, "dist/modules/rfeeds"), { recursive: true });
					await wasmBuild({
						configFile: false,
						root: resolve(__dirname, "modules/rfeeds/components"),
						build: {
							emptyOutDir: false,
							outDir: resolve(__dirname, "dist/modules/rfeeds"),
							lib: {
								entry: resolve(__dirname, "modules/rfeeds/components/folk-feeds-dashboard.ts"),
								formats: ["es"],
								fileName: () => "folk-feeds-dashboard.js",
							},
							rollupOptions: {
								output: {
									entryFileNames: "folk-feeds-dashboard.js",
								},
							},
						},
					});
					copyFileSync(
						resolve(__dirname, "modules/rfeeds/components/feeds.css"),
						resolve(__dirname, "dist/modules/rfeeds/feeds.css"),
					);

					// ── Generate content hashes for cache-busting ──
					const { readdirSync, readFileSync, writeFileSync, statSync: statSync2 } = await import("node:fs");
					const { createHash } = await import("node:crypto");
					const distDir = resolve(__dirname, "dist");

					function walkDir(dir: string, prefix = ""): string[] {
						const entries: string[] = [];
						for (const entry of readdirSync(dir, { withFileTypes: true })) {
							const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
							if (entry.isDirectory()) {
								entries.push(...walkDir(resolve(dir, entry.name), rel));
							} else {
								entries.push(`/${rel}`);
							}
						}
						return entries;
					}

					const allFiles = walkDir(distDir);

					// Compute 8-char SHA-256 content hashes for module + shell assets
					const hashableFiles = allFiles.filter((f) =>
						(f.startsWith("/modules/") && (f.endsWith(".js") || f.endsWith(".css")) && !f.includes("-demo.js")) ||
						f === "/shell.js" || f === "/shell.css" || f === "/theme.css" ||
						(f.startsWith("/assets/shell-") && f.endsWith(".js"))
					);
					const hashes: Record<string, string> = {};
					for (const file of hashableFiles) {
						const content = readFileSync(resolve(distDir, file.slice(1)));
						hashes[file] = createHash("sha256").update(content).digest("hex").slice(0, 8);
					}
					writeFileSync(
						resolve(distDir, "module-hashes.json"),
						JSON.stringify(hashes, null, "\t"),
					);
					console.log(`[hashes] Generated content hashes for ${Object.keys(hashes).length} assets`);

					// ── Generate precache manifest ──
					// Core: shell assets + HTML pages (precached at install, ~300KB)
					const core = allFiles.filter((f) =>
						f === "/" ||
						f === "/index.html" ||
						f === "/canvas.html" ||
						f === "/create-space.html" ||
						f === "/admin.html" ||
						f === "/shell.js" ||
						f === "/shell.css" ||
						f === "/theme.css" ||
						f === "/favicon.png" ||
						f === "/manifest.json" ||
						(f.startsWith("/assets/shell-") && f.endsWith(".js"))
					).map((f) => hashes[f] ? `${f}?v=${hashes[f]}` : f);
					// Ensure root URL is present
					if (!core.some((f) => f === "/" || f.startsWith("/?v="))) core.unshift("/");

					// Modules: all module JS + CSS (lazy-cached after activation)
					const modules = allFiles.filter((f) =>
						f.startsWith("/modules/") &&
						(f.endsWith(".js") || f.endsWith(".css")) &&
						!f.includes("-demo.js")
					).map((f) => hashes[f] ? `${f}?v=${hashes[f]}` : f);

					const manifest = {
						version: new Date().toISOString(),
						core,
						modules,
						hashes,
					};

					writeFileSync(
						resolve(distDir, "precache-manifest.json"),
						JSON.stringify(manifest, null, "\t"),
					);
					console.log(`[precache] Generated manifest: ${core.length} core + ${modules.length} module assets`);
				},
			},
		},
	],
	resolve: {
		alias: {
			"@lib": resolve(__dirname, "./lib"),
			"@encryptid": resolve(__dirname, "./src/encryptid"),
			"@shared": resolve(__dirname, "./shared"),
		},
	},
	build: {
		target: "esnext",
		rollupOptions: {
			input: {
				index: resolve(__dirname, "./website/index.html"),
				canvas: resolve(__dirname, "./website/canvas.html"),
				"create-space": resolve(__dirname, "./website/create-space.html"),
				admin: resolve(__dirname, "./website/admin.html"),
			},
		},
		modulePreload: {
			polyfill: false,
		},
		outDir: "../dist",
		emptyOutDir: true,
		// Copy shell.css to dist
		cssCodeSplit: false,
	},
	server: {
		port: 5173,
	},
	optimizeDeps: {
		exclude: ["@automerge/automerge"],
	},
});
