// ExitOS AI adapter.
// One function · aiComplete(prompt, state) · used by every AI feature.
// Routes calls through (in priority order):
//   1) state.settings.byokAnthropicKey  →  direct Anthropic call (user's key)
//   2) window.EXOS_AI_ENDPOINT          →  your Cloudflare Worker (production)
//   3) window.claude.complete           →  free Claude artifact runtime (dev only)
//
// Adds:
//   - 24-hour prompt-hash cache (same plan → free re-renders)
//   - daily rate limit (resets at UTC midnight), tier-aware
//   - usage counter persisted in localStorage
//   - friendly error states

const AI_USAGE_KEY = "exitos:ai-usage";
const AI_CACHE_KEY = "exitos:ai-cache";
const AI_CACHE_TTL_MS = 24 * 60 * 60 * 1000;

const DEFAULT_LIMITS = {
  lite: 5,    // Etsy Lite tier
  pro:  30,   // Etsy AI tier (license code unlocked)
  byok: 999,  // user brought their own Anthropic key · no app-side cap
};

// ----- usage tracking -----
function todayKey() {
  const d = new Date();
  return `${d.getUTCFullYear()}-${String(d.getUTCMonth()+1).padStart(2,"0")}-${String(d.getUTCDate()).padStart(2,"0")}`;
}
function getAiUsage() {
  try {
    const raw = localStorage.getItem(AI_USAGE_KEY);
    if (!raw) return { date: todayKey(), count: 0 };
    const p = JSON.parse(raw);
    if (p.date !== todayKey()) return { date: todayKey(), count: 0 };
    return p;
  } catch { return { date: todayKey(), count: 0 }; }
}
function bumpAiUsage() {
  const u = getAiUsage();
  u.count += 1;
  try { localStorage.setItem(AI_USAGE_KEY, JSON.stringify(u)); } catch {}
  // notify any subscribers
  window.dispatchEvent(new CustomEvent("exos-ai-usage-changed", { detail: u }));
  return u;
}
function getAiTier(state) {
  if (state?.settings?.byokAnthropicKey) return "byok";
  if (state?.settings?.aiTier === "pro") return "pro";
  return "lite";
}
function getAiLimit(state) {
  return DEFAULT_LIMITS[getAiTier(state)] ?? DEFAULT_LIMITS.lite;
}
function aiUsageSummary(state) {
  const u = getAiUsage();
  const limit = getAiLimit(state);
  const tier = getAiTier(state);
  return { used: u.count, limit, tier, remaining: Math.max(0, limit - u.count) };
}

// ----- cache -----
function hashStr(s) {
  let h = 0;
  for (let i = 0; i < s.length; i++) { h = ((h << 5) - h) + s.charCodeAt(i); h |= 0; }
  return Math.abs(h).toString(36);
}
function getAiCache() {
  try { return JSON.parse(localStorage.getItem(AI_CACHE_KEY) || "{}"); }
  catch { return {}; }
}
function setAiCache(c) {
  try { localStorage.setItem(AI_CACHE_KEY, JSON.stringify(c)); } catch {}
}
function cacheGet(promptHash) {
  const c = getAiCache();
  const entry = c[promptHash];
  if (!entry) return null;
  if (Date.now() - entry.ts > AI_CACHE_TTL_MS) return null;
  return entry.text;
}
function cacheSet(promptHash, text) {
  const c = getAiCache();
  c[promptHash] = { ts: Date.now(), text };
  // keep cache reasonable (last 30 entries)
  const keys = Object.keys(c).sort((a, b) => c[b].ts - c[a].ts);
  if (keys.length > 30) for (const k of keys.slice(30)) delete c[k];
  setAiCache(c);
}

// ----- providers -----
async function callBYOKAnthropic(prompt, apiKey) {
  // Direct fetch from the browser · Anthropic supports CORS with the
  // anthropic-dangerous-direct-browser-access header.
  const res = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "x-api-key": apiKey,
      "anthropic-version": "2023-06-01",
      "content-type": "application/json",
      "anthropic-dangerous-direct-browser-access": "true",
    },
    body: JSON.stringify({
      model: "claude-haiku-4-5",
      max_tokens: 400,
      messages: [{ role: "user", content: prompt }],
    }),
  });
  if (!res.ok) {
    const errText = await res.text().catch(() => "");
    throw new Error(`Anthropic ${res.status}: ${errText.slice(0,120) || "request failed"}`);
  }
  const data = await res.json();
  return data.content?.[0]?.text || "";
}

