import { r as theme } from "./theme-CLQ2HbnQ.js";
import { i as isPathInside } from "./path-BlG8lhgR.js";
import { a as tracePluginLifecyclePhaseAsync } from "./discovery-CmcwOlP_.js";
import { g as shortenHomePath, p as resolveUserPath } from "./utils-CCC-BEJH.js";
import { n as defaultRuntime } from "./runtime-pp2OBTec.js";
import "./path-guards-CBe_wA_B.js";
import { t as loadInstalledPluginIndexInstallRecords } from "./installed-plugin-index-record-reader-BPrT_Xp2.js";
import { t as buildNpmResolutionFields } from "./install-source-utils-DxQ0QLl9.js";
import { n as recordPluginInstallInRecords, o as withoutPluginInstallRecords } from "./installed-plugin-index-records-BZTYWCfi.js";
import { t as enablePluginInConfig } from "./enable-DWEH_UDj.js";
import { i as replaceConfigFile } from "./config-R-DNt3t0.js";
import { n as applyPluginUninstallDirectoryRemoval, o as planPluginUninstall } from "./uninstall-BugWC4H9.js";
import { s as buildPluginSnapshotReport } from "./status-DUHhr6Mx.js";
import { i as enableInternalHookEntries, o as logHookPackRestartHint, s as logSlotWarnings, t as applySlotSelectionForPlugin } from "./plugins-command-helpers-RafPxGnc.js";
import { t as recordHookInstall } from "./installs-DC4E2xLJ.js";
import { r as commitPluginInstallRecordsWithConfig } from "./plugins-install-record-commit-C1D41VKm.js";
import { t as refreshPluginRegistryAfterConfigMutation } from "./plugins-registry-refresh-D3Tv6eGr.js";
//#region src/cli/npm-resolution.ts
/** Choose the install-record spec for an npm package, optionally pinning to the resolved version. */
function resolvePinnedNpmSpec(params) {
	const recordSpec = params.pin && params.resolvedSpec ? params.resolvedSpec : params.rawSpec;
	if (!params.pin) return { recordSpec };
	if (!params.resolvedSpec) return {
		recordSpec,
		pinWarning: "Could not resolve exact npm version for --pin; storing original npm spec."
	};
	return {
		recordSpec,
		pinNotice: `Pinned npm install record to ${params.resolvedSpec}.`
	};
}
/** Build the npm section of a plugin install record. */
function buildNpmInstallRecordFields(params) {
	return {
		source: "npm",
		spec: params.spec,
		installPath: params.installPath,
		version: params.version,
		...buildNpmResolutionFields(params.resolution)
	};
}
/** Resolve and log npm pinning decisions before constructing the persisted install record. */
function resolvePinnedNpmInstallRecord(params) {
	const pinInfo = resolvePinnedNpmSpec({
		rawSpec: params.rawSpec,
		pin: params.pin,
		resolvedSpec: params.resolution?.resolvedSpec
	});
	logPinnedNpmSpecMessages(pinInfo, params.log, params.warn);
	return buildNpmInstallRecordFields({
		spec: pinInfo.recordSpec,
		installPath: params.installPath,
		version: params.version,
		resolution: params.resolution
	});
}
/** CLI adapter for npm install-record pinning with styled warning output. */
function resolvePinnedNpmInstallRecordForCli(rawSpec, pin, installPath, version, resolution, log, warnFormat) {
	return resolvePinnedNpmInstallRecord({
		rawSpec,
		pin,
		installPath,
		version,
		resolution,
		log,
		warn: (message) => log(warnFormat(message))
	});
}
/** Emit any user-facing notice or warning from npm pin resolution. */
function logPinnedNpmSpecMessages(pinInfo, log, logWarn) {
	if (pinInfo.pinWarning) logWarn(pinInfo.pinWarning);
	if (pinInfo.pinNotice) log(pinInfo.pinNotice);
}
//#endregion
//#region src/cli/plugins-install-persist.ts
function addInstalledPluginToAllowlist(cfg, pluginId) {
	const allow = cfg.plugins?.allow;
	if (!Array.isArray(allow) || allow.length === 0 || allow.includes(pluginId)) return cfg;
	return {
		...cfg,
		plugins: {
			...cfg.plugins,
			allow: [...allow, pluginId].toSorted()
		}
	};
}
function removeInstalledPluginFromDenylist(cfg, pluginId) {
	const deny = cfg.plugins?.deny;
	if (!Array.isArray(deny) || !deny.includes(pluginId)) return cfg;
	const nextDeny = deny.filter((id) => id !== pluginId);
	const plugins = {
		...cfg.plugins,
		...nextDeny.length > 0 ? { deny: nextDeny } : {}
	};
	if (nextDeny.length === 0) delete plugins.deny;
	return {
		...cfg,
		plugins
	};
}
function sourceMatchesInstalledPath(params) {
	const activeSource = resolveUserPath(params.activeSource, params.env);
	const installedSource = resolveUserPath(params.installedSource, params.env);
	return activeSource === installedSource || isPathInside(installedSource, activeSource);
}
function logShadowedNpmInstallWarning(params) {
	if (params.install.source !== "npm") return;
	const installedSource = params.install.installPath ?? params.install.sourcePath;
	if (!installedSource) return;
	const active = buildPluginSnapshotReport({
		config: params.config,
		effectiveOnly: true,
		onlyPluginIds: [params.pluginId]
	}).plugins.find((plugin) => plugin.id === params.pluginId);
	if (!active || active.origin !== "config" || sourceMatchesInstalledPath({
		activeSource: active.source,
		installedSource
	})) return;
	params.runtime.log(theme.warn([
		`Warning: installed plugin "${params.pluginId}" is not the active source because a config-selected plugin with the same id is currently selected:`,
		`  active config source: ${shortenHomePath(active.source)}`,
		`  installed npm source: ${shortenHomePath(installedSource)}`,
		"Run `openclaw plugins doctor` for repair options."
	].join("\n")));
}
function resolveComparableInstallPath(install) {
	return install.installPath ?? install.sourcePath;
}
function shouldPreserveReplacedInstallPath(params) {
	const removalTarget = resolveUserPath(params.removalTarget);
	const nextInstallPath = resolveUserPath(params.nextInstallPath);
	return isPathInside(removalTarget, nextInstallPath) || isPathInside(nextInstallPath, removalTarget);
}
function resolveReplacedManagedInstallRemoval(params) {
	if (!params.previousInstall) return null;
	const previousInstallPath = resolveComparableInstallPath(params.previousInstall);
	const nextInstallPath = resolveComparableInstallPath(params.nextInstall);
	if (!previousInstallPath || !nextInstallPath) return null;
	if (shouldPreserveReplacedInstallPath({
		removalTarget: previousInstallPath,
		nextInstallPath
	})) return null;
	const plan = planPluginUninstall({
		config: { plugins: { installs: { [params.pluginId]: params.previousInstall } } },
		pluginId: params.pluginId,
		deleteFiles: true
	});
	if (!plan.ok || !plan.directoryRemoval) return null;
	if (shouldPreserveReplacedInstallPath({
		removalTarget: plan.directoryRemoval.target,
		nextInstallPath
	})) return null;
	return plan.directoryRemoval;
}
async function persistPluginInstall(params) {
	const runtime = params.runtime ?? defaultRuntime;
	const installConfig = params.enable === false ? params.snapshot.config : removeInstalledPluginFromDenylist(addInstalledPluginToAllowlist(params.snapshot.config, params.pluginId), params.pluginId);
	let next = params.enable === false ? installConfig : enablePluginInConfig(installConfig, params.pluginId, { updateChannelConfig: false }).config;
	const installRecords = await tracePluginLifecyclePhaseAsync("install records load", () => loadInstalledPluginIndexInstallRecords(), { command: "install" });
	const replacedInstallRemoval = resolveReplacedManagedInstallRemoval({
		pluginId: params.pluginId,
		previousInstall: installRecords[params.pluginId],
		nextInstall: params.install
	});
	const nextInstallRecords = recordPluginInstallInRecords(installRecords, {
		pluginId: params.pluginId,
		...params.install
	});
	const slotResult = params.enable === false ? {
		config: next,
		warnings: []
	} : await tracePluginLifecyclePhaseAsync("slot selection", async () => applySlotSelectionForPlugin(next, params.pluginId), {
		command: "install",
		pluginId: params.pluginId
	});
	next = withoutPluginInstallRecords(slotResult.config);
	await tracePluginLifecyclePhaseAsync("config mutation", () => commitPluginInstallRecordsWithConfig({
		previousInstallRecords: installRecords,
		nextInstallRecords,
		nextConfig: next,
		baseHash: params.snapshot.baseHash,
		writeOptions: { afterWrite: {
			mode: "restart",
			reason: "plugin source changed"
		} }
	}), { command: "install" });
	if (replacedInstallRemoval) {
		const removalResult = await tracePluginLifecyclePhaseAsync("replaced install cleanup", () => applyPluginUninstallDirectoryRemoval(replacedInstallRemoval), {
			command: "install",
			pluginId: params.pluginId
		});
		for (const warning of removalResult.warnings) runtime.log(theme.warn(warning));
		if (removalResult.directoryRemoved) runtime.log(theme.muted(`Removed previous plugin install directory: ${shortenHomePath(replacedInstallRemoval.target)}`));
	}
	await refreshPluginRegistryAfterConfigMutation({
		config: next,
		reason: "source-changed",
		installRecords: nextInstallRecords,
		traceCommand: "install",
		logger: { warn: (message) => runtime.log(theme.warn(message)) }
	});
	logSlotWarnings(slotResult.warnings, runtime);
	if (params.warningMessage) runtime.log(theme.warn(params.warningMessage));
	runtime.log(params.successMessage ?? `Installed plugin: ${params.pluginId}`);
	logShadowedNpmInstallWarning({
		config: next,
		pluginId: params.pluginId,
		install: params.install,
		runtime
	});
	runtime.log("Restart the gateway to load plugins.");
	return next;
}
async function persistHookPackInstall(params) {
	const runtime = params.runtime ?? defaultRuntime;
	let next = enableInternalHookEntries(params.snapshot.config, params.hooks);
	next = recordHookInstall(next, {
		hookId: params.hookPackId,
		hooks: params.hooks,
		...params.install
	});
	await replaceConfigFile({
		nextConfig: next,
		baseHash: params.snapshot.baseHash
	});
	runtime.log(params.successMessage ?? `Installed hook pack: ${params.hookPackId}`);
	logHookPackRestartHint(runtime);
	return next;
}
//#endregion
export { resolvePinnedNpmInstallRecordForCli as i, persistPluginInstall as n, buildNpmInstallRecordFields as r, persistHookPackInstall as t };
