// CoinGecko API service.
// Three sources are supported:
//   "coingecko-public"  → unauthenticated public endpoint, ~10–30 req/min (best-effort)
//   "coingecko-demo"    → your free Demo key, ~30 req/min, more reliable
//   "coingecko-pro"     → paid Pro plan, much higher limits
//
// All requests are CORS-safe · CoinGecko explicitly enables it.
// We cache search results for 30s and prices for 25s to be friendly with limits.

const CG_PUBLIC_BASE = "https://api.coingecko.com/api/v3";
const CG_PRO_BASE    = "https://pro-api.coingecko.com/api/v3";

const cgCache = {
  search: new Map(), // queryLower -> { ts, results }
  prices: new Map(), // coinId -> { ts, usd, change24h }
};

function cgBase(source) {
  return source === "coingecko-pro" ? CG_PRO_BASE : CG_PUBLIC_BASE;
}
function cgHeaders(source, key) {
  const h = { accept: "application/json" };
  if (source === "coingecko-demo" && key) h["x-cg-demo-api-key"] = key;
  if (source === "coingecko-pro"  && key) h["x-cg-pro-api-key"]  = key;
  return h;
}

function isCoinGeckoSource(s) {
  return s === "coingecko-public" || s === "coingecko-demo" || s === "coingecko-pro";
}

async function cgSearch(query, source, key) {
  if (!query || query.trim().length < 1) return [];
  const q = query.trim().toLowerCase();
  const cached = cgCache.search.get(q);
  if (cached && Date.now() - cached.ts < 30000) return cached.results;

  const url = `${cgBase(source)}/search?query=${encodeURIComponent(query)}`;
  const res = await fetch(url, { headers: cgHeaders(source, key) });
  if (!res.ok) {
    if (res.status === 429) throw new Error("CoinGecko rate limit hit. Slow down or add an API key.");
    throw new Error(`CoinGecko search failed (${res.status}).`);
  }
  const data = await res.json();
  const results = (data.coins || []).slice(0, 14).map(c => ({
    id: c.id,
    symbol: (c.symbol || "").toUpperCase(),
    name: c.name,
    image: c.thumb || c.large || c.small,
    marketCapRank: c.market_cap_rank,
  }));
  cgCache.search.set(q, { ts: Date.now(), results });
  return results;
}

// Fetch prices for a batch of coin ids. Returns map keyed by id.
async function cgFetchPrices(coinIds, source, key) {
  if (!coinIds?.length) return {};
  // Skip ids we have fresh cache for
  const fresh = {};
  const need = [];
  const now = Date.now();
  for (const id of coinIds) {
    const c = cgCache.prices.get(id);
    if (c && now - c.ts < 25000) fresh[id] = { price: c.usd, change24h: c.change24h, base: c.usd, lastPct: 0 };
    else need.push(id);
  }
  if (!need.length) return fresh;

  const ids = need.join(",");
  const url = `${cgBase(source)}/simple/price?ids=${ids}&vs_currencies=usd&include_24hr_change=true`;
  const res = await fetch(url, { headers: cgHeaders(source, key) });
  if (!res.ok) {
    if (res.status === 429) throw new Error("CoinGecko rate limit hit.");
    throw new Error(`Price fetch failed (${res.status}).`);
  }
  const data = await res.json();
  const out = { ...fresh };
  for (const id of need) {
    const row = data[id];
    if (!row) continue;
    out[id] = { price: row.usd, change24h: row.usd_24h_change ?? 0, base: row.usd, lastPct: 0 };
    cgCache.prices.set(id, { ts: Date.now(), usd: row.usd, change24h: row.usd_24h_change ?? 0 });
  }
  return out;
}

// Lightweight ping · does the key work?
async function cgTestConnection(source, key) {
  try {
    if (source === "coingecko-demo" && !key) return { ok: false, msg: "Paste your demo key first." };
    if (source === "coingecko-pro"  && !key) return { ok: false, msg: "Paste your pro key first." };
    const url = `${cgBase(source)}/ping`;
    const res = await fetch(url, { headers: cgHeaders(source, key) });
    if (!res.ok) {
      if (res.status === 401 || res.status === 403) return { ok: false, msg: "Invalid API key." };
      if (res.status === 429) return { ok: false, msg: "Rate limited · wait a minute and retry." };
      return { ok: false, msg: `Server error (${res.status}).` };
    }
    return { ok: true, msg: source === "coingecko-public" ? "Connected (public, rate-limited)." : "Connected." };
  } catch (e) {
    return { ok: false, msg: e?.message?.includes("Failed to fetch")
        ? "Network blocked. CORS or offline?"
        : (e?.message || "Network error.") };
  }
}

// Generate a fallback color from a coin id (stable hash → hue)
function colorFromId(id) {
  let h = 0;
  for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) >>> 0;
  const hue = h % 360;
  return `oklch(0.62 0.16 ${hue})`;
}

window.cgSearch = cgSearch;
window.cgFetchPrices = cgFetchPrices;
window.cgTestConnection = cgTestConnection;
window.isCoinGeckoSource = isCoinGeckoSource;
window.colorFromId = colorFromId;
