import { PACKAGE_VERSION, init_package_version } from "../../package-version.js";
import { init_write_file_atomic, writeTextAtomic } from "../../infra/write-file-atomic.js";
import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
import { init_paths, resolveBinDir } from "../../config/paths.js";
import { assertCacheDir, init_cache_dir_policy } from "../cache-dir-policy.js";
import { resolvePackageRoot } from "../../infra/update-check.js";
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
import { readFile, readdir, rm } from "node:fs/promises";
import { dirname, join } from "node:path";
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
//#region src/browser/providers/browser-ext-install.ts
/**
* Install bundled Chrome extension artifacts into {resolveBinDir()}/browser-ext/.
* Fixed path — gateway upgrades overwrite in place so Chrome sideload paths stay stable.
*/
init_package_version();
init_paths();
init_write_file_atomic();
init_logger();
init_cache_dir_policy();
const log = createLogger("BrowserExtInstall");
const META_FILENAME = ".meta.json";
const STAGING_MAX_AGE_MS = 3600 * 1e3;
const INSTALLED_ARTIFACT_NAMES = [
	"manifest.json",
	"popup.html",
	"dist",
	"icons"
];
const BROWSER_EXT_REQUIRED_FILES = [
	"manifest.json",
	"popup.html",
	"dist/background.js",
	"dist/content.js",
	"dist/popup.js"
];
function moduleDir() {
	return dirname(fileURLToPath(import.meta.url));
}
/** Validate a directory contains a loadable extension tree. */
function validateBrowserExtLayout(dir) {
	return BROWSER_EXT_REQUIRED_FILES.every((rel) => existsSync(join(dir, rel)));
}
function readManifestVersion(dir) {
	try {
		const raw = readFileSync(join(dir, "manifest.json"), "utf8");
		const parsed = JSON.parse(raw);
		return typeof parsed.version === "string" ? parsed.version : void 0;
	} catch {
		return;
	}
}
async function readMeta(metaPath) {
	try {
		const raw = await readFile(metaPath, "utf8");
		const parsed = JSON.parse(raw);
		if (parsed && typeof parsed === "object" && typeof parsed.xopcVersion === "string") return parsed;
		return null;
	} catch {
		return null;
	}
}
function browserExtRoot(cacheDir) {
	return join(cacheDir, "browser-ext");
}
function resolveMetaPath(cacheDir) {
	return join(browserExtRoot(cacheDir), META_FILENAME);
}
/** Resolve the installed extension directory (fixed `browser-ext/` root). */
function resolveInstalledExtensionPath(cacheDir, _meta) {
	const root = browserExtRoot(cacheDir);
	if (validateBrowserExtLayout(root)) return root;
	return null;
}
function walkAncestorsForGitDevBundled(start) {
	let dir = start;
	for (let i = 0; i < 12; i++) {
		const candidate = join(dir, "packages/browser-ext");
		if (validateBrowserExtLayout(candidate)) return candidate;
		const parent = dirname(dir);
		if (parent === dir) break;
		dir = parent;
	}
	return null;
}
/**
* Resolve the bundled extension source directory (read-only).
*/
async function resolveBundledBrowserExtDir() {
	const envOverride = process.env.XOPC_BROWSER_EXT_BUNDLED_ROOT?.trim();
	if (envOverride && validateBrowserExtLayout(envOverride)) return {
		dir: envOverride,
		bundledFrom: "env-override"
	};
	const fromModule = join(moduleDir(), "../../../browser-ext");
	if (validateBrowserExtLayout(fromModule)) {
		const root = await resolvePackageRoot();
		return {
			dir: fromModule,
			bundledFrom: process.versions.electron && root && root.includes("app.asar") ? "electron-asar" : "npm-dist"
		};
	}
	const gitDev = walkAncestorsForGitDevBundled(moduleDir());
	if (gitDev) return {
		dir: gitDev,
		bundledFrom: "git-dev"
	};
	const root = await resolvePackageRoot();
	if (root) {
		const distBundled = join(root, "dist/browser-ext");
		if (validateBrowserExtLayout(distBundled)) return {
			dir: distBundled,
			bundledFrom: process.versions.electron && root.includes("app.asar") ? "electron-asar" : "npm-dist"
		};
	}
	return null;
}
function computeNeedsRefresh(params) {
	if (params.force) return true;
	if (!params.installedPath || !validateBrowserExtLayout(params.installedPath)) return true;
	const installedManifest = readManifestVersion(params.installedPath);
	if (!installedManifest || installedManifest !== params.bundledManifestVersion) return true;
	if (!params.meta || params.meta.xopcVersion !== PACKAGE_VERSION) return true;
	return false;
}
async function cleanupStaleStaging(root) {
	if (!existsSync(root)) return;
	let entries;
	try {
		entries = await readdir(root);
	} catch {
		return;
	}
	const now = Date.now();
	for (const name of entries) {
		if (!name.startsWith(".staging-")) continue;
		const full = join(root, name);
		try {
			if (now - statSync(full).mtimeMs > STAGING_MAX_AGE_MS) await rm(full, {
				recursive: true,
				force: true
			});
		} catch {}
	}
}
function removeInstalledArtifacts(root) {
	for (const name of INSTALLED_ARTIFACT_NAMES) {
		const full = join(root, name);
		if (existsSync(full)) rmSync(full, {
			recursive: true,
			force: true
		});
	}
}
function promoteStagingToRoot(stagingDir, root) {
	removeInstalledArtifacts(root);
	for (const name of readdirSync(stagingDir)) renameSync(join(stagingDir, name), join(root, name));
	rmSync(stagingDir, {
		recursive: true,
		force: true
	});
	if (!validateBrowserExtLayout(root)) throw new Error("Bundled browser extension copy failed validation");
}
/** Copy one bundled file (read/write works when src is inside Electron app.asar). */
function copyBundledFile(src, dest) {
	mkdirSync(dirname(dest), { recursive: true });
	writeFileSync(dest, readFileSync(src));
}
const BROWSER_EXT_DIST_FILES = [
	"background.js",
	"content.js",
	"popup.js"
];
const BROWSER_EXT_ICON_FILES = [
	"icon-16.png",
	"icon-32.png",
	"icon-48.png",
	"icon-128.png"
];
function copyBundledTree(src, dest) {
	mkdirSync(dest, { recursive: true });
	for (const name of ["manifest.json", "popup.html"]) copyBundledFile(join(src, name), join(dest, name));
	for (const file of BROWSER_EXT_DIST_FILES) copyBundledFile(join(src, "dist", file), join(dest, "dist", file));
	for (const icon of BROWSER_EXT_ICON_FILES) {
		const iconSrc = join(src, "icons", icon);
		if (existsSync(iconSrc)) copyBundledFile(iconSrc, join(dest, "icons", icon));
	}
	if (!validateBrowserExtLayout(dest)) throw new Error("Bundled browser extension copy failed validation");
}
async function browserExtDoctor(opts) {
	const cacheDir = (opts?.cacheDir?.trim() ? assertCacheDir(opts.cacheDir) : resolveBinDir()) || resolveBinDir();
	const bundled = await resolveBundledBrowserExtDir();
	const bundledManifestVersion = bundled ? readManifestVersion(bundled.dir) : void 0;
	const meta = await readMeta(resolveMetaPath(cacheDir));
	const installedPath = resolveInstalledExtensionPath(cacheDir, meta);
	const needsRefresh = bundled ? computeNeedsRefresh({
		force: false,
		bundledManifestVersion: bundledManifestVersion ?? PACKAGE_VERSION,
		installedPath,
		meta
	}) : false;
	const installed = Boolean(installedPath) && !needsRefresh;
	const manifestVersion = installedPath ? readManifestVersion(installedPath) : void 0;
	let needsChromeReload;
	const runtimeVer = opts?.runtimeExtensionVersion?.trim();
	if (runtimeVer && manifestVersion && runtimeVer !== manifestVersion) needsChromeReload = true;
	return {
		bundledAvailable: Boolean(bundled),
		installed,
		xopcVersion: PACKAGE_VERSION,
		installedVersion: meta?.xopcVersion,
		manifestVersion,
		extensionDir: installedPath ?? void 0,
		cacheDir,
		needsRefresh,
		needsChromeReload,
		bundledFrom: bundled?.bundledFrom,
		runtimeExtensionVersion: runtimeVer
	};
}
async function ensureBrowserExtensionArtifacts(opts) {
	const cacheDir = (opts?.cacheDir?.trim() ? assertCacheDir(opts.cacheDir) : resolveBinDir()) || resolveBinDir();
	const bundled = await resolveBundledBrowserExtDir();
	if (!bundled) throw new Error("Bundled browser extension not found. Reinstall xopc or run from a built checkout (pnpm run build).");
	const bundledManifestVersion = readManifestVersion(bundled.dir) ?? PACKAGE_VERSION;
	const root = browserExtRoot(cacheDir);
	mkdirSync(root, { recursive: true });
	await cleanupStaleStaging(root);
	const meta = await readMeta(resolveMetaPath(cacheDir));
	const installedPath = resolveInstalledExtensionPath(cacheDir, meta);
	const needsRefresh = computeNeedsRefresh({
		force: opts?.force,
		bundledManifestVersion,
		installedPath,
		meta
	});
	const extensionDir = root;
	if (!needsRefresh && installedPath) return {
		extensionDir: installedPath,
		xopcVersion: PACKAGE_VERSION,
		copied: false
	};
	const stagingDir = join(root, `.staging-${process.pid}`);
	if (existsSync(stagingDir)) rmSync(stagingDir, {
		recursive: true,
		force: true
	});
	copyBundledTree(bundled.dir, stagingDir);
	promoteStagingToRoot(stagingDir, root);
	const nextMeta = {
		xopcVersion: PACKAGE_VERSION,
		manifestVersion: bundledManifestVersion,
		source: "bundled",
		bundledFrom: bundled.bundledFrom,
		installedAt: (/* @__PURE__ */ new Date()).toISOString(),
		installPath: extensionDir
	};
	await writeTextAtomic(resolveMetaPath(cacheDir), JSON.stringify(nextMeta, null, 2));
	log.info({
		extensionDir,
		xopcVersion: PACKAGE_VERSION,
		bundledFrom: bundled.bundledFrom
	}, "Browser extension artifacts installed");
	return {
		extensionDir,
		xopcVersion: PACKAGE_VERSION,
		copied: true
	};
}
/**
* Gateway startup hook: ensure artifacts when extension backend is enabled or prior install exists.
*/
async function ensureBrowserExtensionOnStartup(config) {
	const backend = config.agents?.defaults?.browser?.backend;
	const metaExists = existsSync(resolveMetaPath(resolveBinDir()));
	if (backend !== "extension" && !metaExists) return;
	await ensureBrowserExtensionArtifacts();
}
async function readInstalledExtensionDir(cacheDir) {
	const resolved = (cacheDir?.trim() ? assertCacheDir(cacheDir) : resolveBinDir()) || resolveBinDir();
	return resolveInstalledExtensionPath(resolved, await readMeta(resolveMetaPath(resolved)));
}
function spawnDetached(command, args) {
	spawn(command, [...args], {
		stdio: "ignore",
		detached: true
	}).unref();
}
function openChromeExtensionsPage() {
	const chromeUrl = "chrome://extensions";
	if (process.platform === "darwin") {
		spawnDetached("open", [
			"-a",
			"Google Chrome",
			chromeUrl
		]);
		return;
	}
	if (process.platform === "win32") {
		spawnDetached("cmd", [
			"/c",
			"start",
			"chrome",
			chromeUrl
		]);
		return;
	}
	spawnDetached("xdg-open", [chromeUrl]);
}
function revealFolderInFileManager(dir) {
	if (process.platform === "darwin") {
		spawnDetached("open", [dir]);
		return;
	}
	if (process.platform === "win32") {
		spawnDetached("explorer", [dir]);
		return;
	}
	spawnDetached("xdg-open", [dir]);
}
/**
* Open chrome://extensions and/or reveal the installed extension folder on the gateway host.
*/
async function openBrowserExtensionInstallUi(opts) {
	const dir = (await browserExtDoctor({ cacheDir: opts.cacheDir })).extensionDir;
	if (!dir) throw new Error("Extension not installed. Run xopc browser extension install first.");
	if (opts.action === "chrome" || opts.action === "both") openChromeExtensionsPage();
	if (opts.action === "folder" || opts.action === "both") revealFolderInFileManager(dir);
	return { extensionDir: dir };
}
//#endregion
export { BROWSER_EXT_REQUIRED_FILES, browserExtDoctor, computeNeedsRefresh, ensureBrowserExtensionArtifacts, ensureBrowserExtensionOnStartup, openBrowserExtensionInstallUi, readInstalledExtensionDir, resolveBundledBrowserExtDir, resolveInstalledExtensionPath, validateBrowserExtLayout };

//# sourceMappingURL=browser-ext-install.js.map