// Dev feedback loop. When a local dev endpoint is configured (chrome.storage.local
// `eleje_dev_endpoint`, e.g. "http://localhost:8787"), mirror all in-extension feedback to
// it so it lands as files Claude can read — no published backend needed. Completely inert
// unless that key is set, so it's safe to ship (a public build just never sets it).
const DEV_KEY = "eleje_dev_endpoint";

// The chrome.storage.local keys holding feedback the dev sink should mirror. These are the
// SAME keys the existing flows already write (timings/confirms/lyric-edits) + misdetections.
export const FEEDBACK_KEYS = ["eleje_timings", "eleje_confirms", "eleje_lyric_edits", "eleje_misdetections"];

const hasStorage = () => typeof chrome !== "undefined" && chrome.storage && chrome.storage.local;

export async function devEndpoint() {
  if (!hasStorage()) return "";
  const o = await chrome.storage.local.get(DEV_KEY);
  return o[DEV_KEY] || "";
}

async function post(endpoint, key, value) {
  try {
    await fetch(endpoint.replace(/\/+$/, "") + "/feedback", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ key, value }),
    });
    return true;
  } catch { return false; }
}

// Mirror every feedback key's CURRENT value to the dev sink. No-op unless a dev endpoint is
// set. Called (debounced) by the background storage.onChanged listener, so any change to any
// feedback type re-syncs the whole set to disk.
export async function pushAll() {
  const endpoint = await devEndpoint();
  if (!endpoint || !hasStorage()) return 0;
  const all = await chrome.storage.local.get(FEEDBACK_KEYS);
  let n = 0;
  for (const k of FEEDBACK_KEYS) {
    if (all[k] !== undefined && await post(endpoint, k, all[k])) n++;
  }
  return n;
}

// Record a misdetected / undetected tango. Appended to `eleje_misdetections`; the onChanged
// listener then mirrors it to the dev sink. `rec` = {videoId, url, verdict, detected?, note?}.
export async function reportMisdetect(rec) {
  if (!hasStorage() || !rec) return false;
  const KEY = "eleje_misdetections";
  const o = await chrome.storage.local.get(KEY);
  const list = Array.isArray(o[KEY]) ? o[KEY] : [];
  list.push({ ...rec, at: new Date().toISOString() });
  await chrome.storage.local.set({ [KEY]: list });
  return true;
}

// ---- Production submission (customer feedback → el-eje.com) --------------------------------
// ON BY DEFAULT: submits assignments / "not a tango" / misdetections / lyric edits to the owner's
// server so they can be pooled + REVIEWED (never auto-applied — the owner majority-votes offline
// and their ground truth always wins). Gated by an OPT-OUT flag (not the dev flag), anonymous
// random client id, retry-until-delivered (a content signature is recorded on success so nothing
// is re-sent; 4xx won't retry, network/5xx stay pending). Timings keep their own path (upload.js).
const PROD_ENDPOINT = "https://el-eje.com/api/contribute";
const OPTOUT_KEY = "eleje_share_optout";     // chrome.storage.local; default false ⇒ sharing ON
const CLIENT_KEY = "eleje_client";           // anonymous per-install id (shared with upload.js)
const PSENT_KEY = "eleje_prod_sent";         // {sig: 1} of contributions already delivered to prod

async function optedOut() {
  if (!hasStorage()) return true;
  return (await chrome.storage.local.get(OPTOUT_KEY))[OPTOUT_KEY] === true;
}
async function clientId() {
  const o = await chrome.storage.local.get(CLIENT_KEY);
  if (o[CLIENT_KEY]) return o[CLIENT_KEY];
  const id = "c" + Math.random().toString(36).slice(2, 12);
  await chrome.storage.local.set({ [CLIENT_KEY]: id });
  return id;
}

// Collapse a URL to a stable key (mirror of confirms.js urlKey): YouTube / youtu.be -> "yt:<id>",
// Spotify track -> "sp:<id>", else host+path. Tracking params (playlist id, &t=, &si=) and the
// fragment are dropped — the canonical link is what the lookup table needs.
export function urlKeyMin(url) {
  try {
    const u = new URL(url);
    const host = u.hostname.replace(/^www\./, "").toLowerCase();
    if (/(^|\.)youtube\.com$/.test(host)) { const v = u.searchParams.get("v"); if (v) return "yt:" + v; }
    if (host === "youtu.be") { const id = u.pathname.slice(1); if (id) return "yt:" + id; }
    if (host === "open.spotify.com") { const m = u.pathname.match(/\/track\/([A-Za-z0-9]+)/); if (m) return "sp:" + m[1]; }
    return host + u.pathname.replace(/\/+$/, "");
  } catch { return ""; }
}

