import { init_paths, resolveExtensionsDir } from "../../config/paths.js";
import { init_loader, loadConfig } from "../../config/loader.js";
import { normalizeExtensionManifest } from "../../extensions/normalize-manifest.js";
import { getContextWithOpts } from "../context.js";
import { getExtensionLockfileManager } from "../../extensions/lockfile.js";
import { installExtensionFromStoreZip, installFromLocal, installFromNpm, peekExtensionIdFromStoreZip } from "../../extensions/install.js";
import { fetchRegistry, findExtension, getExtensionMarketplaceStoreBaseUrl, listExtensions, searchExtensions } from "../../extensions/marketplace.js";
import { downloadExtensionStoreZipBuffer, resolveExtensionZipDownloadUrl, resolveExtensionsStoreBaseUrl } from "../../agent/skills/marketplace/adapters/store/store-api-client.js";
import { colors } from "../utils/colors.js";
import { existsSync, readFileSync, rmSync, statSync } from "node:fs";
import { join, resolve } from "node:path";
import { execSync } from "node:child_process";
import { Command } from "commander";
import semver from "semver";
//#region src/cli/commands/extension-marketplace.ts
init_loader();
init_paths();
const MANIFEST = "xopc.extension.json";
function isRecord(x) {
	return typeof x === "object" && x !== null && !Array.isArray(x);
}
function hasWorkspaceDeps(pkg) {
	const deps = pkg.dependencies;
	const dev = pkg.devDependencies;
	const check = (d) => {
		if (!isRecord(d)) return false;
		return Object.values(d).some((v) => typeof v === "string" && v.startsWith("workspace:"));
	};
	return check(deps) || check(dev);
}
function parseNameAtVersion(raw) {
	const t = raw.trim();
	const at = t.lastIndexOf("@");
	if (at <= 0 || at === t.length - 1) return { name: t };
	const maybeVer = t.slice(at + 1);
	const v = semver.valid(maybeVer);
	if (v) return {
		name: t.slice(0, at),
		version: v
	};
	return { name: t };
}
function looksLikeLocalPath(raw) {
	const p = raw.trim();
	if (p.startsWith("./") || p.startsWith("../")) return true;
	if (p === "." || p === "..") return true;
	try {
		const abs = resolve(process.cwd(), p);
		return existsSync(abs) && statSync(abs).isDirectory();
	} catch {
		return false;
	}
}
/** xopc-store package names: lowercase letters, digits, hyphen (see xopc-store scan). */
const STORE_NAME_RE = /^[a-z0-9-]{1,64}$/;
function looksLikeStorePackageRef(raw) {
	const { name } = parseNameAtVersion(raw);
	return STORE_NAME_RE.test(name);
}
async function upsertNpmExtensionLock(lock, targetDir, result, spec) {
	if (!result.extensionId) return;
	const reg = await findExtension(result.extensionId);
	const resolved = reg?.npmPackage ?? spec;
	let ver = reg?.version ?? "0.0.0";
	try {
		const raw = readFileSync(join(targetDir, result.extensionId, MANIFEST), "utf-8");
		const m = JSON.parse(raw);
		const mv = typeof m.version === "string" ? semver.valid(m.version) : null;
		if (mv) ver = mv;
	} catch {}
	await lock.upsert(result.extensionId, {
		name: result.extensionId,
		version: ver,
		resolved,
		source: "npm"
	});
}
async function installExtensionFromStoreWithLock(params) {
	try {
		const { downloadUrl, version } = await resolveExtensionZipDownloadUrl(params.storeBase, params.packageName, params.version);
		console.log(colors.cyan("📦"), `Downloading ${params.packageName}@${version} from xopc-store (${params.storeBase})…`);
		const buf = await downloadExtensionStoreZipBuffer(params.storeBase, downloadUrl);
		if (params.force) {
			const id = peekExtensionIdFromStoreZip(buf);
			if (id && existsSync(join(params.targetDir, id))) rmSync(join(params.targetDir, id), {
				recursive: true,
				force: true
			});
		}
		const result = await installExtensionFromStoreZip(buf, params.targetDir);
		if (!result.ok || !result.extensionId) return {
			ok: false,
			error: result.error ?? "install failed"
		};
		await params.lock.upsert(result.extensionId, {
			name: result.extensionId,
			version,
			resolved: params.packageName,
			source: "store"
		});
		return {
			ok: true,
			extensionId: result.extensionId,
			version
		};
	} catch (e) {
		return {
			ok: false,
			error: e instanceof Error ? e.message : String(e)
		};
	}
}
function createExtensionInstallCommand() {
	return new Command("install").description("Install extension from xopc-store (store.xopc.ai), npm, or a local directory into ~/.xopc/extensions").argument("<target>", "npm spec, path, store:id, or store-shaped id (npm is tried first; use --store / store: for store-only)").option("--store", "Install from xopc-store only (fail if not an extension package)", false).option("--npm", "Install from npm only", false).option("-f, --force", "Remove existing extension folder (manifest id) before store or local install", false).action(async (target, opts) => {
		const cfg = loadConfig(getContextWithOpts().configPath);
		const targetDir = resolveExtensionsDir();
		const lock = getExtensionLockfileManager();
		let installTarget = target.trim();
		const storeExplicit = /^store:/i.test(installTarget);
		if (storeExplicit) installTarget = installTarget.replace(/^store:/i, "").trim();
		if (!installTarget) {
			console.error(colors.red("error:"), "Missing target");
			process.exit(1);
		}
		if (opts.store && opts.npm) {
			console.error(colors.red("error:"), "Use only one of --store and --npm");
			process.exit(1);
		}
		const storeOnly = opts.store || storeExplicit;
		if (storeExplicit && opts.npm) {
			console.error(colors.red("error:"), "Cannot combine store: prefix with --npm");
			process.exit(1);
		}
		const storeBase = resolveExtensionsStoreBaseUrl(cfg);
		if (storeOnly) {
			const { name: pkgName, version: ver } = parseNameAtVersion(installTarget);
			if (!STORE_NAME_RE.test(pkgName)) {
				console.error(colors.red("error:"), "Invalid store package name (lowercase letters, digits, hyphen only)");
				process.exit(1);
			}
			const r = await installExtensionFromStoreWithLock({
				storeBase,
				packageName: pkgName,
				version: ver,
				targetDir,
				lock,
				force: opts.force
			});
			if (r.ok === false) {
				console.error(colors.red("error:"), r.error);
				process.exit(1);
			}
			console.log(colors.green("✓"), `${r.extensionId}@${r.version} (store)`);
			return;
		}
		if (opts.npm) {
			const spec = installTarget;
			console.log(colors.cyan("📦"), `Installing from npm: ${spec}…`);
			const result = await installFromNpm(spec, targetDir);
			if (!result.ok) {
				console.error(colors.red("error:"), result.error ?? "install failed");
				process.exit(1);
			}
			await upsertNpmExtensionLock(lock, targetDir, result, spec);
			console.log(colors.green("✓"), result.extensionId ?? "ok", "(npm)");
			return;
		}
		if (looksLikeLocalPath(installTarget)) {
			const sourceDir = resolve(process.cwd(), installTarget);
			if (opts.force) {
				const manifestPath = join(sourceDir, MANIFEST);
				if (existsSync(manifestPath)) try {
					const raw = readFileSync(manifestPath, "utf-8");
					const m = JSON.parse(raw);
					const extId = typeof m.id === "string" && m.id && !m.id.includes("/") && !m.id.includes("\\") ? m.id : void 0;
					if (extId && existsSync(join(targetDir, extId))) rmSync(join(targetDir, extId), {
						recursive: true,
						force: true
					});
				} catch {}
			}
			console.log(colors.cyan("📂"), "Installing from local directory…");
			const result = await installFromLocal(sourceDir, targetDir);
			if (!result.ok) {
				console.error(colors.red("error:"), result.error ?? "install failed");
				process.exit(1);
			}
			console.log(colors.green("✓"), result.extensionId ?? "ok");
			return;
		}
		if (looksLikeStorePackageRef(installTarget)) {
			const spec = installTarget;
			console.log(colors.cyan("📦"), `Trying npm: ${spec}…`);
			const npmTry = await installFromNpm(spec, targetDir);
			if (npmTry.ok) {
				await upsertNpmExtensionLock(lock, targetDir, npmTry, spec);
				console.log(colors.green("✓"), npmTry.extensionId ?? "ok", "(npm)");
				return;
			}
			console.log(colors.yellow("npm:"), npmTry.error ?? "failed", "— trying xopc-store…");
			const { name: pkgName, version: ver } = parseNameAtVersion(installTarget);
			const r = await installExtensionFromStoreWithLock({
				storeBase,
				packageName: pkgName,
				version: ver,
				targetDir,
				lock,
				force: opts.force
			});
			if (r.ok === false) {
				console.error(colors.red("error:"), r.error);
				process.exit(1);
			}
			console.log(colors.green("✓"), `${r.extensionId}@${r.version} (store)`);
			return;
		}
		const spec = installTarget;
		console.log(colors.cyan("📦"), `Installing from npm: ${spec}…`);
		const result = await installFromNpm(spec, targetDir);
		if (!result.ok) {
			console.error(colors.red("error:"), result.error ?? "install failed");
			process.exit(1);
		}
		await upsertNpmExtensionLock(lock, targetDir, result, spec);
		console.log(colors.green("✓"), result.extensionId ?? "ok", "(npm)");
	});
}
function createExtensionSearchCommand() {
	return new Command("search").description("Search extensions listed on xopc-store").argument("[keyword]", "Search text (omit to list all)", "").option("--category <cat>", "Filter by category").option("--json", "JSON output").action(async (keyword, opts) => {
		try {
			let rows;
			if (opts.category?.trim()) {
				rows = await listExtensions(opts.category.trim());
				if (keyword.trim()) {
					const k = keyword.trim().toLowerCase();
					rows = rows.filter((e) => e.id.toLowerCase().includes(k) || e.name.toLowerCase().includes(k) || (e.description ?? "").toLowerCase().includes(k));
				}
			} else if (keyword.trim()) rows = await searchExtensions(keyword.trim());
			else rows = (await fetchRegistry()).extensions;
			if (opts.json) {
				console.log(JSON.stringify(rows, null, 2));
				return;
			}
			if (rows.length === 0) {
				console.log("No extensions found.");
				return;
			}
			console.log(`xopc-store: ${getExtensionMarketplaceStoreBaseUrl()}`);
			console.log("");
			for (const e of rows) {
				const badge = e.verified ? ` ${colors.green("✓")}` : "";
				console.log(`${colors.cyan(e.name)}${badge} ${colors.gray(e.version ?? "")}`);
				console.log(`  id: ${e.id}  npm: ${e.npmPackage}`);
				if (e.description) console.log(`  ${e.description}`);
				if (e.categories?.length) console.log(`  categories: ${e.categories.join(", ")}`);
				console.log("");
			}
		} catch (err) {
			console.error(colors.red("Error:"), err instanceof Error ? err.message : String(err));
			process.exit(1);
		}
	});
}
function createExtensionPublishCommand() {
	return new Command("publish").description("Publish extension to npm (public)").argument("[directory]", "Extension root", ".").option("--dry-run", "npm publish --dry-run", false).option("--access <level>", "npm access", "public").action((dir, opts) => {
		const root = resolve(dir || ".");
		const manifestPath = join(root, MANIFEST);
		const pkgPath = join(root, "package.json");
		if (!existsSync(manifestPath) || !existsSync(pkgPath)) {
			console.error(colors.red("error:"), `Need ${MANIFEST} and package.json in ${root}`);
			process.exit(1);
		}
		try {
			if (hasWorkspaceDeps(JSON.parse(readFileSync(pkgPath, "utf-8")))) {
				console.error(colors.red("error:"), "Remove workspace:* dependencies before publishing.");
				process.exit(1);
			}
			if (!normalizeExtensionManifest(JSON.parse(readFileSync(manifestPath, "utf-8"))).id) {
				console.error(colors.red("error:"), "Invalid manifest id");
				process.exit(1);
			}
		} catch (e) {
			console.error(colors.red("error:"), e instanceof Error ? e.message : String(e));
			process.exit(1);
		}
		const args = ["publish", `--access=${opts.access}`];
		if (opts.dryRun) args.push("--dry-run");
		console.log(colors.cyan("Running:"), `npm ${args.join(" ")}`);
		try {
			execSync(`npm ${args.join(" ")}`, {
				cwd: root,
				stdio: "inherit"
			});
		} catch {
			process.exit(1);
		}
	});
}
function createExtensionUpdateCommand() {
	return new Command("update").description("Re-install extension(s) from the lockfile (npm or xopc-store) under ~/.xopc/extensions").argument("[extensionId]", "Specific extension id (default: all in lockfile)").action(async (extensionId) => {
		const cfg = loadConfig(getContextWithOpts().configPath);
		const targetDir = resolveExtensionsDir();
		const storeBase = resolveExtensionsStoreBaseUrl(cfg);
		const lock = getExtensionLockfileManager();
		const data = await lock.load();
		const ids = extensionId?.trim() ? [extensionId.trim()] : Object.keys(data.extensions);
		if (ids.length === 0) {
			console.log("No extensions in lockfile.");
			return;
		}
		for (const id of ids) {
			const entry = data.extensions[id];
			if (!entry) {
				console.log(colors.yellow("skip"), id, "(not in lockfile)");
				continue;
			}
			if (entry.source === "store") {
				const pkgName = entry.resolved?.trim() || id;
				console.log(colors.cyan("Updating"), id, "←", `store:${pkgName}`);
				if (existsSync(join(targetDir, id))) rmSync(join(targetDir, id), {
					recursive: true,
					force: true
				});
				const r = await installExtensionFromStoreWithLock({
					storeBase,
					packageName: pkgName,
					targetDir,
					lock
				});
				if (r.ok === false) {
					console.error(colors.red("error:"), r.error ?? id);
					process.exit(1);
				}
				console.log(colors.green("✓"), id);
				continue;
			}
			if (entry.source !== "npm") {
				console.log(colors.yellow("skip"), id, `(source ${entry.source})`);
				continue;
			}
			const spec = entry.resolved?.trim() || await npmPackageForId(id);
			if (!spec) {
				console.log(colors.yellow("skip"), id, "(could not resolve npm package)");
				continue;
			}
			console.log(colors.cyan("Updating"), id, "←", spec);
			if (existsSync(join(targetDir, id))) rmSync(join(targetDir, id), {
				recursive: true,
				force: true
			});
			const result = await installFromNpm(spec, targetDir);
			if (!result.ok) {
				console.error(colors.red("error:"), result.error ?? id);
				process.exit(1);
			}
			await lock.upsert(id, {
				name: id,
				version: entry.version,
				resolved: spec,
				source: "npm"
			});
			console.log(colors.green("✓"), id);
		}
	});
}
async function npmPackageForId(id) {
	return (await findExtension(id))?.npmPackage;
}
//#endregion
export { createExtensionInstallCommand, createExtensionPublishCommand, createExtensionSearchCommand, createExtensionUpdateCommand };

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