async function callWorker(prompt, endpoint) {
  const res = await fetch(endpoint, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ prompt, model: "claude-haiku-4-5", max_tokens: 400 }),
  });
  if (!res.ok) {
    const t = await res.text().catch(() => "");
    throw new Error(`Worker ${res.status}: ${t.slice(0,120) || "failed"}`);
  }
  const data = await res.json();
  return data.text || data.content?.[0]?.text || "";
}

// ----- main -----
async function aiComplete(prompt, state, opts = {}) {
  const h = hashStr(prompt);

  // 1) cache hit · free, always allowed
  if (!opts.bypassCache) {
    const cached = cacheGet(h);
    if (cached) return { text: cached, fromCache: true, usage: aiUsageSummary(state) };
  }

  // 2) rate limit (skip for BYOK)
  const tier = getAiTier(state);
  if (tier !== "byok") {
    const usage = getAiUsage();
    const limit = getAiLimit(state);
    if (usage.count >= limit) {
      const err = new Error(`Daily AI limit reached (${limit}/day). Resets at UTC midnight.`);
      err.code = "RATE_LIMIT";
      throw err;
    }
  }

  // 3) dispatch
  let text;
  if (state?.settings?.byokAnthropicKey) {
    text = await callBYOKAnthropic(prompt, state.settings.byokAnthropicKey);
  } else if (window.EXOS_AI_ENDPOINT) {
    text = await callWorker(prompt, window.EXOS_AI_ENDPOINT);
  } else if (typeof window.claude !== "undefined" && typeof window.claude.complete === "function") {
    text = await window.claude.complete(prompt);
  } else {
    const err = new Error("AI isn't configured for this build. Add a Worker URL or your Anthropic key in Settings.");
    err.code = "NO_PROVIDER";
    throw err;
  }

  text = (text || "").trim();
  cacheSet(h, text);
  if (tier !== "byok") bumpAiUsage();
  return { text, fromCache: false, usage: aiUsageSummary(state) };
}

// React hook · subscribe to usage changes for live counters
function useAiUsage() {
  const { state } = useStore();
  const [usage, setUsage] = React.useState(() => aiUsageSummary(state));
  React.useEffect(() => {
    // Sync immediately when tier/key changes, then keep listening for runtime usage events.
    setUsage(aiUsageSummary(state));
    function onChange() { setUsage(aiUsageSummary(state)); }
    window.addEventListener("exos-ai-usage-changed", onChange);
    // refresh on midnight rollover
    const id = setInterval(() => setUsage(aiUsageSummary(state)), 60000);
    return () => { window.removeEventListener("exos-ai-usage-changed", onChange); clearInterval(id); };
  }, [state?.settings?.aiTier, state?.settings?.byokAnthropicKey]);
  return usage;
}

