import { PACKAGE_VERSION, init_package_version } from "../../package-version.js";
import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
import { init_paths, resolveConfigPath, resolveExtensionsDir } from "../../config/paths.js";
import { init_loader, loadConfig } from "../../config/loader.js";
import { normalizeExtensionManifest } from "../../extensions/normalize-manifest.js";
import { checkEngineCompatibility } from "../../extensions/engine-check.js";
import { getContextWithOpts } from "../context.js";
import { seedMainAgentProfileMarkdown } from "../../agent/context/workspace-seed.js";
import { GatewayServer } from "../../gateway/server.js";
import { runGatewayLoop } from "../../gateway/run-loop.js";
import "../../gateway/index.js";
import { colors } from "../utils/colors.js";
import { initWorkspace } from "../utils/init-workspace.js";
import { existsSync, mkdirSync, readFileSync, symlinkSync, unlinkSync, watch } from "node:fs";
import { join, resolve } from "node:path";
import { Command } from "commander";
//#region src/cli/commands/extension-dev.ts
init_loader();
init_paths();
init_package_version();
init_logger();
const log = createLogger("ExtensionDev");
const MANIFEST = "xopc.extension.json";
function isRecord(x) {
	return typeof x === "object" && x !== null && !Array.isArray(x);
}
async function ensureGatewayReady(configPath, workspacePath, gatewayPort) {
	const result = await initWorkspace({
		configPath,
		workspacePath,
		gatewayPort
	});
	if (result.configCreated || result.workspaceCreated) {
		console.log("");
		console.log("👋 First-time setup before starting the gateway...");
		console.log("");
		console.log("✅ Setup complete.");
		console.log(`   Config:    ${configPath}`);
		console.log(`   Workspace: ${workspacePath}`);
		console.log("");
		seedMainAgentProfileMarkdown(result.config);
	}
}
function loadAndValidateManifest(extensionDir) {
	const manifestPath = join(extensionDir, MANIFEST);
	if (!existsSync(manifestPath)) {
		console.error(colors.red("error:"), `Missing ${MANIFEST} in ${extensionDir}`);
		return null;
	}
	try {
		const raw = JSON.parse(readFileSync(manifestPath, "utf-8"));
		if (!isRecord(raw)) {
			console.error(colors.red("error:"), "Manifest must be a JSON object");
			return null;
		}
		const manifest = normalizeExtensionManifest(raw);
		if (!manifest.id?.trim()) {
			console.error(colors.red("error:"), "Manifest \"id\" is required");
			return null;
		}
		if (manifest.engines?.xopc) {
			const r = checkEngineCompatibility(PACKAGE_VERSION, manifest.engines.xopc);
			if (r.parseWarning) console.log(colors.yellow("warning:"), r.reason ?? "engines.xopc could not be fully parsed — continuing");
			else if (!r.compatible) console.log(colors.yellow("warning:"), r.reason ?? `engines.xopc may not match xopc ${PACKAGE_VERSION} — continuing`);
		}
		return manifest;
	} catch (e) {
		log.error({ err: e }, "Failed to read manifest");
		console.error(colors.red("error:"), e instanceof Error ? e.message : String(e));
		return null;
	}
}
function setupDevSymlink(extensionDir, extensionsDir, extensionId) {
	mkdirSync(extensionsDir, { recursive: true });
	const symlinkPath = join(extensionsDir, extensionId);
	if (existsSync(symlinkPath)) unlinkSync(symlinkPath);
	symlinkSync(extensionDir, symlinkPath, "dir");
	return symlinkPath;
}
function cleanupSymlink(symlinkPath) {
	if (!symlinkPath) return;
	try {
		if (existsSync(symlinkPath)) unlinkSync(symlinkPath);
	} catch (e) {
		log.warn({
			err: e,
			symlinkPath
		}, "Failed to remove dev symlink");
	}
}
function shouldIgnorePath(relativePath) {
	const parts = relativePath.split(/[/\\]/);
	if (parts.some((p) => p === "node_modules")) return true;
	if (parts.some((p) => p.startsWith("."))) return true;
	return false;
}
function createExtensionDevCommand() {
	return new Command("dev").description("Symlink an extension into the workspace for live development (optional file watch + gateway)").argument("[dir]", "Extension directory (default: current working directory)", ".").option("--port <number>", "Gateway port", "18790").option("--bind <mode>", "Gateway bind mode", "loopback").option("--no-gateway", "Do not start the gateway (symlink only)").option("--no-watch", "Do not watch files for changes").action(async (dir, options) => {
		const extensionDir = resolve(dir || ".");
		const manifest = loadAndValidateManifest(extensionDir);
		if (!manifest) process.exit(1);
		const ctx = getContextWithOpts();
		loadConfig(ctx.configPath);
		const symlinkPath = setupDevSymlink(extensionDir, resolveExtensionsDir(), manifest.id);
		console.log(colors.green("✓"), `Dev symlink: ${symlinkPath} → ${extensionDir}`);
		console.log(colors.cyan("Note:"), "restart the gateway or trigger config hot-reload so the extension reload picks up changes.");
		let debounce = null;
		let watcher = null;
		if (options.watch) try {
			watcher = watch(extensionDir, { recursive: true }, (_event, filename) => {
				const rel = filename ? String(filename) : "";
				if (rel && shouldIgnorePath(rel)) return;
				if (debounce) clearTimeout(debounce);
				debounce = setTimeout(() => {
					const label = rel || "(unknown)";
					if (/(^|[\\/])xopc\.extension\.json$/.test(rel) || rel === MANIFEST) console.log(colors.cyan("[watch]"), `manifest: ${label}`);
					else if (/\.(html?|css|mjs|js|tsx?|jsx|json)$/i.test(label)) if (/^ui[\\/]/.test(rel) || /[\\/]ui[\\/]/.test(label)) console.log(colors.cyan("[watch]"), `ui: ${label}`);
					else console.log(colors.cyan("[watch]"), `source: ${label}`);
					else console.log(colors.cyan("[watch]"), `changed: ${label}`);
				}, 300);
			});
		} catch (e) {
			log.warn({ err: e }, "fs.watch failed; continuing without watch");
		}
		let cleaned = false;
		const cleanup = () => {
			if (cleaned) return;
			cleaned = true;
			if (debounce) clearTimeout(debounce);
			if (watcher) try {
				watcher.close();
			} catch (e) {
				log.warn({ err: e }, "watcher close failed");
			}
			cleanupSymlink(symlinkPath);
		};
		for (const sig of ["SIGINT", "SIGTERM"]) process.on(sig, () => {
			cleanup();
			process.exit(0);
		});
		if (!options.gateway) {
			if (options.watch) console.log(colors.cyan("Watching…"), "Ctrl+C to stop and remove symlink");
			else console.log(colors.cyan("Holding process…"), "Ctrl+C to stop and remove symlink (no file watch)");
			await new Promise(() => {});
			return;
		}
		const port = parseInt(options.port, 10);
		const bindModes = new Set([
			"auto",
			"loopback",
			"lan",
			"tailnet",
			"custom"
		]);
		const bindRaw = options.bind.trim().toLowerCase();
		if (!bindModes.has(bindRaw)) {
			console.error(colors.red("error:"), "Invalid --bind mode");
			cleanup();
			process.exit(1);
		}
		const bind = bindRaw;
		await ensureGatewayReady(ctx.configPath, ctx.workspacePath, port);
		const cfg = loadConfig(ctx.configPath);
		const { resolveGatewayListenPlan } = await import("../../gateway/listen.js");
		const listenPlan = resolveGatewayListenPlan({
			cfg,
			bindOverride: bind
		});
		if (Number.isNaN(port)) {
			console.error(colors.red("error:"), "Invalid --port");
			cleanup();
			process.exit(1);
		}
		console.log("🚀 Starting gateway (extension dev)…");
		console.log(`   Bind: ${bind} (${listenPlan.bindHost})`);
		console.log(`   Port: ${port}`);
		console.log("");
		try {
			await runGatewayLoop({
				configPath: ctx.configPath || resolveConfigPath(),
				port,
				start: async () => {
					const server = new GatewayServer({
						bindHost: listenPlan.bindHost,
						bind: listenPlan.bindMode,
						customBindHost: listenPlan.customBindHost,
						port,
						token: cfg?.gateway?.auth?.token,
						verbose: ctx.isVerbose,
						configPath: ctx.configPath,
						enableHotReload: true
					});
					await server.start();
					const displayHost = listenPlan.bindHost === "0.0.0.0" ? "localhost" : listenPlan.bindHost;
					const token = cfg?.gateway?.auth?.token;
					console.log("✅ Gateway started");
					console.log(`   URL: http://${displayHost}:${port}`);
					if (token) console.log(`   Token: ${String(token).slice(0, 8)}...${String(token).slice(-8)}`);
					console.log("");
					return server;
				}
			});
		} finally {
			cleanup();
		}
	});
}
//#endregion
export { createExtensionDevCommand };

//# sourceMappingURL=extension-dev.js.map