// Tebex Headless API client
// Docs: https://docs.tebex.io/developers/headless-api/overview

// ========================================
// CONFIG — REPLACE BEFORE GOING LIVE
// ========================================
const TEBEX_CONFIG = {
  // Your webstore public token (Tebex Creator Panel → Integrations → Headless API)
  // This is safe to expose in frontend code.
  publicToken: "10t8d-908cfbedf2eb48f05ac845b6b9e92103edbc06ab",

  // URLs the user is returned to after payment.
  // Use window.location.origin at call time; these are just path suffixes.
  completePath: "/?checkout=success",
  cancelPath:   "/?checkout=cancel",
};

const TEBEX_BASE = "https://headless.tebex.io/api";
const lsBasket = "le_tebex_basket";

// --- low-level fetch helpers ---
// Tebex returns a Problem-Details body on errors. Surface its `detail` (and any
// field_details validation messages) in the thrown Error so the UI can show
// something more useful than "Tebex API 400".
async function tebexThrow(res) {
  let msg = `Tebex API ${res.status}`;
  try {
    const j = await res.json();
    const fd = Array.isArray(j?.field_details) && j.field_details.length
      ? " · " + j.field_details.map((f) => `${f.identifier || f.field}: ${f.message || f.detail}`).join("; ")
      : "";
    if (j?.detail) msg += ` — ${j.detail}${fd}`;
    else if (j?.title) msg += ` — ${j.title}${fd}`;
  } catch {}
  throw new Error(msg);
}

// Account-scoped endpoints (catalog, basket create/read):
async function tebexFetch(path, opts = {}) {
  const url = `${TEBEX_BASE}/accounts/${TEBEX_CONFIG.publicToken}${path}`;
  const res = await fetch(url, {
    ...opts,
    headers: { "Accept": "application/json", "Content-Type": "application/json", ...(opts.headers || {}) },
  });
  if (!res.ok) await tebexThrow(res);
  return res.json();
}
// Basket-scoped endpoints live at /api/baskets/... with NO account prefix:
async function tebexBasketFetch(path, opts = {}) {
  const url = `${TEBEX_BASE}${path}`;
  const res = await fetch(url, {
    ...opts,
    headers: { "Accept": "application/json", "Content-Type": "application/json", ...(opts.headers || {}) },
  });
  if (!res.ok) await tebexThrow(res);
  return res.json();
}

// --- catalog ---
async function tebexGetCategories() {
  // Returns all categories + packages with prices, descriptions, images
  const { data } = await tebexFetch(`/categories?includePackages=1`);
  return data;
}

// --- basket lifecycle ---
async function tebexCreateBasket() {
  const origin = window.location.origin;
  const body = {
    complete_url: origin + TEBEX_CONFIG.completePath,
    cancel_url:   origin + TEBEX_CONFIG.cancelPath,
    complete_auto_redirect: true,
  };
  const { data } = await tebexFetch(`/baskets`, { method: "POST", body: JSON.stringify(body) });
  localStorage.setItem(lsBasket, data.ident);
  return data;
}

async function tebexGetBasket(ident) {
  if (!ident) return null;
  try {
    const { data } = await tebexFetch(`/baskets/${ident}`);
    return data;
  } catch (e) {
    // basket expired or invalid — clear it
    localStorage.removeItem(lsBasket);
    return null;
  }
}

async function tebexAddPackage(ident, packageId, qty = 1, variables = null) {
  const body = { package_id: packageId, quantity: qty };
  // Tebex Headless package "options" (e.g. discord_id) are passed in via a
  // `variables` array of { identifier, option } entries when the package
  // requires them. Skip the field entirely when there are none — sending an
  // empty array sometimes upsets older webstores.
  if (variables && Object.keys(variables).length > 0) {
    body.variables = Object.entries(variables).map(([identifier, option]) => ({
      identifier,
      option: String(option),
    }));
  }
  const { data } = await tebexBasketFetch(`/baskets/${ident}/packages`, {
    method: "POST",
    body: JSON.stringify(body),
  });
  return data;
}

async function tebexRemovePackage(ident, packageId) {
  const { data } = await tebexBasketFetch(`/baskets/${ident}/packages/remove`, {
    method: "POST",
    body: JSON.stringify({ package_id: packageId }),
  });
  return data;
}

// Auth goes through our Worker:
//   /api/auth/cfx/start      — Discourse SSO bounce, attaches basket.username
//   /api/auth/discord/start  — standard Discord OAuth2 (requires active CFX session)
function tebexAuthUrl(ident, provider /* 'discord' | 'cfx' */, returnPath) {
  const ret = encodeURIComponent(returnPath || window.location.href);
  const basket = ident ? `&basket=${encodeURIComponent(ident)}` : "";
  return `/api/auth/${provider}/start?return=${ret}${basket}`;
}

// Ensure every package in `packageIds` is present in the Tebex basket.
// Tebex rejects adding packages before CFX login, so pre-login cart-adds fail
// silently; this is called at checkout (post-login) to reconcile.
async function tebexSyncBasket(ident, packageIds) {
  const basket = await tebexGetBasket(ident);
  if (!basket) throw new Error("Basket not found");
  const existing = new Set((basket.packages || []).map((p) => p.id));
  for (const pid of packageIds) {
    if (pid && !existing.has(pid)) {
      await tebexAddPackage(ident, pid, 1);
    }
  }
}

async function tebexCheckout(ident) {
  // Fetch fresh basket to get the canonical checkout link.
  const basket = await tebexGetBasket(ident);
  if (!basket || !basket.links || !basket.links.checkout) throw new Error("No checkout link");
  window.location.href = basket.links.checkout;
}

Object.assign(window, {
  TEBEX_CONFIG,
  tebexGetCategories,
  tebexCreateBasket,
  tebexGetBasket,
  tebexAddPackage,
  tebexRemovePackage,
  tebexSyncBasket,
  tebexAuthUrl,
  tebexCheckout,
});
