import { $c as installPluginFromNpmSpec, Ma as discoverFasedAgentPlugins, Sd as openBoundaryFileSync, rl as loadPluginManifest, tl as resolvePluginInstallDir } from "./env-Dlz8aQHP.js";
import { x as resolveUserPath } from "./agent-paths-DUHGyU1s.js";
import { n as recordPluginInstall, t as buildNpmResolutionInstallFields } from "./installs-PYgg3AvN.js";
import path from "node:path";
import fs from "node:fs";

//#region src/plugins/bundled-sources.ts
function resolveBundledPluginSources(params) {
	const discovery = discoverFasedAgentPlugins({ workspaceDir: params.workspaceDir });
	const bundled = /* @__PURE__ */ new Map();
	for (const candidate of discovery.candidates) {
		if (candidate.origin !== "bundled") continue;
		const manifest = loadPluginManifest(candidate.rootDir);
		if (!manifest.ok) continue;
		const pluginId = manifest.manifest.id;
		if (bundled.has(pluginId)) continue;
		const npmSpec = candidate.packageManifest?.install?.npmSpec?.trim() || void 0;
		bundled.set(pluginId, {
			pluginId,
			localPath: candidate.rootDir,
			npmSpec
		});
	}
	return bundled;
}
function findBundledPluginByNpmSpec(params) {
	const targetSpec = params.spec.trim();
	if (!targetSpec) return;
	const bundled = resolveBundledPluginSources({ workspaceDir: params.workspaceDir });
	for (const source of bundled.values()) if (source.npmSpec === targetSpec) return source;
}

