import { isNetworkAccessibleBindHost, resolveGatewayEffectiveHost } from "../config/gateway-bind.js";
import { enumerateLanGatewayCandidates } from "./tunnel-qr.js";
import net from "node:net";
//#region src/tunnel/pair-url.ts
/** Normalize a gateway root URL (no trailing slash, no path). */
function normalizeGatewayBaseUrl(raw) {
	const trimmed = raw.trim();
	if (!trimmed) return null;
	try {
		const u = new URL(trimmed);
		if (u.protocol !== "http:" && u.protocol !== "https:") return null;
		if (u.username || u.password) return null;
		if (u.pathname && u.pathname !== "/") return null;
		if (u.search || u.hash) return null;
		const portSuffix = u.port ? `:${u.port}` : "";
		return `${u.protocol}//${u.hostname}${portSuffix}`;
	} catch {
		return null;
	}
}
/** True when a phone on the network cannot reach this gateway root URL. */
function isLoopbackGatewayBaseUrl(raw) {
	const normalized = normalizeGatewayBaseUrl(raw);
	if (!normalized) return false;
	try {
		const host = new URL(normalized).hostname.trim().toLowerCase();
		if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]") return true;
		if (net.isIP(host) === 4) {
			const parts = host.split(".").map((p) => Number(p));
			if (parts.length === 4 && parts.every((n) => Number.isInteger(n)) && parts[0] === 127) return true;
		}
		return false;
	} catch {
		return false;
	}
}
function validateMobilePairBaseUrl(raw) {
	const url = normalizeGatewayBaseUrl(raw);
	if (!url) return {
		ok: false,
		code: "INVALID_URL",
		message: "Enter an absolute gateway URL starting with http:// or https:// (no path)."
	};
	if (isLoopbackGatewayBaseUrl(url)) return {
		ok: false,
		code: "LOOPBACK_NOT_REACHABLE",
		message: "127.0.0.1 and localhost only work on the gateway machine. Use a LAN IP or tunnel URL instead."
	};
	return {
		ok: true,
		url,
		loopback: false
	};
}
/** Parse `xopc://gateway/mobile-connect?...` payloads from QR codes. */
function parseMobileConnectDeepLink(raw) {
	const trimmed = raw.trim();
	if (!trimmed) return null;
	try {
		const u = new URL(trimmed);
		if (u.protocol !== "xopc:" || u.hostname !== "gateway" || u.pathname !== "/mobile-connect") return null;
		const baseUrl = u.searchParams.get("baseUrl")?.trim() ?? "";
		const psRaw = u.searchParams.get("ps")?.trim() ?? "";
		let pairingSecret = psRaw;
		if (psRaw) try {
			pairingSecret = decodeURIComponent(psRaw);
		} catch {
			pairingSecret = psRaw;
		}
		if (!baseUrl || !pairingSecret) return null;
		const lanRaw = u.searchParams.get("lanUrl")?.trim() ?? "";
		const lanUrl = lanRaw ? normalizeGatewayBaseUrl(lanRaw) : null;
		const normalizedBase = normalizeGatewayBaseUrl(baseUrl);
		if (!normalizedBase) return null;
		return {
			baseUrl: normalizedBase,
			lanUrl,
			pairingSecret
		};
	} catch {
		return null;
	}
}
/**
* Ordered URLs for mobile clients. Priority:
*   1. reverse-proxy public URL (user-deployed HTTPS, typically fastest + works on cellular)
*   2. LAN URL (zero-RTT on same network)
*   3. baseUrl (legacy: usually the FRP tunnel public URL, kept for callers that
*      do not yet split reverseProxyUrl from tunnelUrl)
*   4. tunnelUrl (explicit FRP tunnel URL, kept as a fallback even when a
*      reverse-proxy URL is also configured)
*
* Loopback URLs are dropped (they only work on the gateway host) and
* duplicates are de-duped while preserving first occurrence order.
*/
function buildMobileConnectUrlOrder(params) {
	const ordered = [];
	const seen = /* @__PURE__ */ new Set();
	const push = (raw) => {
		const url = raw ? normalizeGatewayBaseUrl(raw) : null;
		if (!url || isLoopbackGatewayBaseUrl(url) || seen.has(url)) return;
		seen.add(url);
		ordered.push(url);
	};
	push(params.reverseProxyUrl);
	push(params.lanUrl);
	push(params.baseUrl);
	push(params.tunnelUrl);
	return ordered;
}
/** First LAN URL when the gateway listens on a network-accessible bind host. */
function resolveMobilePairLanUrl(config) {
	if (!isNetworkAccessibleBindHost(resolveGatewayEffectiveHost(config))) return null;
	return enumerateLanGatewayCandidates(config.gateway?.port ?? 18790)[0]?.url ?? null;
}
//#endregion
export { buildMobileConnectUrlOrder, isLoopbackGatewayBaseUrl, normalizeGatewayBaseUrl, parseMobileConnectDeepLink, resolveMobilePairLanUrl, validateMobilePairBaseUrl };

//# sourceMappingURL=pair-url.js.map