const FNV_PRIME = 16_777_619;
const FNV_OFFSET_BASIS = 2_166_136_261;

export const GOOGLE_ANALYTICS_READY_EVENT = "officialmail:google-analytics-ready";

type AnalyticsGtag = {
  (command: "get", targetId: string, fieldName: "client_id" | "session_id", callback: (value: unknown) => void): void;
};

type AnalyticsWindow = Window & {
  gtag?: AnalyticsGtag;
  officialMailGoogleAnalyticsReady?: string;
};

function hashToUnsignedInteger(value: string, seed = FNV_OFFSET_BASIS) {
  let hash = seed >>> 0;

  for (let index = 0; index < value.length; index += 1) {
    hash ^= value.charCodeAt(index);
    hash = Math.imul(hash, FNV_PRIME) >>> 0;
  }

  return hash || 1;
}

export function toGoogleAnalyticsClientId(
  visitorId: string | null | undefined,
  fallbackUserId?: number,
) {
  const source =
    visitorId?.trim().toLowerCase() ||
    `registered:${Number.isSafeInteger(fallbackUserId) ? fallbackUserId : 0}`;

  return `${hashToUnsignedInteger(source)}.${hashToUnsignedInteger(`officialmail:${source}`)}`;
}

export function normalizeGoogleAnalyticsClientId(value: unknown) {
  const normalized = typeof value === "string" ? value.trim() : "";
  return /^\d{1,20}\.\d{1,20}$/.test(normalized) ? normalized : null;
}

export function normalizeGoogleAnalyticsSessionId(value: unknown) {
  const normalized =
    typeof value === "number" && Number.isSafeInteger(value)
      ? String(value)
      : typeof value === "string"
        ? value.trim()
        : "";
  return /^\d{1,20}$/.test(normalized) ? normalized : null;
}

export function markGoogleAnalyticsReady(measurementId: string) {
  if (typeof window === "undefined") return;
  const analyticsWindow = window as unknown as AnalyticsWindow;
  analyticsWindow.officialMailGoogleAnalyticsReady = measurementId;
  window.dispatchEvent(new Event(GOOGLE_ANALYTICS_READY_EVENT));
}

export async function getGoogleAnalyticsIdentifiers(
  measurementId: string,
  timeoutMs = 2_000,
) {
  if (typeof window === "undefined" || !/^G-[A-Z0-9]+$/i.test(measurementId)) {
    return { clientId: null, sessionId: null };
  }

  const analyticsWindow = window as unknown as AnalyticsWindow;

  return await new Promise<{ clientId: string | null; sessionId: string | null }>((resolve) => {
    let clientId: string | null = null;
    let sessionId: string | null = null;
    let finished = false;

    const finish = () => {
      if (finished) return;
      finished = true;
      window.removeEventListener(GOOGLE_ANALYTICS_READY_EVENT, readIdentifiers);
      window.clearTimeout(timeout);
      resolve({ clientId, sessionId });
    };
    const accept = (field: "client" | "session", value: unknown) => {
      if (field === "client") clientId = normalizeGoogleAnalyticsClientId(value);
      else sessionId = normalizeGoogleAnalyticsSessionId(value);
      if (clientId && sessionId) finish();
    };
    const readIdentifiers = () => {
      if (
        analyticsWindow.officialMailGoogleAnalyticsReady !== measurementId ||
        !analyticsWindow.gtag
      ) {
        return;
      }
      analyticsWindow.gtag("get", measurementId, "client_id", (value) => accept("client", value));
      analyticsWindow.gtag("get", measurementId, "session_id", (value) => accept("session", value));
    };

    const timeout = window.setTimeout(finish, timeoutMs);
    window.addEventListener(GOOGLE_ANALYTICS_READY_EVENT, readIdentifiers);
    readIdentifiers();
  });
}
