import { createBrowserClient } from "@supabase/ssr";
import type { SupabaseClient, User } from "@supabase/supabase-js";

let browserClient: SupabaseClient | null = null;
let authTrackingInitialized = false;
let browserUserCache: User | null | undefined;
let browserUserCacheAt = 0;
let browserUserPromise: Promise<User | null> | null = null;

const USER_CACHE_TTL_MS = 10_000;

function wait(ms: number) {
  return new Promise((resolve) => window.setTimeout(resolve, ms));
}

async function fetchUserWithRetry(client: SupabaseClient, retries = 1) {
  for (let attempt = 0; attempt <= retries; attempt += 1) {
    const { data, error } = await client.auth.getUser();

    if (!error) {
      return data.user ?? null;
    }

    const isLockError =
      error.name === "AbortError" &&
      /Lock was stolen by another request/i.test(error.message);

    if (isLockError && attempt < retries) {
      await wait(50);
      continue;
    }

    throw error;
  }

  return null;
}

function ensureAuthTracking(client: SupabaseClient) {
  if (authTrackingInitialized) return;

  authTrackingInitialized = true;

  client.auth.onAuthStateChange((_event, session) => {
    browserUserCache = session?.user ?? null;
    browserUserCacheAt = Date.now();
    browserUserPromise = null;
  });
}

export function createClient() {
  if (browserClient) {
    return browserClient;
  }

  browserClient = createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
  ensureAuthTracking(browserClient);

  return browserClient;
}

export async function getBrowserUser(options?: { force?: boolean }) {
  const client = createClient();
  const force = options?.force ?? false;
  const hasFreshCache =
    browserUserCache !== undefined &&
    Date.now() - browserUserCacheAt < USER_CACHE_TTL_MS;

  if (!force && hasFreshCache) {
    return browserUserCache;
  }

  if (!force && browserUserPromise) {
    return browserUserPromise;
  }

  browserUserPromise = fetchUserWithRetry(client, 1)
    .then((user) => {
      browserUserCache = user;
      browserUserCacheAt = Date.now();
      return user;
    })
    .finally(() => {
      browserUserPromise = null;
    });

  return browserUserPromise;
}

export async function getBrowserUserId() {
  const user = await getBrowserUser();
  return user?.id ?? null;
}
