// web/landing/api-client.jsx — single-source-of-truth for the landing
// page's worldserver API calls.
//
// Role: thin wrapper around fetch() that auto-injects the Firebase ID
//       token in the Authorization header, retries once on 401 with a
//       force-refreshed token (handles the "token JUST expired between
//       getIdToken and the request landing" race), and points at the
//       right backend hostname based on whether we're running locally
//       or against the live deploy. Single point where every authenticated
//       /api/* call from the landing page funnels through.
//
// Callers: any .jsx in web/landing/ that needs to hit the worldserver.
//       First real consumer lands with F6 (auth-required endpoints —
//       /api/me, /api/hex/:h3 etc.). Until then, the helper sits ready
//       so future code just imports apiFetch and never touches token
//       plumbing.
//
// Runtime deps: window.__firebase (initialised by index.html's inline
//       Firebase Web SDK init). currentUser.getIdToken() is what makes
//       the auto-refresh-before-expiry behavior work — Web SDK
//       internally checks if the token is within ~5min of `exp` and
//       fetches a fresh one if so. We trust that mechanism rather
//       than caching tokens ourselves.
//
// Contract: all functions return Promises. apiFetch returns the raw
//       Response (caller does .json()/.text() based on content-type).
//       Throws on transport-level errors (network down, DNS fail) but
//       not on HTTP errors — caller checks res.ok or res.status. Unauth
//       (no Firebase user) throws a NotSignedInError before making the
//       request.
//
// Why a separate file vs. inline in app.jsx: app.jsx is already 1100+
//       lines of UI code. The auth-and-fetch concern is independent of
//       presentation; isolating it lets future API hooks/tests touch
//       just this file. Also: when we add a wsClient.jsx for WebSocket
//       (F7/F8 territory), it'll live alongside this one, both using
//       the same getIdToken() pattern.

// API_BASE — the worldserver's public base URL. Hardcoded today because
// the landing page is a no-build static site (no env.NODE_ENV at build
// time). When localhost dev becomes a thing, switch this to read
// window.location.hostname or a meta tag set by build-version.sh.
const API_BASE = 'https://terraforge-api.retrohubai.com';

// NotSignedInError signals that the caller invoked apiFetch without a
// signed-in Firebase user. UI code can catch this and route to the
// SignInModal rather than showing a generic auth error.
class NotSignedInError extends Error {
  constructor(message = 'Not signed in') {
    super(message);
    this.name = 'NotSignedInError';
  }
}

// apiFetch issues an authenticated request to the worldserver.
// path: API path starting with '/' (e.g., '/api/me'). API_BASE prepended.
// options: standard fetch options (method, body, headers, etc.).
// Returns: Promise<Response>.
async function apiFetch(path, options = {}) {
  const fb = (typeof window !== 'undefined') ? window.__firebase : null;
  const user = fb && fb.auth && fb.auth.currentUser;
  if (!user) {
    throw new NotSignedInError();
  }

  // First attempt with whatever token Firebase has cached. The Web SDK's
  // getIdToken() auto-refreshes if the token is within ~5min of exp;
  // most calls just return the cached token immediately.
  const token = await user.getIdToken();
  const url = `${API_BASE}${path}`;
  const headers = {
    ...(options.headers || {}),
    Authorization: `Bearer ${token}`,
  };

  let res = await fetch(url, { ...options, headers });

  // 401 here means: the token was *just* old enough to expire between
  // getIdToken returning and the server validating it. Rare but possible.
  // Force-refresh once and retry.
  if (res.status === 401) {
    const fresh = await user.getIdToken(true); // force-refresh
    res = await fetch(url, {
      ...options,
      headers: { ...headers, Authorization: `Bearer ${fresh}` },
    });
  }

  return res;
}

// apiFetchJson is a convenience wrapper for the common case: GET, parse
// JSON response, throw on non-2xx with the response body as message.
async function apiFetchJson(path, options = {}) {
  const res = await apiFetch(path, options);
  const body = await res.text();
  if (!res.ok) {
    throw new Error(`${options.method || 'GET'} ${path}: ${res.status} — ${body.slice(0, 200)}`);
  }
  try {
    return JSON.parse(body);
  } catch (e) {
    throw new Error(`${path}: response was not JSON: ${body.slice(0, 200)}`);
  }
}

// Expose to text/babel .jsx files via globals (no module system in
// browser-Babel). app.jsx and any future component just call
// window.apiFetch(...) without importing.
window.apiFetch = apiFetch;
window.apiFetchJson = apiFetchJson;
window.NotSignedInError = NotSignedInError;
window.__API_BASE = API_BASE; // for debug/inspection in DevTools console
