import { getAllModels, getAvailableModels, init_providers } from "../../../providers/index.js";
import { ActivationPlanner } from "../../../extensions/activation-planner.js";
import { mergeActivationContext } from "../../../extensions/activation-context.js";
import { fetchRegistry, listExtensions, searchExtensions } from "../../../extensions/marketplace.js";
import { buildWhenContextSnapshot } from "../../../extensions/when-context.js";
import { createOAuthHandler } from "../oauth.js";
import { createOAuthAsyncHandler } from "../oauth-async.js";
import { extensionAssetMimeType } from "../lib/extension-assets.js";
import { loadExtensionStore, saveExtensionStore } from "../lib/extension-store.js";
import { existsSync, readFileSync, statSync } from "node:fs";
import { relative, resolve } from "node:path";
//#region src/gateway/hono/routes/auth-registry-extensions.ts
init_providers();
const EXTENSION_ASSET_CSP = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'none'; frame-ancestors 'self'; frame-src 'none'; base-uri 'none'; object-src 'none'; form-action 'none'";
/** Sandboxed extension iframes send `Origin: null`; echo it (and real origins) so every status includes ACAO. */
function extensionUiAssetCorsAllowOrigin(c) {
	const origin = c.req.header("origin");
	if (origin === "null") return "null";
	if (origin) return origin;
	return "*";
}
function rewriteExtensionAssetHtml(html, extensionId, assetPath) {
	const assetBase = `/api/extensions/${extensionId}/assets/${assetPath.includes("/") ? assetPath.slice(0, assetPath.lastIndexOf("/") + 1) : ""}`;
	return html.replace(/(<script\b[^>]*?\ssrc=)(["'])(\.\/)?([^"']+)\2/gi, (_match, tag, quote, _dot, file) => {
		return `${tag}${quote}${file.startsWith("/") || file.startsWith("http://") || file.startsWith("https://") ? file : `${assetBase}${file}`}${quote} crossorigin="anonymous"`;
	}).replace(/(<link\b[^>]*?\shref=)(["'])(\.\/)?([^"']+)\2/gi, (_match, tag, quote, _dot, file) => {
		return `${tag}${quote}${file.startsWith("/") || file.startsWith("http://") || file.startsWith("https://") ? file : `${assetBase}${file}`}${quote}`;
	});
}
function hasNonEmptyConfigSchema(schema) {
	if (!schema || typeof schema !== "object") return false;
	const properties = schema.properties;
	return Boolean(properties && typeof properties === "object" && Object.keys(properties).length > 0);
}
/**
* Register extension UI asset routes on the public (unauthenticated) app.
*
* Sandboxed iframes (`sandbox="allow-scripts …"` without `allow-same-origin`) have an
* opaque origin of `null`, so sub-resource requests from inside the iframe cannot
* carry the `?token=` query parameter that was on the parent HTML URL. Putting these
* routes behind the auth middleware therefore causes every JS/CSS asset to return 401.
*
* Security is maintained by the strict Content-Security-Policy returned with every
* asset (`frame-ancestors 'self'`), which prevents any page other than the gateway
* console itself from embedding the extension iframes.
*/
function registerPublicExtensionAssetRoutes(app, service) {
	app.get("/api/extensions/:id/assets/*", async (c) => {
		const acao = extensionUiAssetCorsAllowOrigin(c);
		const corsHdr = { "Access-Control-Allow-Origin": acao };
		const extensionId = c.req.param("id");
		const loader = service.getExtensionLoader();
		if (!loader) return c.json({ error: "Extensions unavailable" }, 503, corsHdr);
		const ext = loader.discoverExtensions().find((e) => e.id === extensionId);
		if (!ext || !ext.manifest.ui) return c.json({ error: "Extension not found or has no UI" }, 404, corsHdr);
		const prefix = `/api/extensions/${extensionId}/assets/`;
		const assetPathEncoded = c.req.path.startsWith(prefix) ? c.req.path.slice(prefix.length) : "";
		let assetPath = assetPathEncoded;
		try {
			assetPath = decodeURIComponent(assetPathEncoded);
		} catch {
			return c.json({ error: "Invalid asset path" }, 400, corsHdr);
		}
		if (!assetPath || assetPath.includes("..")) return c.json({ error: "Invalid asset path" }, 400, corsHdr);
		const root = resolve(ext.path);
		const fullPath = resolve(root, assetPath);
		const rel = relative(root, fullPath);
		if (rel.startsWith("..") || rel === "") return c.json({ error: "Path traversal denied" }, 403, corsHdr);
		if (!existsSync(fullPath) || !statSync(fullPath).isFile()) return c.json({ error: "Not found" }, 404, corsHdr);
		const rawContent = readFileSync(fullPath);
		const mimeType = extensionAssetMimeType(assetPath);
		const body = mimeType.startsWith("text/html") ? rewriteExtensionAssetHtml(rawContent.toString("utf-8"), extensionId, assetPath) : new Uint8Array(rawContent);
		return new Response(body, {
			status: 200,
			headers: {
				"Content-Type": mimeType,
				"Content-Security-Policy": EXTENSION_ASSET_CSP,
				"Cache-Control": "no-store",
				"X-Content-Type-Options": "nosniff",
				"Access-Control-Allow-Origin": acao
			}
		});
	});
}
function registerAuthRegistryExtensionsRoutes(authenticated, deps) {
	const { service, strictRateLimitMiddleware } = deps;
	authenticated.get("/api/auth/token", (c) => {
		const authToken = service.getAuthToken();
		return c.json({
			ok: true,
			payload: {
				token: authToken,
				mode: service.getAuthMode()
			}
		});
	});
	authenticated.post("/api/auth/token/refresh", async (c) => {
		try {
			const newToken = await service.refreshAuthToken();
			return c.json({
				ok: true,
				payload: {
					token: newToken,
					message: "Token refreshed successfully. Please update your client configuration."
				}
			});
		} catch (err) {
			return c.json({
				ok: false,
				error: err instanceof Error ? err.message : "Failed to refresh token"
			}, 500);
		}
	});
	authenticated.route("/api/auth/oauth", createOAuthHandler(service));
	authenticated.route("/api/auth/oauth-async", createOAuthAsyncHandler(service));
	authenticated.get("/api/registry", async (c) => {
		const allModels = getAllModels();
		const availableModels = await getAvailableModels();
		const configured = new Set(availableModels.map((m) => `${m.provider}/${m.id}`));
		const providerMap = /* @__PURE__ */ new Map();
		for (const model of allModels) {
			const list = providerMap.get(model.provider) ?? [];
			list.push(model);
			providerMap.set(model.provider, list);
		}
		return c.json({
			ok: true,
			payload: {
				version: "pi-ai",
				providers: Array.from(providerMap.entries()).map(([id, models]) => ({
					id,
					name: id.charAt(0).toUpperCase() + id.slice(1),
					configured: models.some((m) => configured.has(`${m.provider}/${m.id}`)),
					models: models.map((m) => ({
						ref: `${m.provider}/${m.id}`,
						id: m.id,
						name: m.name,
						provider: m.provider,
						reasoning: m.reasoning ?? false,
						input: m.input ?? ["text"],
						contextWindow: m.contextWindow ?? 128e3,
						maxTokens: m.maxTokens ?? 4096,
						cost: {
							input: m.cost?.input ?? 0,
							output: m.cost?.output ?? 0
						},
						available: configured.has(`${m.provider}/${m.id}`)
					}))
				}))
			}
		});
	});
	authenticated.get("/api/extensions", async (c) => {
		const loader = service.getExtensionLoader();
		if (!loader) return c.json({ extensions: [] });
		const registry = loader.getRegistry();
		const discovered = loader.discoverExtensions();
		const activeIds = /* @__PURE__ */ new Set();
		for (const [id] of registry.extensions) activeIds.add(id);
		loader.setConfig(service.currentConfig);
		let activationEligibleIds = /* @__PURE__ */ new Set();
		try {
			const planner = new ActivationPlanner(loader.buildManifestRegistry());
			activationEligibleIds = new Set(planner.getActivatedIds(mergeActivationContext(service.currentConfig)));
		} catch {
			activationEligibleIds = /* @__PURE__ */ new Set();
		}
		const extensions = discovered.map((ext) => ({
			id: ext.manifest.id,
			name: ext.manifest.name,
			description: ext.manifest.description,
			version: ext.manifest.version,
			kind: ext.manifest.kind,
			source: ext.source,
			active: activeIds.has(ext.id),
			activationEligible: activationEligibleIds.has(ext.id),
			hasUi: Boolean(ext.manifest.ui),
			hasConfigSchema: hasNonEmptyConfigSchema(ext.manifest.configSchema),
			ui: ext.manifest.ui ? {
				icon: ext.manifest.ui.icon,
				permissions: ext.manifest.ui.permissions,
				contributions: ext.manifest.ui.contributions
			} : void 0
		}));
		return c.json({ extensions });
	});
	/**
	* Built-in (bundled) extension enable/disable — persists `extensions.enabled` / `extensions.disabled`.
	* Body: `{ extensionId: string, enabled: boolean }`
	*/
	authenticated.post("/api/extensions/bundled/activation", strictRateLimitMiddleware, async (c) => {
		const body = await c.req.json().catch(() => null);
		const extensionId = typeof body?.extensionId === "string" ? body.extensionId.trim() : "";
		if (!extensionId) return c.json({
			ok: false,
			error: { message: "extensionId is required" }
		}, 400);
		if (typeof body?.enabled !== "boolean") return c.json({
			ok: false,
			error: { message: "enabled must be a boolean" }
		}, 400);
		const result = await service.setBundledExtensionActivationTarget(extensionId, body.enabled);
		if (!result.ok) return c.json({
			ok: false,
			error: { message: result.error ?? "Request failed" }
		}, 400);
		return c.json({
			ok: true,
			payload: { requiresGatewayRestart: result.requiresGatewayRestart }
		});
	});
	authenticated.get("/api/extensions/:id", async (c) => {
		const extensionId = c.req.param("id");
		const loader = service.getExtensionLoader();
		if (!loader) return c.json({ error: "Extensions unavailable" }, 503);
		const ext = loader.discoverExtensions().find((e) => e.id === extensionId);
		if (!ext) return c.json({ error: "Extension not found" }, 404);
		const registry = loader.getRegistry();
		return c.json({
			id: ext.manifest.id,
			name: ext.manifest.name,
			description: ext.manifest.description,
			version: ext.manifest.version,
			kind: ext.manifest.kind,
			source: ext.source,
			path: ext.path,
			active: registry.extensions.has(ext.id),
			manifest: ext.manifest
		});
	});
	authenticated.get("/api/extensions/:id/storage", async (c) => {
		const store = await loadExtensionStore(c.req.param("id"));
		return c.json({ keys: Object.keys(store) });
	});
	authenticated.get("/api/extensions/:id/storage/:key", async (c) => {
		const extensionId = c.req.param("id");
		const key = decodeURIComponent(c.req.param("key"));
		const store = await loadExtensionStore(extensionId);
		if (!(key in store)) return c.json({ error: "Key not found" }, 404);
		return c.json({ value: store[key] });
	});
	authenticated.put("/api/extensions/:id/storage/:key", async (c) => {
		const extensionId = c.req.param("id");
		const key = decodeURIComponent(c.req.param("key"));
		const body = await c.req.json().catch(() => null);
		if (body === null || !("value" in body)) return c.json({ error: "Request body must contain a \"value\" field" }, 400);
		const store = await loadExtensionStore(extensionId);
		store[key] = body.value;
		await saveExtensionStore(extensionId, store);
		return c.json({ ok: true });
	});
	authenticated.delete("/api/extensions/:id/storage/:key", async (c) => {
		const extensionId = c.req.param("id");
		const key = decodeURIComponent(c.req.param("key"));
		const store = await loadExtensionStore(extensionId);
		delete store[key];
		await saveExtensionStore(extensionId, store);
		return c.json({ ok: true });
	});
	authenticated.get("/api/extensions/:id/config", async (c) => {
		const extensionId = c.req.param("id");
		return c.json(await loadExtensionStore(`__config__${extensionId}`));
	});
	authenticated.patch("/api/extensions/:id/config", async (c) => {
		const extensionId = c.req.param("id");
		const patch = await c.req.json().catch(() => null);
		if (!patch || typeof patch !== "object" || Array.isArray(patch)) return c.json({ error: "Request body must be a JSON object" }, 400);
		const namespace = `__config__${extensionId}`;
		const config = await loadExtensionStore(namespace);
		Object.assign(config, patch);
		await saveExtensionStore(namespace, config);
		return c.json({ ok: true });
	});
	authenticated.get("/api/context", (c) => {
		const loader = service.getExtensionLoader();
		const snapshot = buildWhenContextSnapshot(service.currentConfig, loader);
		return c.json(snapshot);
	});
	authenticated.get("/api/marketplace", async (c) => {
		const q = c.req.query("q");
		const category = c.req.query("category");
		try {
			let extensions;
			if (typeof q === "string" && q.trim()) extensions = await searchExtensions(q.trim());
			else if (typeof category === "string" && category.trim()) extensions = await listExtensions(category.trim());
			else extensions = (await fetchRegistry()).extensions;
			return c.json({
				ok: true,
				extensions
			});
		} catch (err) {
			return c.json({
				ok: false,
				extensions: [],
				error: err instanceof Error ? err.message : "marketplace fetch failed"
			}, 500);
		}
	});
	authenticated.get("/api/marketplace/packages/:pkgName", async (c) => {
		const raw = c.req.param("pkgName");
		if (!raw) return c.json({
			ok: false,
			error: "Missing package name"
		}, 400);
		let pkgName;
		try {
			pkgName = decodeURIComponent(raw);
		} catch {
			return c.json({
				ok: false,
				error: "Invalid package name"
			}, 400);
		}
		try {
			const payload = await service.marketplace.fetchExtensionPackageDetail(pkgName);
			return c.json({
				ok: true,
				payload
			});
		} catch (err) {
			return c.json({
				ok: false,
				error: err instanceof Error ? err.message : "Marketplace request failed"
			}, 502);
		}
	});
	authenticated.post("/api/marketplace/install", strictRateLimitMiddleware, async (c) => {
		let body;
		try {
			body = await c.req.json();
		} catch {
			return c.json({
				ok: false,
				error: "Invalid JSON"
			}, 400);
		}
		const name = typeof body.name === "string" ? body.name.trim() : "";
		const version = typeof body.version === "string" ? body.version.trim() : void 0;
		const overwrite = body.overwrite === true || body.overwrite === "true" || body.overwrite === "1";
		if (!name) return c.json({
			ok: false,
			error: "Expected { name: string, version?: string, overwrite?: boolean }"
		}, 400);
		try {
			const payload = await service.marketplace.installExtension({
				name,
				version,
				overwrite
			});
			return c.json({
				ok: true,
				payload
			});
		} catch (err) {
			return c.json({
				ok: false,
				error: err instanceof Error ? err.message : "Install failed"
			}, 400);
		}
	});
	authenticated.post("/api/marketplace/uninstall", strictRateLimitMiddleware, async (c) => {
		let body;
		try {
			body = await c.req.json();
		} catch {
			return c.json({
				ok: false,
				error: "Invalid JSON"
			}, 400);
		}
		const extensionId = typeof body.extensionId === "string" ? body.extensionId.trim() : "";
		if (!extensionId) return c.json({
			ok: false,
			error: "Expected { extensionId: string }"
		}, 400);
		try {
			const payload = await service.marketplace.uninstallExtension(extensionId);
			return c.json({
				ok: true,
				payload
			});
		} catch (err) {
			return c.json({
				ok: false,
				error: err instanceof Error ? err.message : "Uninstall failed"
			}, 400);
		}
	});
	authenticated.post("/api/registry/reload", async (c) => {
		try {
			await service.reloadConfig();
			const models = getAllModels();
			service.emit("registry.updated", { modelCount: models.length });
			return c.json({
				ok: true,
				payload: {
					message: "Registry reloaded",
					modelCount: models.length
				}
			});
		} catch (err) {
			return c.json({ error: err instanceof Error ? err.message : "Failed to reload registry" }, 500);
		}
	});
}
//#endregion
export { registerAuthRegistryExtensionsRoutes, registerPublicExtensionAssetRoutes };

//# sourceMappingURL=auth-registry-extensions.js.map