/** IPv4 helpers for RIPEstat prefix cache and EvoBGP CIDR match. */

export function ipv4ToInt(ip: string): number | null {
  const parts = String(ip ?? "").trim().split(".")
  if (parts.length !== 4) return null
  let n = 0
  for (const p of parts) {
    if (!/^\d+$/.test(p)) return null
    const o = Number(p)
    if (o < 0 || o > 255) return null
    n = ((n << 8) >>> 0) + o
  }
  return n >>> 0
}

export function parseCidrV4(cidr: string): { net: number; mask: number; prefixLen: number } | null {
  const raw = String(cidr ?? "").trim()
  const [ip, lenRaw] = raw.split("/")
  const addr = ipv4ToInt(ip ?? "")
  const prefixLen = Number.parseInt(lenRaw ?? "", 10)
  if (addr == null || !Number.isFinite(prefixLen) || prefixLen < 0 || prefixLen > 32) return null
  const mask = prefixLen === 0 ? 0 : (0xffffffff << (32 - prefixLen)) >>> 0
  return { net: (addr & mask) >>> 0, mask, prefixLen }
}

export function ipInCidrV4(ip: string, cidr: string): boolean {
  const addr = ipv4ToInt(ip)
  const parsed = parseCidrV4(cidr)
  if (addr == null || !parsed) return false
  return ((addr & parsed.mask) >>> 0) === parsed.net
}

export function isNonPublicIp(ip: string): boolean {
  const trimmed = String(ip ?? "").trim()
  if (!trimmed) return true
  if (trimmed.includes(":")) {
    const lower = trimmed.toLowerCase()
    return lower === "::1" || lower.startsWith("fe80:") || lower.startsWith("fc") || lower.startsWith("fd") || lower === "::"
  }
  const n = ipv4ToInt(trimmed)
  if (n == null) return true
  const inRange = (cidr: string) => ipInCidrV4(trimmed, cidr)
  return (
    inRange("0.0.0.0/8")
    || inRange("10.0.0.0/8")
    || inRange("127.0.0.0/8")
    || inRange("169.254.0.0/16")
    || inRange("172.16.0.0/12")
    || inRange("192.168.0.0/16")
    || inRange("100.64.0.0/10")
    || inRange("224.0.0.0/4")
    || inRange("255.255.255.255/32")
  )
}
