import ipaddr from "ipaddr.js";

//#region src/shared/net/ip.ts
const BLOCKED_IPV4_SPECIAL_USE_RANGES = new Set([
	"unspecified",
	"broadcast",
	"multicast",
	"linkLocal",
	"loopback",
	"carrierGradeNat",
	"private",
	"reserved"
]);
const PRIVATE_OR_LOOPBACK_IPV4_RANGES = new Set([
	"loopback",
	"private",
	"linkLocal",
	"carrierGradeNat"
]);
const BLOCKED_IPV6_SPECIAL_USE_RANGES = new Set([
	"unspecified",
	"loopback",
	"linkLocal",
	"uniqueLocal",
	"multicast"
]);
const RFC2544_BENCHMARK_PREFIX = [ipaddr.IPv4.parse("198.18.0.0"), 15];
const EMBEDDED_IPV4_SENTINEL_RULES = [
	{
		matches: (parts) => parts[0] === 0 && parts[1] === 0 && parts[2] === 0 && parts[3] === 0 && parts[4] === 0 && parts[5] === 0,
		toHextets: (parts) => [parts[6], parts[7]]
	},
	{
		matches: (parts) => parts[0] === 100 && parts[1] === 65435 && parts[2] === 1 && parts[3] === 0 && parts[4] === 0 && parts[5] === 0,
		toHextets: (parts) => [parts[6], parts[7]]
	},
	{
		matches: (parts) => parts[0] === 8194,
		toHextets: (parts) => [parts[1], parts[2]]
	},
	{
		matches: (parts) => parts[0] === 8193 && parts[1] === 0,
		toHextets: (parts) => [parts[6] ^ 65535, parts[7] ^ 65535]
	},
	{
		matches: (parts) => (parts[4] & 64767) === 0 && parts[5] === 24318,
		toHextets: (parts) => [parts[6], parts[7]]
	}
];
function stripIpv6Brackets(value) {
	if (value.startsWith("[") && value.endsWith("]")) return value.slice(1, -1);
	return value;
}
function isNumericIpv4LiteralPart(value) {
	return /^[0-9]+$/.test(value) || /^0x[0-9a-f]+$/i.test(value);
}
function parseIpv6WithEmbeddedIpv4(raw) {
	if (!raw.includes(":") || !raw.includes(".")) return;
	const match = /^(.*:)([^:%]+(?:\.[^:%]+){3})(%[0-9A-Za-z]+)?$/i.exec(raw);
	if (!match) return;
	const [, prefix, embeddedIpv4, zoneSuffix = ""] = match;
	if (!ipaddr.IPv4.isValidFourPartDecimal(embeddedIpv4)) return;
	const octets = embeddedIpv4.split(".").map((part) => Number.parseInt(part, 10));
	const normalizedIpv6 = `${prefix}${(octets[0] << 8 | octets[1]).toString(16)}:${(octets[2] << 8 | octets[3]).toString(16)}${zoneSuffix}`;
	if (!ipaddr.IPv6.isValid(normalizedIpv6)) return;
	return ipaddr.IPv6.parse(normalizedIpv6);
}
function isIpv4Address(address) {
	return address.kind() === "ipv4";
}
function isIpv6Address(address) {
	return address.kind() === "ipv6";
}
function normalizeIpv4MappedAddress(address) {
	if (!isIpv6Address(address)) return address;
	if (!address.isIPv4MappedAddress()) return address;
	return address.toIPv4Address();
}
function parseCanonicalIpAddress(raw) {
	const trimmed = raw?.trim();
	if (!trimmed) return;
	const normalized = stripIpv6Brackets(trimmed);
	if (!normalized) return;
	if (ipaddr.IPv4.isValid(normalized)) {
		if (!ipaddr.IPv4.isValidFourPartDecimal(normalized)) return;
		return ipaddr.IPv4.parse(normalized);
	}
	if (ipaddr.IPv6.isValid(normalized)) return ipaddr.IPv6.parse(normalized);
	return parseIpv6WithEmbeddedIpv4(normalized);
}
function parseLooseIpAddress(raw) {
	const trimmed = raw?.trim();
	if (!trimmed) return;
	const normalized = stripIpv6Brackets(trimmed);
	if (!normalized) return;
	if (ipaddr.isValid(normalized)) return ipaddr.parse(normalized);
	return parseIpv6WithEmbeddedIpv4(normalized);
}
function normalizeIpAddress(raw) {
	const parsed = parseCanonicalIpAddress(raw);
	if (!parsed) return;
	return normalizeIpv4MappedAddress(parsed).toString().toLowerCase();
}
function isCanonicalDottedDecimalIPv4(raw) {
	const trimmed = raw?.trim();
	if (!trimmed) return false;
	const normalized = stripIpv6Brackets(trimmed);
	if (!normalized) return false;
	return ipaddr.IPv4.isValidFourPartDecimal(normalized);
}
function isLegacyIpv4Literal(raw) {
	const trimmed = raw?.trim();
	if (!trimmed) return false;
	const normalized = stripIpv6Brackets(trimmed);
	if (!normalized || normalized.includes(":")) return false;
	if (isCanonicalDottedDecimalIPv4(normalized)) return false;
	const parts = normalized.split(".");
	if (parts.length === 0 || parts.length > 4) return false;
	if (parts.some((part) => part.length === 0)) return false;
	if (!parts.every((part) => isNumericIpv4LiteralPart(part))) return false;
	return true;
}
function isLoopbackIpAddress(raw) {
	const parsed = parseCanonicalIpAddress(raw);
	if (!parsed) return false;
	return normalizeIpv4MappedAddress(parsed).range() === "loopback";
}
function isPrivateOrLoopbackIpAddress(raw) {
	const parsed = parseCanonicalIpAddress(raw);
	if (!parsed) return false;
	const normalized = normalizeIpv4MappedAddress(parsed);
	if (isIpv4Address(normalized)) return PRIVATE_OR_LOOPBACK_IPV4_RANGES.has(normalized.range());
	return isBlockedSpecialUseIpv6Address(normalized);
}
function isBlockedSpecialUseIpv6Address(address) {
	if (BLOCKED_IPV6_SPECIAL_USE_RANGES.has(address.range())) return true;
	return (address.parts[0] & 65472) === 65216;
}
function isRfc1918Ipv4Address(raw) {
	const parsed = parseCanonicalIpAddress(raw);
	if (!parsed || !isIpv4Address(parsed)) return false;
	return parsed.range() === "private";
}
function isCarrierGradeNatIpv4Address(raw) {
	const parsed = parseCanonicalIpAddress(raw);
	if (!parsed || !isIpv4Address(parsed)) return false;
	return parsed.range() === "carrierGradeNat";
}
function isBlockedSpecialUseIpv4Address(address, options = {}) {
	const inRfc2544BenchmarkRange = address.match(RFC2544_BENCHMARK_PREFIX);
	if (inRfc2544BenchmarkRange && options.allowRfc2544BenchmarkRange === true) return false;
	return BLOCKED_IPV4_SPECIAL_USE_RANGES.has(address.range()) || inRfc2544BenchmarkRange;
}
function decodeIpv4FromHextets(high, low) {
	const octets = [
		high >>> 8 & 255,
		high & 255,
		low >>> 8 & 255,
		low & 255
	];
	return ipaddr.IPv4.parse(octets.join("."));
}
function extractEmbeddedIpv4FromIpv6(address) {
	if (address.isIPv4MappedAddress()) return address.toIPv4Address();
	if (address.range() === "rfc6145") return decodeIpv4FromHextets(address.parts[6], address.parts[7]);
	if (address.range() === "rfc6052") return decodeIpv4FromHextets(address.parts[6], address.parts[7]);
	for (const rule of EMBEDDED_IPV4_SENTINEL_RULES) {
		if (!rule.matches(address.parts)) continue;
		const [high, low] = rule.toHextets(address.parts);
		return decodeIpv4FromHextets(high, low);
	}
}
function isIpInCidr(ip, cidr) {
	const normalizedIp = parseCanonicalIpAddress(ip);
	if (!normalizedIp) return false;
	const candidate = cidr.trim();
	if (!candidate) return false;
	const comparableIp = normalizeIpv4MappedAddress(normalizedIp);
	if (!candidate.includes("/")) {
		const exact = parseCanonicalIpAddress(candidate);
		if (!exact) return false;
		const comparableExact = normalizeIpv4MappedAddress(exact);
		return comparableIp.kind() === comparableExact.kind() && comparableIp.toString() === comparableExact.toString();
	}
	let parsedCidr;
	try {
		parsedCidr = ipaddr.parseCIDR(candidate);
	} catch {
		return false;
	}
	const [baseAddress, prefixLength] = parsedCidr;
	const comparableBase = normalizeIpv4MappedAddress(baseAddress);
	if (comparableIp.kind() !== comparableBase.kind()) return false;
	try {
		if (isIpv4Address(comparableIp) && isIpv4Address(comparableBase)) return comparableIp.match([comparableBase, prefixLength]);
		if (isIpv6Address(comparableIp) && isIpv6Address(comparableBase)) return comparableIp.match([comparableBase, prefixLength]);
		return false;
	} catch {
		return false;
	}
}

//#endregion
export { isCarrierGradeNatIpv4Address as a, isLegacyIpv4Literal as c, isRfc1918Ipv4Address as d, normalizeIpAddress as f, isCanonicalDottedDecimalIPv4 as i, isLoopbackIpAddress as l, parseLooseIpAddress as m, isBlockedSpecialUseIpv4Address as n, isIpInCidr as o, parseCanonicalIpAddress as p, isBlockedSpecialUseIpv6Address as r, isIpv4Address as s, extractEmbeddedIpv4FromIpv6 as t, isPrivateOrLoopbackIpAddress as u };