// Shared API client used by every store and the signup form.
//
// Base URL resolution (in order):
//   1. window.SWTD_API_URL                    — explicit override
//   2. localhost / 127.0.0.1                  — http://localhost:8787 (dev)
//   3. anywhere else                          — https://api.<frontend-host>
// So when the page is served from swtrainingday.com, the API resolves to
// https://api.swtrainingday.com automatically. No build step needed.
//
// Admin token lives in sessionStorage under SWTD_TOKEN_KEY. AdminGate sets
// it after a successful validation request; clearAdminToken() wipes it on
// logout. Admin endpoints automatically include `Authorization: Bearer <t>`.
//
// Errors thrown by these helpers carry { code, message, status, fields? } so
// callers can render field-level messages without re-parsing.

// Production API base URL. Used as the fallback for *.pages.dev origins
// (preview deploys + the default Pages URL) where api.<host> isn't a real
// hostname. The custom domain on the Worker auto-provisioned in Cloudflare
// when we deployed with [[routes]] custom_domain = true in wrangler.toml.
const PRODUCTION_API_URL = 'https://api.swtrainingday.com';

function _resolveApiBaseUrl() {
  if (typeof window === 'undefined') return 'http://localhost:8787';
  if (window.SWTD_API_URL) return window.SWTD_API_URL;
  const host = window.location.hostname;
  if (host === 'localhost' || host === '127.0.0.1' || host.endsWith('.local')) {
    return 'http://localhost:8787';
  }
  // Cloudflare Pages preview/branch URLs: there's no DNS for api.<host>, so
  // fall back to the workers.dev origin.
  if (host.endsWith('.pages.dev')) {
    return PRODUCTION_API_URL;
  }
  // Custom domain: derive from frontend host. Strip any leading "www." so
  // www.swtrainingday.com still hits api.swtrainingday.com.
  const apex = host.replace(/^www\./, '');
  return `${window.location.protocol}//api.${apex}`;
}

const API_BASE_URL = _resolveApiBaseUrl();
const SWTD_TOKEN_KEY = 'swtd.admin.token.v1';

class ApiError extends Error {
  constructor(message, opts = {}) {
    super(message);
    this.code = opts.code || 'UNKNOWN';
    this.status = opts.status || 0;
    this.fields = opts.fields;
  }
}

function getAdminToken() {
  try { return sessionStorage.getItem(SWTD_TOKEN_KEY) || ''; } catch (_) { return ''; }
}
function setAdminToken(token) {
  try { sessionStorage.setItem(SWTD_TOKEN_KEY, token); } catch (_) {}
}
function clearAdminToken() {
  try { sessionStorage.removeItem(SWTD_TOKEN_KEY); } catch (_) {}
}
function hasAdminToken() {
  return getAdminToken().length > 0;
}

// Build common headers — Authorization for /admin/* paths, JSON content type
// when we have a body.
function buildHeaders(path, hasBody) {
  const h = {};
  if (hasBody) h['Content-Type'] = 'application/json';
  if (path.includes('/admin/')) {
    const t = getAdminToken();
    if (t) h.Authorization = `Bearer ${t}`;
  }
  return h;
}

async function request(method, path, body) {
  const url = API_BASE_URL + (path.startsWith('/') ? path : '/' + path);
  const init = {
    method,
    headers: buildHeaders(path, body !== undefined && !(body instanceof FormData)),
  };
  if (body !== undefined) {
    init.body = body instanceof FormData ? body : JSON.stringify(body);
  }

  let res;
  try {
    res = await fetch(url, init);
  } catch (e) {
    // Network error — backend not running, CORS misconfig, offline.
    throw new ApiError(`Cannot reach API at ${API_BASE_URL}. Is the backend running?`, {
      code: 'NETWORK', status: 0,
    });
  }

  // 204 / no-body responses.
  if (res.status === 204) return null;

  let data = null;
  try { data = await res.json(); } catch (_) { /* non-JSON or empty */ }

  if (!res.ok) {
    const err = data || {};
    throw new ApiError(err.error || `HTTP ${res.status}`, {
      code: err.code || `HTTP_${res.status}`,
      status: res.status,
      fields: err.fields,
    });
  }
  return data;
}

const apiGet    = (path) => request('GET', path);
const apiPost   = (path, body) => request('POST', path, body ?? {});
const apiPatch  = (path, body) => request('PATCH', path, body ?? {});
const apiPut    = (path, body) => request('PUT', path, body ?? {});
const apiDelete = (path) => request('DELETE', path);

// Multipart upload — pass a File and optional display name.
async function apiUpload(path, file, displayName) {
  const fd = new FormData();
  fd.append('file', file);
  if (displayName) fd.append('name', displayName);
  return request('POST', path, fd);
}
async function apiUploadPut(path, file) {
  const fd = new FormData();
  fd.append('file', file);
  return request('PUT', path, fd);
}

// Validate a candidate token by hitting an admin-only endpoint. Returns true
// on 200, false on 401/403, throws on network errors.
async function validateAdminToken(token) {
  const prev = getAdminToken();
  setAdminToken(token);
  try {
    await apiGet('/api/admin/roster');
    return true;
  } catch (e) {
    if (e.status === 401 || e.status === 403) {
      // Restore the previous token (could be empty) so a bad attempt doesn't
      // log the user out.
      if (prev) setAdminToken(prev); else clearAdminToken();
      return false;
    }
    if (prev) setAdminToken(prev); else clearAdminToken();
    throw e;
  }
}

window.api = {
  API_BASE_URL,
  ApiError,
  apiGet, apiPost, apiPatch, apiPut, apiDelete,
  apiUpload, apiUploadPut,
  getAdminToken, setAdminToken, clearAdminToken, hasAdminToken,
  validateAdminToken,
};
