// Cinematon web data layer. Loaded as a global script before the screens so
// they can call window.CinematonAPI.* without ESM imports (the prototype runs
// straight in the browser via Babel-standalone).

(function () {
  const API_BASE = (typeof window !== 'undefined' && window.__CINEMATON_API__) || '/api';
  const CREDS    = { credentials: 'include' };

  async function req(path, opts = {}) {
    const res = await fetch(`${API_BASE}${path}`, {
      ...CREDS,
      ...opts,
      headers: { 'Content-Type': 'application/json', ...(opts.headers ?? {}) },
    });
    if (!res.ok) {
      let body;
      try { body = await res.json(); } catch { body = { error: res.statusText }; }
      const err = new Error(body.error || `HTTP ${res.status}`);
      err.status = res.status;
      err.body = body;
      throw err;
    }
    if (res.status === 204) return null;
    return res.json();
  }

  const API = {
    // system
    health()         { return req('/health'); },
    plugins()        { return req('/plugins'); },

    // projects
    listProjects()   { return req('/projects'); },
    runPipeline(input) { return req('/pipeline/run', { method: 'POST', body: JSON.stringify(input) }); },
    getRun(projectId)  { return req(`/projects/${projectId}/run`); },

    // edits — prompts
    patchPrompt(shotId, packet) {
      return req(`/prompts/${shotId}`, { method: 'PATCH', body: JSON.stringify({ packet }) });
    },

    // project lifecycle
    patchProject(projectId, patch) {
      return req(`/projects/${projectId}`, { method: 'PATCH', body: JSON.stringify(patch) });
    },
    deleteProject(projectId) {
      return req(`/projects/${projectId}`, { method: 'DELETE' });
    },

    // characters
    addCharacter(projectId, character) {
      return req(`/projects/${projectId}/characters`, { method: 'POST', body: JSON.stringify(character) });
    },
    patchCharacter(charId, patch) {
      return req(`/characters/${charId}`, { method: 'PATCH', body: JSON.stringify(patch) });
    },
    deleteCharacter(charId) {
      return req(`/characters/${charId}`, { method: 'DELETE' });
    },
    regenCharacterReference(charId) {
      return req(`/characters/${charId}/reference`, { method: 'POST' });
    },
    cloneCharacterVoice(charId, files) {
      const form = new FormData();
      (Array.isArray(files) ? files : [files]).forEach((f, i) => form.append(`sample_${i}`, f, f.name));
      return fetch(`${API_BASE}/characters/${charId}/voice`, {
        ...CREDS, method: 'POST', body: form,
      }).then(async (res) => {
        if (!res.ok) {
          let body; try { body = await res.json(); } catch { body = { error: res.statusText }; }
          const err = new Error(body.error || `HTTP ${res.status}`); err.status = res.status; err.body = body; throw err;
        }
        return res.json();
      });
    },
    assetFileUrl(assetId) {
      return `${API_BASE}/assets/${assetId}/file`;
    },

    // scenes
    addScene(projectId, scene) {
      return req(`/projects/${projectId}/scenes`, { method: 'POST', body: JSON.stringify(scene) });
    },
    patchScene(sceneId, patch) {
      return req(`/scenes/${sceneId}`, { method: 'PATCH', body: JSON.stringify(patch) });
    },
    deleteScene(sceneId) {
      return req(`/scenes/${sceneId}`, { method: 'DELETE' });
    },
    regenScene(sceneId) {
      return req(`/scenes/${sceneId}/regen`, { method: 'POST' });
    },

    // shots
    addShot(projectId, shot) {
      return req(`/projects/${projectId}/shots`, { method: 'POST', body: JSON.stringify(shot) });
    },
    patchShot(shotId, patch) {
      return req(`/shots/${shotId}`, { method: 'PATCH', body: JSON.stringify(patch) });
    },
    deleteShot(shotId) {
      return req(`/shots/${shotId}`, { method: 'DELETE' });
    },
    regenShotPrompt(shotId) {
      return req(`/shots/${shotId}/regen-prompt`, { method: 'POST' });
    },

    // screenplay + dialogue
    patchScreenplayScene(sceneId, patch) {
      return req(`/screenplay-scenes/${sceneId}`, { method: 'PATCH', body: JSON.stringify(patch) });
    },
    patchDialogueLine(lineId, patch) {
      return req(`/dialogue-lines/${lineId}`, { method: 'PATCH', body: JSON.stringify(patch) });
    },

    // threat (singleton per project)
    patchThreat(threatId, patch) {
      return req(`/threats/${threatId}`, { method: 'PATCH', body: JSON.stringify(patch) });
    },

    // locations
    addLocation(projectId, location) {
      return req(`/projects/${projectId}/locations`, { method: 'POST', body: JSON.stringify(location) });
    },
    patchLocation(locationId, patch) {
      return req(`/locations/${locationId}`, { method: 'PATCH', body: JSON.stringify(patch) });
    },
    deleteLocation(locationId) {
      return req(`/locations/${locationId}`, { method: 'DELETE' });
    },
    regenLocationReference(locationId) {
      return req(`/locations/${locationId}/reference`, { method: 'POST' });
    },

    // mini-bible (singleton per project)
    patchBible(projectId, patch) {
      return req(`/projects/${projectId}/bible`, { method: 'PATCH', body: JSON.stringify(patch) });
    },

    // audio cues — music
    addMusicCue(sceneId, cue) {
      return req(`/scenes/${sceneId}/music-cues`, { method: 'POST', body: JSON.stringify(cue) });
    },
    patchMusicCue(cueId, patch) {
      return req(`/music-cues/${cueId}`, { method: 'PATCH', body: JSON.stringify(patch) });
    },
    deleteMusicCue(cueId) {
      return req(`/music-cues/${cueId}`, { method: 'DELETE' });
    },

    // audio cues — sfx
    addSFXCue(sceneId, cue) {
      return req(`/scenes/${sceneId}/sfx-cues`, { method: 'POST', body: JSON.stringify(cue) });
    },
    patchSFXCue(cueId, patch) {
      return req(`/sfx-cues/${cueId}`, { method: 'PATCH', body: JSON.stringify(patch) });
    },
    deleteSFXCue(cueId) {
      return req(`/sfx-cues/${cueId}`, { method: 'DELETE' });
    },

    // audio cues — ambience
    addAmbienceCue(sceneId, cue) {
      return req(`/scenes/${sceneId}/ambience-cues`, { method: 'POST', body: JSON.stringify(cue) });
    },
    patchAmbienceCue(cueId, patch) {
      return req(`/ambience-cues/${cueId}`, { method: 'PATCH', body: JSON.stringify(patch) });
    },
    deleteAmbienceCue(cueId) {
      return req(`/ambience-cues/${cueId}`, { method: 'DELETE' });
    },

    // approval
    approve(projectId, body) {
      return req(`/projects/${projectId}/approve`, { method: 'POST', body: JSON.stringify(body) });
    },
    listApprovals(projectId) { return req(`/projects/${projectId}/approvals`); },

    // batches / status
    batchStatus(batchId) { return req(`/batches/${batchId}/status`); },
    cancelJob(jobId)     { return req(`/jobs/${jobId}/cancel`,    { method: 'POST' }); },
    resubmitJob(jobId, packet) {
      return req(`/jobs/${jobId}/resubmit`, { method: 'POST', body: JSON.stringify({ packet }) });
    },

    // assets / timeline / export
    listAssets(projectId, filters) {
      const qs = new URLSearchParams(filters || {}).toString();
      return req(`/projects/${projectId}/assets${qs ? `?${qs}` : ''}`);
    },
    timeline(projectId) { return req(`/projects/${projectId}/timeline`); },
    exportProject(projectId) {
      return req(`/projects/${projectId}/export`, { method: 'POST' });
    },
    muxFinal(projectId) {
      return req(`/projects/${projectId}/mux`, { method: 'POST' });
    },
    generateSubtitles(projectId) {
      return req(`/projects/${projectId}/subtitles`, { method: 'POST' });
    },

    // cost
    costRollup(projectId) { return req(`/projects/${projectId}/cost-rollup`); },

    // ack text constants (must match server-side enforcement)
    ACK_FULL:    'I have reviewed the prompts, rights warnings, and estimated render cost.',
    ACK_PREVIEW: 'I have reviewed the preview cost.',
  };

  // Polling helper: drives the Render screen.
  API.pollBatch = function (batchId, opts = {}) {
    const intervalMs = opts.intervalMs ?? 1500;
    const onUpdate   = opts.onUpdate   ?? (() => {});
    const isDone     = opts.isDone     ?? ((jobs) => jobs.every((j) => ['complete','failed','cancelled'].includes(j.status)));
    let cancelled = false;
    (async () => {
      while (!cancelled) {
        try {
          const { jobs } = await API.batchStatus(batchId);
          onUpdate(jobs);
          if (isDone(jobs)) return;
        } catch (err) {
          onUpdate(null, err);
        }
        await new Promise((r) => setTimeout(r, intervalMs));
      }
    })();
    return () => { cancelled = true; };
  };

  // React hook helpers (no JSX dependencies — uses globals React).
  API.useApi = function useApi(fn, deps) {
    const [state, setState] = React.useState({ loading: true, error: null, data: null });
    React.useEffect(() => {
      let cancelled = false;
      setState({ loading: true, error: null, data: null });
      Promise.resolve()
        .then(fn)
        .then((data) => { if (!cancelled) setState({ loading: false, error: null, data }); })
        .catch((err) => { if (!cancelled) setState({ loading: false, error: err, data: null }); });
      return () => { cancelled = true; };
    }, deps || []);
    return state;
  };

  // Live mode flag — when API is reachable, screens should swap fixtures for
  // real fetches. Surfaces in the titlebar.
  API.live = false;
  API.health()
    .then(() => { API.live = true; window.dispatchEvent(new CustomEvent('cinematon:live', { detail: { live: true } })); })
    .catch(() => { API.live = false; });

  window.CinematonAPI = API;
})();
