// auth.jsx — Supabase auth client + session hook + authedFetch helper.
//
// Boot order:
//   1. App.jsx calls window.CoachAuth.boot() before first render.
//   2. boot() fetches /api/config to get the public Supabase URL + key,
//      then constructs the supabase client and stores it on window.CoachAuth.
//   3. useAuth() subscribes to auth state changes and re-renders on
//      sign-in / sign-out.
//   4. authedFetch(path, options) attaches the current access_token as a
//      Bearer header. Every /api/* call goes through this.

const { useEffect, useState, useCallback } = React;

let _client = null;
let _readyPromise = null;

async function boot() {
  if (_readyPromise) return _readyPromise;
  _readyPromise = (async () => {
    const r = await fetch('/api/config');
    if (!r.ok) throw new Error('config fetch failed: ' + r.status);
    const cfg = await r.json();
    const { createClient } = window.supabase;
    _client = createClient(cfg.supabase_url, cfg.supabase_publishable_key, {
      auth: {
        // Persist session in localStorage; keep alive across reloads.
        persistSession: true,
        autoRefreshToken: true,
        // True so the email-confirmation redirect (returns the user to
        // `<APP>/#access_token=…&type=signup`) auto-establishes the session.
        // Supabase JS strips the hash after consuming it.
        detectSessionInUrl: true,
      },
    });
    return _client;
  })();
  return _readyPromise;
}

function getClient() {
  if (!_client) throw new Error('CoachAuth not booted yet');
  return _client;
}

// React hook — re-renders on sign-in / sign-out / token refresh.
function useAuth() {
  const [state, setState] = useState({ loading: true, session: null });

  useEffect(() => {
    let mounted = true;
    const client = getClient();
    client.auth.getSession().then(({ data }) => {
      if (mounted) setState({ loading: false, session: data.session });
    });
    const { data: sub } = client.auth.onAuthStateChange((_event, session) => {
      if (mounted) setState({ loading: false, session });
    });
    return () => {
      mounted = false;
      sub.subscription.unsubscribe();
    };
  }, []);

  return state;
}

async function signIn(email, password) {
  const { error } = await getClient().auth.signInWithPassword({ email, password });
  if (error) throw error;
}

async function signUp(email, password) {
  const { data, error } = await getClient().auth.signUp({ email, password });
  if (error) throw error;
  return data;
}

async function signOut() {
  await getClient().auth.signOut();
}

// Wraps fetch — adds Authorization: Bearer <jwt> if we have a session.
// Returns parsed JSON on 2xx; throws an Error with the server's detail on
// 4xx/5xx so callers don't need to write try/catch ladders.
async function authedFetch(path, options = {}) {
  const client = getClient();
  const { data } = await client.auth.getSession();
  const token = data.session?.access_token;
  const headers = new Headers(options.headers || {});
  if (token) headers.set('Authorization', 'Bearer ' + token);
  if (options.body && !headers.has('Content-Type')) {
    headers.set('Content-Type', 'application/json');
  }
  const r = await fetch(path, { ...options, headers });
  let body = null;
  try { body = await r.json(); } catch (_e) { /* not json */ }
  if (!r.ok) {
    const msg = body?.detail?.detail || body?.detail || body?.error || ('http_' + r.status);
    const err = new Error(typeof msg === 'string' ? msg : JSON.stringify(msg));
    err.status = r.status;
    err.body = body;
    throw err;
  }
  return body;
}

window.CoachAuth = { boot, getClient, useAuth, signIn, signUp, signOut, authedFetch };
