import { Q as theme, _ as defaultRuntime } from "./entry.js";
import { t as runCommandWithTimeout } from "./exec-CzwIUObA.js";
import "./agent-scope-C-Jte08L.js";
import "./model-selection-CMx1evaF.js";
import "./manifest-registry-CVLmi8rz.js";
import { i as loadConfig } from "./config-D7TcU4qN.js";
import "./shell-env-CTYaxUs_.js";
import os from "node:os";
import qrcode from "qrcode-terminal";

//#region src/pairing/setup-code.ts
const DEFAULT_GATEWAY_PORT = 18789;
function normalizeUrl(raw, schemeFallback) {
	const trimmed = raw.trim();
	if (!trimmed) return null;
	try {
		const parsed = new URL(trimmed);
		const scheme = parsed.protocol.replace(":", "");
		if (!scheme) return null;
		const resolvedScheme = scheme === "http" ? "ws" : scheme === "https" ? "wss" : scheme;
		if (resolvedScheme !== "ws" && resolvedScheme !== "wss") return null;
		const host = parsed.hostname;
		if (!host) return null;
		return `${resolvedScheme}://${host}${parsed.port ? `:${parsed.port}` : ""}`;
	} catch {}
	const withoutPath = trimmed.split("/")[0] ?? "";
	if (!withoutPath) return null;
	return `${schemeFallback}://${withoutPath}`;
}
function resolveGatewayPort(cfg, env) {
	const envRaw = env.SYMI_GATEWAY_PORT?.trim();
	if (envRaw) {
		const parsed = Number.parseInt(envRaw, 10);
		if (Number.isFinite(parsed) && parsed > 0) return parsed;
	}
	const configPort = cfg.gateway?.port;
	if (typeof configPort === "number" && Number.isFinite(configPort) && configPort > 0) return configPort;
	return DEFAULT_GATEWAY_PORT;
}
function resolveScheme(cfg, opts) {
	if (opts?.forceSecure) return "wss";
	return cfg.gateway?.tls?.enabled === true ? "wss" : "ws";
}
function parseIPv4Octets(address) {
	const parts = address.split(".");
	if (parts.length !== 4) return null;
	const octets = parts.map((part) => Number.parseInt(part, 10));
	if (octets.some((value) => !Number.isFinite(value) || value < 0 || value > 255)) return null;
	return [
		octets[0],
		octets[1],
		octets[2],
		octets[3]
	];
}
function isPrivateIPv4(address) {
	const octets = parseIPv4Octets(address);
	if (!octets) return false;
	const [a, b] = octets;
	if (a === 10) return true;
	if (a === 172 && b >= 16 && b <= 31) return true;
	if (a === 192 && b === 168) return true;
	return false;
}
function isTailnetIPv4(address) {
	const octets = parseIPv4Octets(address);
	if (!octets) return false;
	const [a, b] = octets;
	return a === 100 && b >= 64 && b <= 127;
}
function pickIPv4Matching(networkInterfaces, matches) {
	const nets = networkInterfaces();
	for (const entries of Object.values(nets)) {
		if (!entries) continue;
		for (const entry of entries) {
			const isIpv4 = entry?.family === "IPv4";
			if (!entry || entry.internal || !isIpv4) continue;
			const address = entry.address?.trim() ?? "";
			if (!address) continue;
			if (matches(address)) return address;
		}
	}
	return null;
}
function pickLanIPv4(networkInterfaces) {
	return pickIPv4Matching(networkInterfaces, isPrivateIPv4);
}
function pickTailnetIPv4(networkInterfaces) {
	return pickIPv4Matching(networkInterfaces, isTailnetIPv4);
}
function parsePossiblyNoisyJsonObject(raw) {
	const start = raw.indexOf("{");
	const end = raw.lastIndexOf("}");
	if (start === -1 || end <= start) return {};
	try {
		return JSON.parse(raw.slice(start, end + 1));
	} catch {
		return {};
	}
}
async function resolveTailnetHost(runCommandWithTimeout) {
	if (!runCommandWithTimeout) return null;
	for (const candidate of ["tailscale", "/Applications/Tailscale.app/Contents/MacOS/Tailscale"]) try {
		const result = await runCommandWithTimeout([
			candidate,
			"status",
			"--json"
		], { timeoutMs: 5e3 });
		if (result.code !== 0) continue;
		const raw = result.stdout.trim();
		if (!raw) continue;
		const parsed = parsePossiblyNoisyJsonObject(raw);
		const self = typeof parsed.Self === "object" && parsed.Self !== null ? parsed.Self : void 0;
		const dns = typeof self?.DNSName === "string" ? self.DNSName : void 0;
		if (dns && dns.length > 0) return dns.replace(/\.$/, "");
		const ips = Array.isArray(self?.TailscaleIPs) ? self.TailscaleIPs : [];
		if (ips.length > 0) return ips[0] ?? null;
	} catch {
		continue;
	}
	return null;
}
function resolveAuth(cfg, env) {
	const mode = cfg.gateway?.auth?.mode;
	const token = env.SYMI_GATEWAY_TOKEN?.trim() || env.SYMI_GATEWAY_TOKEN?.trim() || cfg.gateway?.auth?.token?.trim();
	const password = env.SYMI_GATEWAY_PASSWORD?.trim() || env.SYMI_GATEWAY_PASSWORD?.trim() || cfg.gateway?.auth?.password?.trim();
	if (mode === "password") {
		if (!password) return { error: "Gateway auth is set to password, but no password is configured." };
		return {
			password,
			label: "password"
		};
	}
	if (mode === "token") {
		if (!token) return { error: "Gateway auth is set to token, but no token is configured." };
		return {
			token,
			label: "token"
		};
	}
	if (token) return {
		token,
		label: "token"
	};
	if (password) return {
		password,
		label: "password"
	};
	return { error: "Gateway auth is not configured (no token or password)." };
}
async function resolveGatewayUrl(cfg, opts) {
	const scheme = resolveScheme(cfg, { forceSecure: opts.forceSecure });
	const port = resolveGatewayPort(cfg, opts.env);
	if (typeof opts.publicUrl === "string" && opts.publicUrl.trim()) {
		const url = normalizeUrl(opts.publicUrl, scheme);
		if (url) return {
			url,
			source: "plugins.entries.device-pair.config.publicUrl"
		};
		return { error: "Configured publicUrl is invalid." };
	}
	const remoteUrlRaw = cfg.gateway?.remote?.url;
	const remoteUrl = typeof remoteUrlRaw === "string" && remoteUrlRaw.trim() ? normalizeUrl(remoteUrlRaw, scheme) : null;
	if (opts.preferRemoteUrl && remoteUrl) return {
		url: remoteUrl,
		source: "gateway.remote.url"
	};
	const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
	if (tailscaleMode === "serve" || tailscaleMode === "funnel") {
		const host = await resolveTailnetHost(opts.runCommandWithTimeout);
		if (!host) return { error: "Tailscale Serve is enabled, but MagicDNS could not be resolved." };
		return {
			url: `wss://${host}`,
			source: `gateway.tailscale.mode=${tailscaleMode}`
		};
	}
	if (remoteUrl) return {
		url: remoteUrl,
		source: "gateway.remote.url"
	};
	const bind = cfg.gateway?.bind ?? "loopback";
	if (bind === "custom") {
		const host = cfg.gateway?.customBindHost?.trim();
		if (host) return {
			url: `${scheme}://${host}:${port}`,
			source: "gateway.bind=custom"
		};
		return { error: "gateway.bind=custom requires gateway.customBindHost." };
	}
	if (bind === "tailnet") {
		const host = pickTailnetIPv4(opts.networkInterfaces);
		if (host) return {
			url: `${scheme}://${host}:${port}`,
			source: "gateway.bind=tailnet"
		};
		return { error: "gateway.bind=tailnet set, but no tailnet IP was found." };
	}
	if (bind === "lan") {
		const host = pickLanIPv4(opts.networkInterfaces);
		if (host) return {
			url: `${scheme}://${host}:${port}`,
			source: "gateway.bind=lan"
		};
		return { error: "gateway.bind=lan set, but no private LAN IP was found." };
	}
	return { error: "Gateway is only bound to loopback. Set gateway.bind=lan, enable tailscale serve, or configure plugins.entries.device-pair.config.publicUrl." };
}
function encodePairingSetupCode(payload) {
	const json = JSON.stringify(payload);
	return Buffer.from(json, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}
async function resolvePairingSetupFromConfig(cfg, options = {}) {
	const env = options.env ?? process.env;
	const auth = resolveAuth(cfg, env);
	if (auth.error) return {
		ok: false,
		error: auth.error
	};
	const urlResult = await resolveGatewayUrl(cfg, {
		env,
		publicUrl: options.publicUrl,
		preferRemoteUrl: options.preferRemoteUrl,
		forceSecure: options.forceSecure,
		runCommandWithTimeout: options.runCommandWithTimeout,
		networkInterfaces: options.networkInterfaces ?? os.networkInterfaces
	});
	if (!urlResult.url) return {
		ok: false,
		error: urlResult.error ?? "Gateway URL unavailable."
	};
	if (!auth.label) return {
		ok: false,
		error: "Gateway auth is not configured (no token or password)."
	};
	return {
		ok: true,
		payload: {
			url: urlResult.url,
			token: auth.token,
			password: auth.password
		},
		authLabel: auth.label,
		urlSource: urlResult.source ?? "unknown"
	};
}

//#endregion
//#region src/cli/qr-cli.ts
function renderQrAscii(data) {
	return new Promise((resolve) => {
		qrcode.generate(data, { small: true }, (output) => {
			resolve(output);
		});
	});
}
function readDevicePairPublicUrlFromConfig(cfg) {
	const value = cfg.plugins?.entries?.["device-pair"]?.config?.["publicUrl"];
	if (typeof value !== "string") return;
	const trimmed = value.trim();
	return trimmed.length > 0 ? trimmed : void 0;
}
function registerQrCli(program) {
	program.command("qr").description("Generate an iOS pairing QR code and setup code").option("--remote", "Use gateway.remote.url and gateway.remote token/password (ignores device-pair publicUrl)", false).option("--url <url>", "Override gateway URL used in the setup payload").option("--public-url <url>", "Override gateway public URL used in the setup payload").option("--token <token>", "Override gateway token for setup payload").option("--password <password>", "Override gateway password for setup payload").option("--setup-code-only", "Print only the setup code", false).option("--no-ascii", "Skip ASCII QR rendering").option("--json", "Output JSON", false).action(async (opts) => {
		try {
			if (opts.token && opts.password) throw new Error("Use either --token or --password, not both.");
			const loaded = loadConfig();
			const cfg = {
				...loaded,
				gateway: {
					...loaded.gateway,
					auth: { ...loaded.gateway?.auth }
				}
			};
			const token = typeof opts.token === "string" ? opts.token.trim() : "";
			const password = typeof opts.password === "string" ? opts.password.trim() : "";
			const wantsRemote = opts.remote === true;
			if (token) {
				cfg.gateway.auth.mode = "token";
				cfg.gateway.auth.token = token;
			}
			if (password) {
				cfg.gateway.auth.mode = "password";
				cfg.gateway.auth.password = password;
			}
			if (wantsRemote && !token && !password) {
				const remoteToken = typeof cfg.gateway?.remote?.token === "string" ? cfg.gateway.remote.token.trim() : "";
				const remotePassword = typeof cfg.gateway?.remote?.password === "string" ? cfg.gateway.remote.password.trim() : "";
				if (remoteToken) {
					cfg.gateway.auth.mode = "token";
					cfg.gateway.auth.token = remoteToken;
					cfg.gateway.auth.password = void 0;
				} else if (remotePassword) {
					cfg.gateway.auth.mode = "password";
					cfg.gateway.auth.password = remotePassword;
					cfg.gateway.auth.token = void 0;
				}
			}
			if (wantsRemote && !opts.url && !opts.publicUrl) {
				const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
				const remoteUrl = cfg.gateway?.remote?.url;
				if (!(typeof remoteUrl === "string" && remoteUrl.trim().length > 0) && !(tailscaleMode === "serve" || tailscaleMode === "funnel")) throw new Error("qr --remote requires gateway.remote.url (or gateway.tailscale.mode=serve/funnel).");
			}
			const resolved = await resolvePairingSetupFromConfig(cfg, {
				publicUrl: (typeof opts.url === "string" && opts.url.trim() ? opts.url.trim() : typeof opts.publicUrl === "string" && opts.publicUrl.trim() ? opts.publicUrl.trim() : void 0) ?? (wantsRemote ? void 0 : readDevicePairPublicUrlFromConfig(cfg)),
				preferRemoteUrl: wantsRemote,
				runCommandWithTimeout: async (argv, runOpts) => await runCommandWithTimeout(argv, { timeoutMs: runOpts.timeoutMs })
			});
			if (!resolved.ok) throw new Error(resolved.error);
			const setupCode = encodePairingSetupCode(resolved.payload);
			if (opts.setupCodeOnly) {
				defaultRuntime.log(setupCode);
				return;
			}
			if (opts.json) {
				defaultRuntime.log(JSON.stringify({
					setupCode,
					gatewayUrl: resolved.payload.url,
					auth: resolved.authLabel,
					urlSource: resolved.urlSource
				}, null, 2));
				return;
			}
			const lines = [
				theme.heading("Pairing QR"),
				"Scan this with the Symi iOS app (Onboarding -> Scan QR).",
				""
			];
			if (opts.ascii !== false) {
				const qrAscii = await renderQrAscii(setupCode);
				lines.push(qrAscii.trimEnd(), "");
			}
			lines.push(`${theme.muted("Setup code:")} ${setupCode}`, `${theme.muted("Gateway:")} ${resolved.payload.url}`, `${theme.muted("Auth:")} ${resolved.authLabel}`, `${theme.muted("Source:")} ${resolved.urlSource}`, "", "Approve after scan with:", `  ${theme.command("symi devices list")}`, `  ${theme.command("symi devices approve <requestId>")}`);
			defaultRuntime.log(lines.join("\n"));
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
		}
	});
}

//#endregion
export { registerQrCli };