// Expand a key back to the canonical page link we collect: yt:<id> -> the YouTube watch URL,
// sp:<id> -> the Spotify track URL, host+path -> https URL. (Tracking params already dropped by
// urlKeyMin, so this is the clean, stable link — disclosed in the privacy policy.)
export function urlCanon(key) {
  if (!key || typeof key !== "string") return null;
  if (key.startsWith("yt:")) return "https://www.youtube.com/watch?v=" + key.slice(3);
  if (key.startsWith("sp:")) return "https://open.spotify.com/track/" + key.slice(3);
  if (key.includes(".")) return "https://" + key;     // host+path
  return null;
}

// Pure: storage state → [{sig, payload}] in the backend's per-kind contract. Exported for tests.
// Sends the canonical page link (YouTube watch URL / site link) + videoId + the user's edits.
export function contribsFrom(all) {
  const out = [];
  const confirms = (all && all.eleje_confirms) || {};
  for (const k of Object.keys(confirms)) {
    const r = confirms[k] || {};
    const kk = r.key || k;
    const videoId = typeof kk === "string" && kk.startsWith("yt:") ? kk.slice(3) : (r.videoId || null);
    out.push({
      sig: `confirm:${kk}:${r.songId || ""}:${r.notTango ? 1 : 0}`,
      payload: { kind: "confirm", key: kk, url: urlCanon(kk), videoId, songId: r.songId || null,
        song: (r.t || r.o || r.s || r.y) ? { t: r.t || null, o: r.o || null, s: r.s || null, y: r.y || null } : null,
        notTango: !!r.notTango },
    });
  }
  const mis = Array.isArray(all && all.eleje_misdetections) ? all.eleje_misdetections : [];
  for (const r of mis) {
    const videoId = r.videoId || null;
    const key = videoId || (r.url ? urlKeyMin(r.url) : "");
    if (!key) continue;
    out.push({
      sig: `mis:${key}:${r.verdict || ""}:${(r.assigned && r.assigned.id) || ""}:${r.at || ""}`,
      payload: { kind: "misdetect", key, url: urlCanon(videoId ? "yt:" + videoId : key), videoId,
        videoTitle: r.videoTitle || null, verdict: r.verdict || null,
        detected: r.detected || null, assigned: r.assigned || null, note: r.note || null },
    });
  }
  const lyr = (all && all.eleje_lyric_edits) || {};
  for (const k of Object.keys(lyr)) {
    const e = lyr[k] || {};
    if (!e.songId) continue;
    const es = String(e.es || "").split("\n"), en = String(e.en || "").split("\n");
    const lines = [];
    for (let i = 0; i < Math.max(es.length, en.length); i++) lines.push({ i, es: es[i] || "", en: en[i] || "" });
    out.push({ sig: `lyric:${e.songId}:${e.updatedAt || ""}`,
      payload: { kind: "lyric", songId: e.songId, lines,
        by: e.by || "", grabaciones: Array.isArray(e.grabaciones) ? e.grabaciones : [] } });
  }
  return out;
}

// Submit not-yet-delivered customer feedback to production. ON unless opted out.
export async function submitProd() {
  if (!hasStorage() || await optedOut()) return 0;
  const all = await chrome.storage.local.get(["eleje_confirms", "eleje_misdetections", "eleje_lyric_edits"]);
  const contribs = contribsFrom(all);
  if (!contribs.length) return 0;
  const client = await clientId();
  const sent = (await chrome.storage.local.get(PSENT_KEY))[PSENT_KEY] || {};
  let n = 0, changed = false;
  for (const c of contribs) {
    if (sent[c.sig]) continue;
    try {
      const r = await fetch(PROD_ENDPOINT, { method: "POST", headers: { "content-type": "application/json" },
        body: JSON.stringify({ ...c.payload, client }) });
      if (r.ok) { sent[c.sig] = 1; n++; changed = true; }
      else if (r.status === 400 || r.status === 422) { sent[c.sig] = 1; changed = true; }   // malformed → don't retry
      // 404 (backend not deployed yet), 403, 5xx, network → leave PENDING and retry later
    } catch { /* network / down — leave pending, retry next time */ }
  }
  if (changed) await chrome.storage.local.set({ [PSENT_KEY]: sent });
  return n;
}
