import { existsSync } from "node:fs";
import { join, resolve } from "node:path";
import { readFile } from "node:fs/promises";
//#region src/vite/validate-manifest.ts
/**
* Lightweight manifest validation for the SDK vite plugin.
*
* Inlined here (rather than imported from @fluid-app/portal-core) because
* portal-core is private and not published to npm. This avoids a runtime
* ERR_MODULE_NOT_FOUND for portals installed from npm.
*/
const VALID_FIELD_TYPES = [
	"text",
	"textarea",
	"number",
	"boolean",
	"select",
	"color",
	"range",
	"dataSource",
	"resource",
	"image",
	"alignment",
	"slider",
	"colorPicker",
	"sectionHeader",
	"separator",
	"buttonGroup",
	"colorSelect",
	"sectionLayoutSelect",
	"background",
	"contentPosition",
	"textSizeSelect",
	"cssUnit",
	"fontPicker",
	"stringArray",
	"borderRadius",
	"screenPicker"
];
function validateManifest(input) {
	const errors = [];
	const m = input;
	if (!m || typeof m !== "object") return {
		success: false,
		errors: [{
			path: "",
			message: "Manifest must be an object"
		}]
	};
	for (const key of [
		"type",
		"displayName",
		"description",
		"icon",
		"category"
	]) if (typeof m[key] !== "string" || m[key].length === 0) errors.push({
		path: key,
		message: `${key} is required and must be a non-empty string`
	});
	if (typeof m.manifestVersion !== "number" || m.manifestVersion < 1) errors.push({
		path: "manifestVersion",
		message: "manifestVersion must be a positive integer"
	});
	if (typeof m.component !== "function") errors.push({
		path: "component",
		message: "component must be a React component (function)"
	});
	const schema = m.propertySchema;
	if (schema && typeof schema === "object") {
		if (typeof schema.widgetType !== "string" || !schema.widgetType) errors.push({
			path: "propertySchema.widgetType",
			message: "widgetType is required"
		});
		if (typeof m.type === "string" && schema.widgetType !== m.type) errors.push({
			path: "propertySchema.widgetType",
			message: "manifest.type must match manifest.propertySchema.widgetType"
		});
		if (Array.isArray(schema.fields)) for (let i = 0; i < schema.fields.length; i++) {
			const field = schema.fields[i];
			if (!field || typeof field.type !== "string") continue;
			if (!VALID_FIELD_TYPES.includes(field.type)) errors.push({
				path: `propertySchema.fields.${i}.type`,
				message: `Invalid field type "${field.type}". Valid types: ${VALID_FIELD_TYPES.join(", ")}`
			});
		}
	}
	return errors.length === 0 ? { success: true } : {
		success: false,
		errors
	};
}
//#endregion
//#region src/vite/builder-preview-plugin.ts
const VIRTUAL_ENTRY_ID = "virtual:builder-preview-entry";
const RESOLVED_VIRTUAL_ID = "\0" + VIRTUAL_ENTRY_ID;
const RAW_HTML = `<!doctype html>
<html lang="en" data-theme-mode="dark">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Custom Widget Preview</title>
    <style>
      body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }
    </style>
  </head>
  <body>
    <div id="builder-preview-root"></div>
    <script type="module" src="/@id/virtual:builder-preview-entry"><\/script>
  </body>
</html>`;
/**
* Vite plugin that serves a standalone widget preview page at /builder-preview.
*
* Dev mode only. Renders all customWidgets from portal.config.ts with
* a live preview and property editor — no auth, no iframe, no fluid-admin needed.
*/
function fluidBuilderPreviewPlugin() {
	let configPath;
	let cssPath;
	return {
		name: "fluid-builder-preview",
		apply: "serve",
		configResolved(config) {
			const root = config.root;
			configPath = ["src/portal.config.ts", "portal.config.ts"].find((c) => existsSync(join(root, c))) ?? "src/portal.config.ts";
			cssPath = [
				"src/index.css",
				"src/styles/index.css",
				"index.css"
			].find((c) => existsSync(join(root, c))) ?? "src/index.css";
		},
		resolveId(id) {
			if (id === VIRTUAL_ENTRY_ID) return RESOLVED_VIRTUAL_ID;
		},
		load(id) {
			if (id === RESOLVED_VIRTUAL_ID) return `
import "/${cssPath}";
import * as portalConfig from "/${configPath}";
import { createRoot } from "react-dom/client";
import { createElement } from "react";
import { BuilderPreviewApp } from "@fluid-app/portal-preview";

const widgets = portalConfig.customWidgets || [];
const root = document.getElementById("builder-preview-root");
if (root) {
  createRoot(root).render(
    createElement(BuilderPreviewApp, { widgets })
  );
}
`;
		},
		configureServer(server) {
			server.middlewares.use(async (req, res, next) => {
				const pathname = (req.url ?? "").split("?")[0];
				if (pathname !== "/builder-preview" && pathname !== "/builder-preview/") return next();
				try {
					const transformed = await server.transformIndexHtml("/builder-preview", RAW_HTML);
					res.setHeader("Content-Type", "text/html");
					res.end(transformed);
				} catch (e) {
					server.config.logger.error(`[fluid] Failed to serve builder preview: ${e}`);
					res.statusCode = 500;
					res.end("Builder preview failed to load");
				}
			});
		}
	};
}
//#endregion
//#region src/vite/manifest-plugin.ts
/**
* Vite plugin bundle that serves widget manifest metadata and the builder preview.
*
* Returns an array of plugins:
* 1. Manifest plugin — serves /__manifests__ (dev) and emits __manifests__.json (build)
* 2. Builder preview plugin — serves /builder-preview with live widget editing (dev only)
*
* Every portal using `fluidManifestPlugin()` automatically gets the builder preview.
*/
function fluidManifestPlugin() {
	return [fluidManifestPluginInternal(), fluidBuilderPreviewPlugin()];
}
function fluidManifestPluginInternal() {
	let configPath;
	return {
		name: "fluid-manifest-plugin",
		configResolved(config) {
			const root = config.root;
			configPath = "/" + ([
				"src/widgets.config.ts",
				"src/portal.config.ts",
				"portal.config.ts"
			].find((c) => existsSync(join(root, c))) ?? "src/portal.config.ts");
		},
		configureServer(server) {
			server.middlewares.use("/__manifests__", async (_req, res) => {
				try {
					const serializable = await loadManifests(server, server.config.logger, configPath);
					res.setHeader("Content-Type", "application/json");
					res.setHeader("Access-Control-Allow-Origin", "*");
					res.end(JSON.stringify(serializable));
				} catch (err) {
					server.config.logger.error(`[fluid] Failed to load manifests: ${err}`);
					res.statusCode = 500;
					res.end(JSON.stringify({ error: String(err) }));
				}
			});
		},
		generateBundle() {
			this.warn("[fluid] fluidManifestPlugin: emitting empty __manifests__.json. Run `fluid build` instead of `vite build` to include widget manifests.");
			this.emitFile({
				type: "asset",
				fileName: "__manifests__.json",
				source: JSON.stringify([])
			});
		}
	};
}
/**
* Load and serialize manifests from the resolved widget config
* (widgets.config.ts or portal.config.ts) via Vite's ssrLoadModule.
* Validates each manifest before stripping the `component` field.
* Returns an empty array if the config module or export is missing/invalid.
*/
async function loadManifests(server, logger, configFilePath) {
	let mod;
	try {
		mod = await server.ssrLoadModule(configFilePath ?? "/src/portal.config.ts");
	} catch (err) {
		const message = err instanceof Error ? err.message : String(err);
		if (/(document|window|navigator|localStorage|sessionStorage|location|history|HTMLElement) is not defined/.test(message)) {
			const configFile = configFilePath ?? "/src/portal.config.ts";
			const fixHint = configFile.includes("portal.config") ? "  Fix: Create src/widgets.config.ts with only your customWidgets export.\n  The manifest plugin will load it instead of portal.config.ts.\n" : `  Fix: Ensure ${configFile} does not import browser-only code.\n`;
			logger?.warn(`[fluid] Cannot load widget manifests — ${configFile} imports browser-only code that fails during server-side evaluation.\n  Custom widgets will not appear in the builder.\n` + fixHint + "  Widget components are fine — the issue is usually screen imports\n  (e.g. DashboardScreen) that pull in the SDK barrel export.");
		} else logger?.warn(`[fluid] Could not load widget config: ${message}`);
		return [];
	}
	const rawWidgets = mod.customWidgets;
	if (!rawWidgets) {
		if (configFilePath?.includes("widgets.config")) logger?.warn("[fluid] widgets.config.ts was loaded but exports no customWidgets. Custom widgets will not appear in the builder.");
		return [];
	}
	if (!Array.isArray(rawWidgets)) {
		logger?.warn(`[fluid] customWidgets export is not an array (got ${typeof rawWidgets}). Skipping manifest serving.`);
		return [];
	}
	const manifests = rawWidgets;
	if (logger) for (const manifest of manifests) {
		const result = validateManifest(manifest);
		if (!result.success) {
			const type = manifest.type ?? "unknown";
			logger.warn(`[fluid] Invalid manifest for "${type}":\n` + result.errors.map((e) => `  - ${e.path}: ${e.message}`).join("\n"));
		}
	}
	return manifests.map(({ component: _component, ...rest }) => rest);
}
//#endregion
//#region ../core/src/preview/protocol.ts
const ALLOWED_ORIGIN_PATTERNS = [
	/^https?:\/\/localhost(:\d+)?$/,
	/^https?:\/\/127\.0\.0\.1(:\d+)?$/,
	/^https:\/\/[a-z0-9-]+\.portal\.fluid\.app$/,
	/^https:\/\/[a-z0-9-]+\.fluid\.app$/
];
/**
* Validate that a postMessage origin is trusted.
* Accepts localhost (any port), *.portal.fluid.app, and *.fluid.app.
*/
function isAllowedOrigin(origin) {
	return ALLOWED_ORIGIN_PATTERNS.some((pattern) => pattern.test(origin));
}
//#endregion
//#region src/vite/preview-plugin.ts
/**
* Vite plugin that serves the widget preview route.
*
* Dev mode: serves `/__preview__` by transforming preview.html through
* Vite's HTML pipeline (so import resolution and HMR work).
*
* Build mode: adds preview.html as a second Rollup input so `vite build`
* produces it alongside index.html for production deployment.
*/
function fluidPreviewPlugin() {
	return {
		name: "fluid-preview-plugin",
		config(_config, { command }) {
			if (command !== "build") return;
			const projectRoot = process.cwd();
			const previewHtml = resolve(projectRoot, "preview.html");
			if (!existsSync(previewHtml)) return;
			return { build: { rollupOptions: { input: {
				main: resolve(projectRoot, "index.html"),
				preview: previewHtml
			} } } };
		},
		configurePreviewServer(server) {
			server.middlewares.use("/__preview__", async (req, res) => {
				try {
					const distDir = server.config.build.outDir;
					const html = await readFile(resolve(distDir, "preview.html"), "utf-8");
					res.setHeader("Content-Type", "text/html");
					const origin = req.headers.origin;
					if (origin && isAllowedOrigin(origin)) {
						res.setHeader("Access-Control-Allow-Origin", origin);
						res.setHeader("Vary", "Origin");
					}
					res.statusCode = 200;
					res.end(html);
				} catch {
					res.statusCode = 404;
					res.end("preview.html not found in build output");
				}
			});
			server.middlewares.use("/__manifests__", async (_req, res) => {
				try {
					const distDir = server.config.build.outDir;
					const json = await readFile(resolve(distDir, "__manifests__.json"), "utf-8");
					res.setHeader("Content-Type", "application/json");
					res.setHeader("Access-Control-Allow-Origin", "*");
					res.statusCode = 200;
					res.end(json);
				} catch {
					res.statusCode = 404;
					res.end("__manifests__.json not found in build output");
				}
			});
		},
		configureServer(server) {
			server.middlewares.use("/__preview__", async (req, res) => {
				try {
					const htmlPath = resolve(server.config.root, "preview.html");
					let html;
					try {
						html = await readFile(htmlPath, "utf-8");
					} catch {
						html = getDefaultPreviewHtml();
					}
					html = await server.transformIndexHtml("/__preview__", html);
					res.setHeader("Content-Type", "text/html");
					const origin = req.headers.origin;
					if (origin && isAllowedOrigin(origin)) {
						res.setHeader("Access-Control-Allow-Origin", origin);
						res.setHeader("Vary", "Origin");
					}
					res.statusCode = 200;
					res.end(html);
				} catch (err) {
					server.config.logger.error(`[fluid] Failed to serve preview: ${err}`);
					res.statusCode = 500;
					res.end("Preview failed to load");
				}
			});
		}
	};
}
function getDefaultPreviewHtml() {
	return `<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Widget Preview</title>
    <meta name="referrer" content="strict-origin-when-cross-origin" />
    <style>body { margin: 0; padding: 0; }</style>
  </head>
  <body>
    <div id="preview-root"></div>
    <script type="module" src="/src/preview-entry.tsx"><\/script>
  </body>
</html>`;
}
//#endregion
export { fluidBuilderPreviewPlugin, fluidManifestPlugin, fluidPreviewPlugin };

//# sourceMappingURL=index.mjs.map