/* global */
// =============================================================
// API Bridge — thin fetch wrapper for story backend
// =============================================================
// Exposes window.storyAPI with methods matching the server routes.
// All methods return parsed JSON or throw with { status, error, message }.
//
// Reads window.__devMode (set by dev-mode.jsx) for:
//   .wordsPerTurn  — override words per turn on /start
//   .flavour       — override prose flavour on /start
//   .debug         — append ?debug=true to requests

function _devOpts() {
  const d = window.__devMode;
  if (!d || !d.enabled) return { query: "", wordsPerTurn: undefined, flavour: undefined };
  return {
    query: d.debug ? "?debug=true" : "",
    wordsPerTurn: d.wordsPerTurn || undefined,
    flavour: d.flavour || undefined,
  };
}

// Abort stalled requests after 90s. A hung connection used to leave the
// machine in "loading" with every button inert until a manual page reload —
// the abort instead surfaces as a retryable 503 through the normal error
// mapping. Generation worst cases (research + first turn) finish well under
// this.
function _fetchWithTimeout(url, opts) {
  const canTimeout = typeof AbortSignal !== "undefined" && !!AbortSignal.timeout;
  return fetch(url, canTimeout ? { ...opts, signal: AbortSignal.timeout(90000) } : opts)
    .catch((err) => {
      if (err && (err.name === "TimeoutError" || err.name === "AbortError")) {
        throw { status: 503, error: "timeout", retryable: true, message: "No response. Try again." };
      }
      throw err;
    });
}

function _emitDebug(endpoint, data) {
  if (data?.debug) {
    try {
      window.dispatchEvent(new CustomEvent("device:debug", {
        detail: { type: "API_RESPONSE", endpoint, debug: data.debug },
      }));
    } catch (_) {}
  }
}

window.storyAPI = {
  async start(domain, opts = {}) {
    const dev = _devOpts();
    const body = { domain, mode: 'light', ...opts };
    if (dev.wordsPerTurn) body.wordsPerTurn = dev.wordsPerTurn;
    if (dev.flavour) body.flavour = dev.flavour;

    const res = await _fetchWithTimeout(`/api/story/start${dev.query}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    });
    const data = await res.json();
    if (!res.ok) throw { status: res.status, ...data };
    _emitDebug('/start', data);
    return data;
  },

  async choice(sessionId, choiceIndex) {
    const dev = _devOpts();
    const res = await _fetchWithTimeout(`/api/story/choice${dev.query}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId, choiceIndex }),
    });
    const data = await res.json();
    if (!res.ok) throw { status: res.status, ...data };
    _emitDebug('/choice', data);
    return data;
  },

  async freedom(sessionId, freeText) {
    const dev = _devOpts();
    const res = await _fetchWithTimeout(`/api/story/freedom${dev.query}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId, freeText }),
    });
    const data = await res.json();
    if (!res.ok) throw { status: res.status, ...data };
    _emitDebug('/freedom', data);
    return data;
  },

  async invokeAlly(sessionId, ally, biography) {
    const dev = _devOpts();
    const res = await _fetchWithTimeout(`/api/story/invoke-ally${dev.query}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId, ally, biography }),
    });
    const data = await res.json();
    if (!res.ok) throw { status: res.status, ...data };
    _emitDebug('/invoke-ally', data);
    return data;
  },

  async allyCull(sessionId, incumbent, newcomerName, biography) {
    const dev = _devOpts();
    const res = await _fetchWithTimeout(`/api/story/ally-cull${dev.query}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId, incumbent, newcomerName, biography }),
    });
    const data = await res.json();
    if (!res.ok) throw { status: res.status, ...data };
    _emitDebug('/ally-cull', data);
    return data;
  },

  async allyDecision(sessionId) {
    const dev = _devOpts();
    const res = await _fetchWithTimeout(`/api/story/ally-decision${dev.query}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId }),
    });
    const data = await res.json();
    if (!res.ok) throw { status: res.status, ...data };
    _emitDebug('/ally-decision', data);
    return data;
  },

  async biography(sessionId, currentBiography) {
    const dev = _devOpts();
    const res = await _fetchWithTimeout(`/api/story/biography${dev.query}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId, currentBiography: currentBiography || '' }),
    });
    const data = await res.json();
    if (!res.ok) throw { status: res.status, ...data };
    _emitDebug('/biography', data);
    return data;
  },

  async nextEpisodes(sessionId) {
    const dev = _devOpts();
    const res = await _fetchWithTimeout(`/api/story/next-episodes${dev.query}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sessionId }),
    });
    const data = await res.json();
    if (!res.ok) throw { status: res.status, ...data };
    _emitDebug('/next-episodes', data);
    return data;
  },

  // Fire-and-forget; never throws, never awaited.
  analytics(payload) {
    try {
      fetch('/api/story/analytics', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
      }).catch(() => {});
    } catch (_) {}
  },
};