//#endregion
//#region src/plugins/update.ts
function parseNpmRegistrySpec(spec) {
	const trimmed = spec.trim();
	if (!trimmed || /\s/.test(trimmed) || trimmed.includes("://") || trimmed.includes("#")) return null;
	const selectorAt = trimmed.startsWith("@") ? trimmed.lastIndexOf("@") > trimmed.indexOf("/") ? trimmed.lastIndexOf("@") : -1 : trimmed.lastIndexOf("@");
	const name = selectorAt > 0 ? trimmed.slice(0, selectorAt) : trimmed;
	const selector = selectorAt > 0 ? trimmed.slice(selectorAt + 1) : void 0;
	if (!(name.startsWith("@") ? /^@[a-z0-9][a-z0-9-._~]*\/[a-z0-9][a-z0-9-._~]*$/.test(name) : /^[a-z0-9][a-z0-9-._~]*$/.test(name)) || selector === "") return null;
	return {
		name,
		selector
	};
}
function resolveDefaultNpmBetaTarget(spec) {
	const parsed = parseNpmRegistrySpec(spec);
	if (!parsed) return null;
	if (!parsed.selector || parsed.selector.toLowerCase() === "latest") return { name: parsed.name };
	return null;
}
function resolveNpmUpdateSpecs(params) {
	const recordSpec = params.recordSpec?.trim();
	if (!recordSpec) return {};
	if (params.updateChannel !== "beta") return {
		installSpec: recordSpec,
		recordSpec
	};
	const betaTarget = resolveDefaultNpmBetaTarget(recordSpec);
	if (!betaTarget) return {
		installSpec: recordSpec,
		recordSpec
	};
	return {
		installSpec: `${betaTarget.name}@beta`,
		recordSpec,
		fallbackSpec: recordSpec,
		fallbackLabel: `${betaTarget.name}@beta`
	};
}
function describeBetaNpmFallback(params) {
	const reason = /\b(E404|ETARGET|notarget)\b|No matching version found|dist-tag|tag .*not found|not in this registry/i.test(params.result.error) ? "has no beta npm release" : "failed beta npm update";
	return `Plugin "${params.pluginId}" ${reason} for ${params.betaSpec ?? "the beta npm release"}; falling back to ${params.fallbackSpec}.`;
}
function npmUpdateFailureSpec(params) {
	if (params.usedFallback && params.fallbackSpec) return params.fallbackSpec;
	return params.effectiveSpec ?? params.fallbackSpec ?? "unknown";
}
function formatUpdateError(err) {
	return err instanceof Error ? err.message : String(err);
}
async function readInstalledPackageVersion(dir, opts) {
	const opened = openBoundaryFileSync({
		absolutePath: path.join(dir, "package.json"),
		rootPath: dir,
		boundaryLabel: "installed plugin directory"
	});
	if (!opened.ok) return;
	try {
		const raw = fs.readFileSync(opened.fd, "utf-8");
		const parsed = JSON.parse(raw);
		return typeof parsed.version === "string" ? parsed.version : void 0;
	} catch (err) {
		opts?.logger?.warn?.(`Could not inspect installed package for "${opts.pluginId ?? path.basename(dir)}": package.json is unreadable (${formatUpdateError(err)}). Continuing update with unknown current version.`);
		return;
	} finally {
		fs.closeSync(opened.fd);
	}
}
function pathsEqual(left, right) {
	if (!left || !right) return false;
	return resolveUserPath(left) === resolveUserPath(right);
}
function buildLoadPathHelpers(existing) {
	let paths = [...existing];
	const resolveSet = () => new Set(paths.map((entry) => resolveUserPath(entry)));
	let resolved = resolveSet();
	let changed = false;
	const addPath = (value) => {
		const normalized = resolveUserPath(value);
		if (resolved.has(normalized)) return;
		paths.push(value);
		resolved.add(normalized);
		changed = true;
	};
	const removePath = (value) => {
		const normalized = resolveUserPath(value);
		if (!resolved.has(normalized)) return;
		paths = paths.filter((entry) => resolveUserPath(entry) !== normalized);
		resolved = resolveSet();
		changed = true;
	};
	return {
		addPath,
		removePath,
		get changed() {
			return changed;
		},
		get paths() {
			return paths;
		}
	};
}
function createPluginUpdateIntegrityDriftHandler(params) {
	return async (drift) => {
		const payload = {
			pluginId: params.pluginId,
			spec: drift.spec,
			expectedIntegrity: drift.expectedIntegrity,
			actualIntegrity: drift.actualIntegrity,
			resolvedSpec: drift.resolution.resolvedSpec,
			resolvedVersion: drift.resolution.version,
			dryRun: params.dryRun
		};
		if (params.onIntegrityDrift) return await params.onIntegrityDrift(payload);
		params.logger.warn?.(`Integrity drift for "${params.pluginId}" (${payload.resolvedSpec ?? payload.spec}): expected ${payload.expectedIntegrity}, got ${payload.actualIntegrity}`);
		return true;
	};
}
async function updateNpmInstalledPlugins(params) {
	const logger = params.logger ?? {};
	const installs = params.config.plugins?.installs ?? {};
	const targets = params.pluginIds?.length ? params.pluginIds : Object.keys(installs);
	const outcomes = [];
	let next = params.config;
	let changed = false;
	for (const pluginId of targets) {
		const warnings = [];
		const pluginLogger = {
			info: (message) => logger.info?.(message),
			warn: (message) => {
				warnings.push(message);
				logger.warn?.(message);
			},
			error: (message) => logger.error?.(message)
		};
		if (params.skipIds?.has(pluginId)) {
			outcomes.push({
				pluginId,
				status: "skipped",
				message: `Skipping "${pluginId}" (already updated).`
			});
			continue;
		}
		const record = installs[pluginId];
		if (!record) {
			outcomes.push({
				pluginId,
				status: "skipped",
				message: `No install record for "${pluginId}".`
			});
			continue;
		}
		if (record.source !== "npm") {
			outcomes.push({
				pluginId,
				status: "skipped",
				message: `Skipping "${pluginId}" (source: ${record.source}).`
			});
			continue;
		}
		const npmSpecs = resolveNpmUpdateSpecs({
			recordSpec: record.spec,
			updateChannel: params.updateChannel
		});
		const effectiveSpec = npmSpecs.installSpec;
		const recordSpec = npmSpecs.recordSpec;
		const fallbackSpec = npmSpecs.fallbackSpec;
		const expectedIntegrity = effectiveSpec === record.spec ? record.integrity : void 0;
		const fallbackExpectedIntegrity = fallbackSpec === record.spec ? record.integrity : void 0;
		if (!effectiveSpec || !recordSpec) {
			outcomes.push({
				pluginId,
				status: "skipped",
				message: `Skipping "${pluginId}" (missing npm spec).`
			});
			continue;
		}
		let installPath;
		try {
			installPath = record.installPath ?? resolvePluginInstallDir(pluginId);
		} catch (err) {
			outcomes.push({
				pluginId,
				status: "error",
				message: `Invalid install path for "${pluginId}": ${String(err)}`
			});
			continue;
		}
		const currentVersion = await readInstalledPackageVersion(installPath, {
			pluginId,
			logger: pluginLogger
		});
		if (params.dryRun) {
			let probe;
			try {
				probe = await installPluginFromNpmSpec({
					spec: effectiveSpec,
					mode: "update",
					dryRun: true,
					expectedPluginId: pluginId,
					expectedIntegrity,
					onIntegrityDrift: createPluginUpdateIntegrityDriftHandler({
						pluginId,
						dryRun: true,
						logger: pluginLogger,
						onIntegrityDrift: params.onIntegrityDrift
					}),
					logger: pluginLogger
				});
			} catch (err) {
				outcomes.push({
					pluginId,
					status: "error",
					message: `Failed to check ${pluginId}: ${String(err)}`
				});
				continue;
			}
			let usedNpmFallback = false;
			if (!probe.ok && fallbackSpec) {
				pluginLogger.warn?.(describeBetaNpmFallback({
					pluginId,
					betaSpec: npmSpecs.fallbackLabel ?? effectiveSpec,
					fallbackSpec,
					result: probe
				}));
				usedNpmFallback = true;
				probe = await installPluginFromNpmSpec({
					spec: fallbackSpec,
					mode: "update",
					dryRun: true,
					expectedPluginId: pluginId,
					expectedIntegrity: fallbackExpectedIntegrity,
					onIntegrityDrift: createPluginUpdateIntegrityDriftHandler({
						pluginId,
						dryRun: true,
						logger: pluginLogger,
						onIntegrityDrift: params.onIntegrityDrift
					}),
					logger: pluginLogger
				});
			}
			if (!probe.ok) {
				outcomes.push({
					pluginId,
					status: "error",
					message: `Failed to check ${pluginId}: npm install failed for ${npmUpdateFailureSpec({
						effectiveSpec,
						fallbackSpec,
						usedFallback: usedNpmFallback
					})}: ${probe.error}`
				});
				continue;
			}
			const nextVersion = probe.version ?? "unknown";
			const currentLabel = currentVersion ?? "unknown";
			if (currentVersion && probe.version && currentVersion === probe.version) outcomes.push({
				pluginId,
				status: "unchanged",
				currentVersion: currentVersion ?? void 0,
				nextVersion: probe.version ?? void 0,
				resolvedSpec: probe.npmResolution?.resolvedSpec,
				integrity: probe.npmResolution?.integrity,
				warnings,
				packageReview: probe.review,
				message: `${pluginId} is up to date (${currentLabel}).`
			});
			else outcomes.push({
				pluginId,
				status: "updated",
				currentVersion: currentVersion ?? void 0,
				nextVersion: probe.version ?? void 0,
				resolvedSpec: probe.npmResolution?.resolvedSpec,
				integrity: probe.npmResolution?.integrity,
				warnings,
				packageReview: probe.review,
				message: `Would update ${pluginId}: ${currentLabel} -> ${nextVersion}.`
			});
			continue;
		}
		let result;
		try {
			result = await installPluginFromNpmSpec({
				spec: effectiveSpec,
				mode: "update",
				expectedPluginId: pluginId,
				expectedIntegrity,
				onIntegrityDrift: createPluginUpdateIntegrityDriftHandler({
					pluginId,
					dryRun: false,
					logger: pluginLogger,
					onIntegrityDrift: params.onIntegrityDrift
				}),
				logger: pluginLogger
			});
		} catch (err) {
			outcomes.push({
				pluginId,
				status: "error",
				message: `Failed to update ${pluginId}: ${String(err)}`
			});
			continue;
		}
		let usedNpmFallback = false;
		if (!result.ok && fallbackSpec) {
			pluginLogger.warn?.(describeBetaNpmFallback({
				pluginId,
				betaSpec: npmSpecs.fallbackLabel ?? effectiveSpec,
				fallbackSpec,
				result
			}));
			usedNpmFallback = true;
			result = await installPluginFromNpmSpec({
				spec: fallbackSpec,
				mode: "update",
				expectedPluginId: pluginId,
				expectedIntegrity: fallbackExpectedIntegrity,
				onIntegrityDrift: createPluginUpdateIntegrityDriftHandler({
					pluginId,
					dryRun: false,
					logger: pluginLogger,
					onIntegrityDrift: params.onIntegrityDrift
				}),
				logger: pluginLogger
			});
		}
		if (!result.ok) {
			outcomes.push({
				pluginId,
				status: "error",
				message: `Failed to update ${pluginId}: npm install failed for ${npmUpdateFailureSpec({
					effectiveSpec,
					fallbackSpec,
					usedFallback: usedNpmFallback
				})}: ${result.error}`
			});
			continue;
		}
		const nextVersion = result.version ?? await readInstalledPackageVersion(result.targetDir, {
			pluginId,
			logger: pluginLogger
		});
		next = recordPluginInstall(next, {
			pluginId,
			source: "npm",
			spec: recordSpec,
			installPath: result.targetDir,
			version: nextVersion,
			...buildNpmResolutionInstallFields(result.npmResolution)
		});
		changed = true;
		const currentLabel = currentVersion ?? "unknown";
		const nextLabel = nextVersion ?? "unknown";
		if (currentVersion && nextVersion && currentVersion === nextVersion) outcomes.push({
			pluginId,
			status: "unchanged",
			currentVersion: currentVersion ?? void 0,
			nextVersion: nextVersion ?? void 0,
			resolvedSpec: result.npmResolution?.resolvedSpec,
			integrity: result.npmResolution?.integrity,
			warnings,
			packageReview: result.review,
			message: `${pluginId} already at ${currentLabel}.`
		});
		else outcomes.push({
			pluginId,
			status: "updated",
			currentVersion: currentVersion ?? void 0,
			nextVersion: nextVersion ?? void 0,
			resolvedSpec: result.npmResolution?.resolvedSpec,
			integrity: result.npmResolution?.integrity,
			warnings,
			packageReview: result.review,
			message: `Updated ${pluginId}: ${currentLabel} -> ${nextLabel}.`
		});
	}
	return {
		config: next,
		changed,
		outcomes
	};
}
async function syncPluginsForUpdateChannel(params) {
	const summary = {
		switchedToBundled: [],
		switchedToNpm: [],
		warnings: [],
		errors: []
	};
	const bundled = resolveBundledPluginSources({ workspaceDir: params.workspaceDir });
	if (bundled.size === 0) return {
		config: params.config,
		changed: false,
		summary
	};
	let next = params.config;
	const loadHelpers = buildLoadPathHelpers(next.plugins?.load?.paths ?? []);
	const installs = next.plugins?.installs ?? {};
	let changed = false;
	if (params.channel === "dev") for (const [pluginId, record] of Object.entries(installs)) {
		const bundledInfo = bundled.get(pluginId);
		if (!bundledInfo) continue;
		loadHelpers.addPath(bundledInfo.localPath);
		if (record.source === "path" && pathsEqual(record.sourcePath, bundledInfo.localPath)) continue;
		next = recordPluginInstall(next, {
			pluginId,
			source: "path",
			sourcePath: bundledInfo.localPath,
			installPath: bundledInfo.localPath,
			spec: record.spec ?? bundledInfo.npmSpec,
			version: record.version
		});
		summary.switchedToBundled.push(pluginId);
		changed = true;
	}
	else for (const [pluginId, record] of Object.entries(installs)) {
		const bundledInfo = bundled.get(pluginId);
		if (!bundledInfo) continue;
		if (record.source === "npm") {
			loadHelpers.removePath(bundledInfo.localPath);
			continue;
		}
		if (record.source !== "path") continue;
		if (!pathsEqual(record.sourcePath, bundledInfo.localPath)) continue;
		const spec = record.spec ?? bundledInfo.npmSpec;
		if (!spec) {
			summary.warnings.push(`Missing npm spec for ${pluginId}; keeping local path.`);
			continue;
		}
		let result;
		try {
			result = await installPluginFromNpmSpec({
				spec,
				mode: "update",
				expectedPluginId: pluginId,
				logger: params.logger
			});
		} catch (err) {
			summary.errors.push(`Failed to install ${pluginId}: ${String(err)}`);
			continue;
		}
		if (!result.ok) {
			summary.errors.push(`Failed to install ${pluginId}: ${result.error}`);
			continue;
		}
		next = recordPluginInstall(next, {
			pluginId,
			source: "npm",
			spec,
			installPath: result.targetDir,
			version: result.version,
			...buildNpmResolutionInstallFields(result.npmResolution),
			sourcePath: void 0
		});
		summary.switchedToNpm.push(pluginId);
		changed = true;
		loadHelpers.removePath(bundledInfo.localPath);
	}
	if (loadHelpers.changed) {
		next = {
			...next,
			plugins: {
				...next.plugins,
				load: {
					...next.plugins?.load,
					paths: loadHelpers.paths
				}
			}
		};
		changed = true;
	}
	return {
		config: next,
		changed,
		summary
	};
}

//#endregion
export { resolveBundledPluginSources as i, updateNpmInstalledPlugins as n, findBundledPluginByNpmSpec as r, syncPluginsForUpdateChannel as t };