// License code unlock · simple obfuscated check.
// SOURCE OF TRUTH: licenses.csv at project root. Regenerate this list when CSV changes.
// Batch 01 · 100 sale codes + 10 founder (EXOS-FNDR-*) generated 2026-05-25.
const VALID_LICENSE_CODES = new Set([
  "EXOS-2WD7-ZZQH",
  "EXOS-36XS-ZRDX",
  "EXOS-464W-EZAB",
  "EXOS-48KH-KC6K",
  "EXOS-4D5J-98FM",
  "EXOS-4N56-Q3HP",
  "EXOS-54AU-GR2S",
  "EXOS-55AF-3SND",
  "EXOS-58Q5-LURV",
  "EXOS-5D54-ZSB6",
  "EXOS-5EZ7-DHBJ",
  "EXOS-5FQC-PLZ5",
  "EXOS-5JFE-34KZ",
  "EXOS-5KMU-CE79",
  "EXOS-5UA9-E5F8",
  "EXOS-5VWV-6DEN",
  "EXOS-6ALF-YS2K",
  "EXOS-6GAE-UTN3",
  "EXOS-6HEW-YG9X",
  "EXOS-7CRV-ZAWJ",
  "EXOS-7GGU-TE3K",
  "EXOS-83VW-BXSY",
  "EXOS-84FH-4J2P",
  "EXOS-86JX-PXKV",
  "EXOS-8BVQ-DA97",
  "EXOS-8CKE-74DX",
  "EXOS-8EEL-HZY2",
  "EXOS-8GBT-FJQD",
  "EXOS-8GLB-AWU9",
  "EXOS-98GF-YAZR",
  "EXOS-9ZJW-Y9MV",
  "EXOS-9ZNS-RZVN",
  "EXOS-AMD6-6TY7",
  "EXOS-AV3Q-G7LR",
  "EXOS-AYXU-G9QD",
  "EXOS-B98K-4ZXJ",
  "EXOS-BC5G-LRA4",
  "EXOS-BF93-ED8K",
  "EXOS-BQTF-LH92",
  "EXOS-BR9D-SGLW",
  "EXOS-C3E5-4QU3",
  "EXOS-C4H6-LNGL",
  "EXOS-C4Q5-Q3AK",
  "EXOS-C6FV-QP5A",
  "EXOS-CMFD-XCG6",
  "EXOS-DK2C-LN8X",
  "EXOS-DMX6-7XXB",
  "EXOS-DWTZ-9CJ5",
  "EXOS-EKZK-ALAL",
  "EXOS-EQH3-NAXY",
  "EXOS-ES3F-86NK",
  "EXOS-F4CT-LLMK",
  "EXOS-FBPZ-FFDZ",
  "EXOS-FUES-L2RF",
  "EXOS-FYDG-NRKF",
  "EXOS-G867-2ZU6",
  "EXOS-HAKF-CSPR",
  "EXOS-HDBY-STWS",
  "EXOS-HK6P-LLBH",
  "EXOS-HMR8-LGQM",
  "EXOS-HNPR-FLKM",
  "EXOS-HPMC-D8G4",
  "EXOS-JGMB-JSTS",
  "EXOS-JHMJ-EP6W",
  "EXOS-KBKJ-ED44",
  "EXOS-KU8K-UHEU",
  "EXOS-LEEV-JLTK",
  "EXOS-LFSD-NVXQ",
  "EXOS-LLDP-BNCY",
  "EXOS-LX7H-3Z4B",
  "EXOS-M4EN-9CG9",
  "EXOS-M9T4-8KNE",
  "EXOS-MAMC-VSTE",
  "EXOS-MNMM-LUSB",
  "EXOS-N52Y-F4KW",
  "EXOS-NHP7-CU55",
  "EXOS-NQ3R-TV7R",
  "EXOS-PLUH-42J7",
  "EXOS-Q4HY-2ET8",
  "EXOS-Q89F-C5G3",
  "EXOS-QGMZ-5BCP",
  "EXOS-QQUU-26MK",
  "EXOS-QSL7-A7FS",
  "EXOS-QW74-8T2X",
  "EXOS-R2D4-57FN",
  "EXOS-R32Y-MZGD",
  "EXOS-R3J7-9523",
  "EXOS-R5MU-W3VN",
  "EXOS-RRVV-3W7W",
  "EXOS-RWBD-46PZ",
  "EXOS-SEMH-DMQT",
  "EXOS-T2AH-6UUJ",
  "EXOS-TTME-VZFE",
  "EXOS-TUWU-GTD8",
  "EXOS-U9WE-JSWK",
  "EXOS-W2Q5-F56K",
  "EXOS-XD7G-4676",
  "EXOS-YDKC-WEAN",
  "EXOS-YJ5N-JWTL",
  "EXOS-YZZ9-68UH",
  "EXOS-FNDR-293H",
  "EXOS-FNDR-BFSF",
  "EXOS-FNDR-C5HG",
  "EXOS-FNDR-C6EY",
  "EXOS-FNDR-JV9V",
  "EXOS-FNDR-JZ24",
  "EXOS-FNDR-L7LX",
  "EXOS-FNDR-QM75",
  "EXOS-FNDR-TEPK",
  "EXOS-FNDR-ZTPT",
]);
function isValidLicense(code) {
  if (!code) return false;
  return VALID_LICENSE_CODES.has(code.trim().toUpperCase());
}

// Is the AI option actually available right now?
// True if BYOK key, deployed Worker endpoint, or Claude artifact runtime is present.
function aiAvailable(state) {
  if (state?.settings?.byokAnthropicKey) return true;
  if (typeof window !== "undefined" && window.EXOS_AI_ENDPOINT) return true;
  if (typeof window !== "undefined" && typeof window.claude !== "undefined"
      && typeof window.claude.complete === "function") return true;
  return false;
}

Object.assign(window, {
  aiComplete, useAiUsage, aiUsageSummary, isValidLicense, aiAvailable,
});
