// Mapping incoming deep links to in-app routes. Used by `app/+native-intent.tsx`
// so a universal link to a relay subdomain opens the app straight into the
// pairing screen. Kept as a pure, dependency-free module so the parsing is
// unit-testable without expo-router or react-native.

// The relay registrable domain. A universal link to any `*.<RELAY_DOMAIN>` host
// is a "connect to this gateway" link. Mirrors the gateway's GINI_RELAY_DOMAIN
// default (src/lib/origin-trust.ts).
export const RELAY_DOMAIN = "gini-relay.lilaclabs.ai";

// True when `host` is the relay domain or one of its per-device subdomains. A
// trailing-label match (not a substring) so `gini-relay.lilaclabs.ai.evil.com`
// does NOT match. Strips an optional :port.
export function isRelayHost(host: string): boolean {
  const lower = host.toLowerCase().replace(/:\d+$/, "");
  return lower === RELAY_DOMAIN || lower.endsWith(`.${RELAY_DOMAIN}`);
}

// Hosts the device-pairing handshake can run against. Mirrors the gateway's
// native-pairing gate (relay OR loopback): pairing exists for the relay, where
// copying a bearer token to the phone is hard. A gateway on the local network is
// reached with the bearer token on the setup screen instead, so a LAN URL is not
// pairable here — guiding the user to that is clearer than a server 403.
export function isPairableHost(host: string): boolean {
  if (isRelayHost(host)) return true;
  const lower = host.toLowerCase();
  // Strip the port without mangling IPv6: a bracketed `[::1]:port` keeps `[::1]`;
  // a bare IPv6 `::1` (multiple colons, no brackets) is left intact; a plain
  // `host:port` (single colon) drops the port.
  const hostname = lower.startsWith("[")
    ? lower.slice(0, lower.indexOf("]") + 1)
    : lower.includes(":") && lower.indexOf(":") === lower.lastIndexOf(":")
      ? lower.slice(0, lower.indexOf(":"))
      : lower;
  return (
    hostname === "localhost"
    || hostname === "127.0.0.1"
    || hostname === "::1"
    || hostname === "[::1]"
  );
}

// Whether a deep-link auto-pair should pause for explicit confirmation: only when
// credentials ALREADY exist AND the incoming relay host differs from the stored
// one. This blocks a silent gateway switch — a relay link to an attacker's own
// tenant (which passes isPairableHost) would otherwise repoint an already-paired
// app to the attacker's backend after they approve their own request. A
// first-time pair, or re-pairing the SAME host, proceeds without a prompt. A
// malformed/unparseable existing or incoming value errs toward confirming.
export function isGatewaySwitch(
  existingBaseUrl: string | null | undefined,
  incomingOrigin: string
): boolean {
  if (!existingBaseUrl) return false;
  let existingHost: string;
  let incomingHost: string;
  try {
    existingHost = new URL(existingBaseUrl).host.toLowerCase();
  } catch {
    return true;
  }
  try {
    incomingHost = new URL(incomingOrigin).host.toLowerCase();
  } catch {
    return true;
  }
  return existingHost !== incomingHost;
}

// Map an incoming deep-link URL to an in-app route. An https link to a relay
// host becomes `/pair?relay=<https origin>` so the pair screen knows which
// gateway to pair with (the host identifies the gateway; the link's own path is
// irrelevant). Anything else returns null — the caller then leaves the original
// path untouched so the app's own `gini://` links and other routes still work.
// Pure and total: never throws (a malformed URL just returns null).
export function relayPairingRedirect(url: string): string | null {
  let parsed: URL;
  try {
    parsed = new URL(url);
  } catch {
    return null;
  }
  // Only the HTTPS relay front. A `gini://...` custom-scheme link or any other
  // host falls through to expo-router's normal routing.
  if (parsed.protocol !== "https:") return null;
  if (!isRelayHost(parsed.host)) return null;
  return `/pair?relay=${encodeURIComponent(`https://${parsed.host}`)}`;
}
