import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
import { loadConfig, saveConfig } from "../../config/loader.js";
import { getModelRegistry } from "../../providers/model-registry.js";
import { init_providers } from "../../providers/index.js";
import { disposeAllSessionMcpRuntimes } from "../../agent/mcp/bundle-mcp-runtime.js";
import "../../agent/mcp/bundle-mcp-tools.js";
import { ConfigHotReloader } from "../../config/reload.js";
import "../../config/index.js";
import { computeBundledExtensionExtensionsPatch } from "../../extensions/bundled-extension-activation.js";
import { sanitizeTunnelConfig } from "../../tunnel/tunnel-config.js";
//#region src/gateway/service/config-coordinator.ts
init_providers();
init_logger();
const log = createLogger("GatewayConfigCoordinator");
var GatewayConfigCoordinator = class {
	opts;
	configReloader = null;
	channelReloadFlushPromise = null;
	channelReloadPending = false;
	constructor(opts) {
		this.opts = opts;
	}
	/** Start the fs.watch-based reloader (idempotent — only starts once). */
	startHotReloader() {
		if (this.configReloader) return;
		this.configReloader = new ConfigHotReloader(this.opts.configPath, this.opts.getConfig(), {
			onModelsReload: (newConfig) => this.handleModelsReload(newConfig),
			onAgentDefaultsReload: (newConfig) => this.handleAgentDefaultsReload(newConfig),
			onChannelsReload: (newConfig) => this.handleChannelsReload(newConfig),
			onCronReload: (newConfig) => this.handleCronReload(newConfig),
			onHeartbeatReload: (newConfig) => this.handleHeartbeatReload(newConfig),
			onToolsReload: (newConfig) => this.handleToolsReload(newConfig),
			onMcpReload: (newConfig) => this.handleMcpReload(newConfig),
			onExtensionsReload: async (newConfig, changedPaths) => {
				await this.handleExtensionsReload(newConfig, changedPaths);
			},
			onFullRestart: (newConfig) => {
				log.warn({
					requiresProcessRestart: true,
					hint: "Restart the gateway process (hot reload cannot apply this change)."
				}, "Config reload: full gateway restart required — see prior \"restartPaths\" info log");
				this.opts.setConfig(newConfig);
				this.opts.emit("config.reload", {
					section: "full",
					requiresRestart: true
				});
			}
		}, {
			debounceMs: 300,
			enabled: this.opts.enableHotReload
		});
		this.configReloader.start();
	}
	async stopHotReloader() {
		if (this.configReloader) {
			await this.configReloader.stop();
			this.configReloader = null;
		}
	}
	async reloadConfig() {
		if (!this.configReloader) return {
			reloaded: false,
			error: "Config reloader not initialized"
		};
		const result = await this.configReloader.triggerReload();
		return {
			reloaded: result.success,
			error: result.error
		};
	}
	async saveConfig(config) {
		try {
			await this.writeConfigAndReloadFromDisk(config);
			this.scheduleChannelPluginsAfterPersist();
			return { saved: true };
		} catch (err) {
			const error = err instanceof Error ? err.message : String(err);
			log.error({ error }, "Failed to save config");
			return {
				saved: false,
				error
			};
		}
	}
	/** Merge partial updates into `currentConfig` and persist. */
	async updateConfig(updates) {
		try {
			log.debug("Updating configuration...");
			const merged = {
				...this.opts.getConfig(),
				...updates
			};
			this.opts.setConfig(merged);
			await this.writeConfigAndReloadFromDisk(merged);
			this.scheduleChannelPluginsAfterPersist();
			log.debug("Configuration updated successfully");
			return { updated: true };
		} catch (err) {
			const error = err instanceof Error ? err.message : String(err);
			log.error({ error }, "Failed to update config");
			return {
				updated: false,
				error
			};
		}
	}
	/**
	* App store (phase 1): persist `extensions.enabled` / `extensions.disabled`
	* for a bundled extension. Marketplace-only extensions hot-load on enable;
	* disable still needs a gateway restart to unload.
	*/
	async setBundledExtensionActivationTarget(extensionId, wanted) {
		const loader = this.opts.getExtensionLoader();
		if (!loader) return {
			ok: false,
			error: "Extension loader unavailable",
			requiresGatewayRestart: false
		};
		const id = extensionId.trim();
		if (!id) return {
			ok: false,
			error: "Invalid extension id",
			requiresGatewayRestart: false
		};
		const patch = computeBundledExtensionExtensionsPatch(loader, this.opts.getConfig(), id, wanted);
		if (patch.ok === false) return {
			ok: false,
			error: patch.error,
			requiresGatewayRestart: false
		};
		const newConfig = {
			...this.opts.getConfig(),
			extensions: patch.extensions
		};
		const saved = await this.saveConfig(newConfig);
		if (!saved.saved) return {
			ok: false,
			error: saved.error ?? "Failed to save config",
			requiresGatewayRestart: false
		};
		loader.setConfig(this.opts.getConfig());
		let requiresGatewayRestart = true;
		if (wanted) try {
			loader.invalidateManifestCache();
			await loader.loadByActivationPlan();
			requiresGatewayRestart = false;
		} catch (err) {
			const em = err instanceof Error ? err.message : String(err);
			log.warn({
				err,
				extensionId: id,
				errorMessage: em
			}, `Extension hot-load after bundled activation failed: ${em}`);
			requiresGatewayRestart = true;
		}
		this.opts.emit("config.reload", {
			section: "extensions",
			source: "bundled-activation"
		});
		return {
			ok: true,
			requiresGatewayRestart
		};
	}
	async afterWeixinCredentialsPersisted() {
		const next = loadConfig(this.opts.configPath);
		this.opts.setConfig(next);
		this.opts.getAgentService().applyAgentDefaultsFromConfig(next);
		this.configReloader?.syncCurrentConfig(next);
		await this.handleChannelsReload(next);
		const { weixinPlugin } = await import("../../channels/weixin/index.js");
		await weixinPlugin.reloadMonitorsWithConfig(this.opts.getConfig(), this.opts.bus);
		log.info("Weixin monitors restarted after credential login");
	}
	async afterFeishuCredentialsPersisted() {
		const next = loadConfig(this.opts.configPath);
		this.opts.setConfig(next);
		this.opts.getAgentService().applyAgentDefaultsFromConfig(next);
		this.configReloader?.syncCurrentConfig(next);
		await this.handleChannelsReload(next);
		log.info("Feishu config applied after QR setup");
	}
	/**
	* Apply `latest.channels` to every registered channel plugin (Telegram,
	* Weixin, extensions). Single runtime path for: file watcher hot reload, API
	* saves, and Weixin QR follow-up.
	*/
	async handleChannelsReload(newConfig) {
		log.debug("Reloading channels config...");
		this.opts.setConfig(newConfig);
		await this.opts.getChannelManager().updateConfig(newConfig);
		this.opts.emit("config.reload", { section: "channels" });
		this.opts.emit("channels.status", { channels: this.opts.getChannelsStatus() });
		log.debug("Channels config reloaded");
	}
	/**
	* Apply `gateway.heartbeat` from current config after PATCH /api/config (and
	* when hot reload is off). File watcher uses `handleHeartbeatReload` with
	* the same effect when paths match.
	*/
	reloadHeartbeatFromCurrentConfig() {
		this.handleHeartbeatReload(this.opts.getConfig());
	}
	handleModelsReload(newConfig) {
		log.debug("Reloading models config...");
		this.opts.setConfig(newConfig);
		getModelRegistry().refresh();
		this.opts.emit("config.reload", { section: "models" });
		log.debug("Models config reloaded");
	}
	handleAgentDefaultsReload(newConfig) {
		log.debug("Reloading agent defaults...");
		this.opts.setConfig(newConfig);
		this.opts.getAgentService().applyAgentDefaultsFromConfig(newConfig);
		this.opts.reconcileBrowserExtensionServer();
		this.opts.emit("config.reload", { section: "agents" });
		log.debug("Agent defaults reloaded");
	}
	/**
	* Coalesces rapid saves so Telegram/Weixin do not stop/start repeatedly.
	* The persist path schedules the channel apply; the same coalescer absorbs
	* follow-up saves until the first flush settles.
	*/
	scheduleChannelPluginsAfterPersist() {
		this.channelReloadPending = true;
		if (this.channelReloadFlushPromise) return;
		this.channelReloadFlushPromise = (async () => {
			try {
				while (this.channelReloadPending) {
					this.channelReloadPending = false;
					await this.handleChannelsReload(this.opts.getConfig());
				}
			} catch (err) {
				const em = err instanceof Error ? err.message : String(err);
				log.error({
					err,
					errorMessage: em
				}, `Channel reload after persist failed: ${em}`);
			} finally {
				this.channelReloadFlushPromise = null;
				if (this.channelReloadPending) this.scheduleChannelPluginsAfterPersist();
			}
		})();
	}
	handleCronReload(newConfig) {
		log.debug("Reloading cron config...");
		this.opts.setConfig(newConfig);
		this.opts.getCronService().updateConfig(newConfig);
		this.opts.emit("config.reload", { section: "cron" });
		log.debug("Cron config reloaded");
	}
	handleHeartbeatReload(newConfig) {
		log.debug("Reloading heartbeat config...");
		this.opts.setConfig(newConfig);
		this.opts.getHeartbeatService()?.updateConfig(newConfig);
		this.opts.emit("config.reload", { section: "heartbeat" });
		log.debug("Heartbeat config reloaded");
	}
	handleToolsReload(newConfig) {
		log.debug("Reloading tools config...");
		this.opts.setConfig(newConfig);
		this.opts.emit("config.reload", { section: "tools" });
		log.debug("Tools config reloaded");
	}
	handleMcpReload(newConfig) {
		log.debug("Reloading MCP config...");
		this.opts.setConfig(newConfig);
		disposeAllSessionMcpRuntimes().catch((err) => {
			log.warn({ err }, "MCP runtime dispose on config reload failed");
		});
		this.opts.emit("config.reload", { section: "mcp" });
		log.debug("MCP config reloaded");
	}
	/** Dispatch config hot reload to extensions that registered `registerReload`. */
	async handleExtensionsReload(newConfig, changedPaths) {
		this.opts.setConfig(newConfig);
		const loader = this.opts.getExtensionLoader();
		loader?.setConfig(newConfig);
		if (!loader) {
			this.opts.emit("config.reload", {
				section: "extensions",
				source: "extension-reload",
				changedPaths
			});
			return;
		}
		const matchingRegs = loader.getRegistry().getMatchingReloadRegistrations(changedPaths);
		if (matchingRegs.length === 0) {
			log.debug({ changedPaths }, "No extension reload handlers matched");
			this.opts.emit("config.reload", {
				section: "extensions",
				source: "extension-reload",
				changedPaths
			});
			return;
		}
		for (const reg of matchingRegs) {
			const relevantPaths = changedPaths.filter((p) => reg.configPrefixes.length === 0 || reg.configPrefixes.some((prefix) => p === prefix || p.startsWith(`${prefix}.`)));
			log.info({
				extensionId: reg.extensionId,
				relevantPaths
			}, "Calling extension reload handler");
			try {
				const result = await reg.handler(newConfig, relevantPaths);
				if (result.success) log.info({ extensionId: reg.extensionId }, "Extension reload succeeded");
				else log.warn({
					extensionId: reg.extensionId,
					error: result.error
				}, `Extension reload reported failure: ${result.error ?? "unknown"}`);
			} catch (err) {
				const errorMessage = err instanceof Error ? err.message : String(err);
				log.error({
					err,
					extensionId: reg.extensionId,
					errorMessage
				}, `Extension reload handler threw: ${errorMessage}`);
			}
		}
		this.opts.emit("config.reload", {
			section: "extensions",
			source: "extension-reload",
			changedPaths
		});
	}
	/**
	* Persist and replace `currentConfig` with the validated file contents so
	* runtime matches disk (PATCH merge objects can drift from Zod-normalized
	* output).
	*/
	async writeConfigAndReloadFromDisk(configToWrite) {
		await saveConfig(configToWrite, this.opts.configPath);
		const reloaded = loadConfig(this.opts.configPath);
		this.opts.setConfig(reloaded);
		if (sanitizeTunnelConfig(reloaded)) await saveConfig(reloaded, this.opts.configPath);
		this.opts.getAgentService().applyAgentDefaultsFromConfig(reloaded);
		await this.opts.reconcileBrowserExtensionServer();
		await this.opts.getAgentService().reconcileDreamingNow().catch((err) => {
			const em = err instanceof Error ? err.message : String(err);
			log.warn({
				err,
				errorMessage: em
			}, `Dreaming cron reconcile after save failed: ${em}`);
		});
		this.configReloader?.syncCurrentConfig(reloaded);
	}
};
//#endregion
export { GatewayConfigCoordinator };

//# sourceMappingURL=config-coordinator.js.map