// Dragonflight API client — v2.1.4
// UXP fetch() notes:
//   • redirect:'manual' is NOT a supported option — UXP auto-follows redirects
//   • Authorization is stripped by UXP on cross-origin redirects (security fix)
//   • For same-origin API calls: add Bearer header
//   • For off-origin URLs (S3 presigned): do NOT add Bearer

(function () {
  const API = {};
  const LS_URL   = 'df.uxp.serverUrl';
  const LS_TOKEN = 'df.uxp.apiToken';

  API.state = {
    serverUrl: localStorage.getItem(LS_URL)   || '',
    apiToken:  localStorage.getItem(LS_TOKEN) || '',
    connected: false,
  };

  API.save = function () {
    localStorage.setItem(LS_URL,   API.state.serverUrl);
    localStorage.setItem(LS_TOKEN, API.state.apiToken);
  };

  API.clear = function () {
    API.state.serverUrl = '';
    API.state.apiToken  = '';
    API.state.connected = false;
    localStorage.removeItem(LS_URL);
    localStorage.removeItem(LS_TOKEN);
  };

  function trimUrl(u) { return String(u || '').replace(/\/+$/, ''); }

  // Core request — adds Bearer only for same-server URLs.
  // No redirect option — UXP handles redirects automatically.
  API.request = async function (urlOrPath, opts) {
    opts = opts || {};
    const isAbs = /^https?:\/\//i.test(urlOrPath);
    const base  = trimUrl(API.state.serverUrl);
    const url   = isAbs ? urlOrPath : (base + urlOrPath);
    const headers = Object.assign({}, opts.headers || {});
    if (!isAbs || url.startsWith(base)) {
      if (API.state.apiToken) headers['Authorization'] = 'Bearer ' + API.state.apiToken;
    }
    return fetch(url, Object.assign({}, opts, { headers }));
  };

  // Fetch an off-origin URL (e.g. presigned S3) — no auth header.
  API.requestExternal = async function (url) {
    return fetch(url);
  };

  API.json = async function (pathOrUrl, opts) {
    const r = await API.request(pathOrUrl, opts);
    if (!r.ok) {
      const text = await r.text().catch(() => '');
      throw new Error('HTTP ' + r.status + (text ? ' — ' + text.slice(0, 200) : ''));
    }
    return r.json();
  };

  // ── Auth ─────────────────────────────────────────────────────────
  API.connect = async function (serverUrl, apiToken) {
    API.state.serverUrl = trimUrl(serverUrl);
    API.state.apiToken  = String(apiToken || '').trim();
    if (!API.state.serverUrl || !API.state.apiToken) {
      throw new Error('Server URL and API token required');
    }
    const me = await API.json('/api/v1/auth/me');
    API.state.connected = true;
    API.save();
    return me;
  };

  API.disconnect = function () { API.clear(); };

  // ── Assets ───────────────────────────────────────────────────────
  API.listAssets = async function (query, projectId) {
    const p = new URLSearchParams({ limit: '60' });
    if (query)     p.set('q', query);
    if (projectId && projectId !== 'all') p.set('project_id', projectId);
    return API.json('/api/v1/assets?' + p.toString());
  };

  API.getAsset = async function (assetId) {
    return API.json('/api/v1/assets/' + assetId);
  };

  // /stream → { url, type, source }  (same-origin proxy video URL)
  API.getProxyUrl = async function (assetId) {
    const data = await API.json('/api/v1/assets/' + assetId + '/stream');
    if (!data || !data.url) throw new Error('Asset has no proxy');
    const u = data.url;
    const abs = /^https?:\/\//i.test(u) ? u : trimUrl(API.state.serverUrl) + u;
    return { url: abs, source: data.source || 'proxy' };
  };

  // /hires → { url, filename, ext, file_size, type }  (presigned S3 URL)
  API.getHiresInfo = async function (assetId) {
    const data = await API.json('/api/v1/assets/' + assetId + '/hires');
    if (!data || !data.url) throw new Error('Asset has no hi-res source');
    return data;
  };

  // /live-path → { win_path, posix_path, display_name }
  API.getLivePath = async function (assetId) {
    return API.json('/api/v1/assets/' + assetId + '/live-path');
  };

  // ── Projects ─────────────────────────────────────────────────────
  API.listProjects = async function () {
    const data = await API.json('/api/v1/projects');
    return Array.isArray(data) ? data : [];
  };

  // ── Sequences ────────────────────────────────────────────────────
  API.listSequences = async function (projectId) {
    const p = new URLSearchParams();
    if (projectId) p.set('project_id', projectId);
    const data = await API.json('/api/v1/sequences?' + p.toString());
    return Array.isArray(data) ? data : [];
  };

  API.createSequence = async function (projectId, name, frameRate, width, height) {
    return API.json('/api/v1/sequences', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ project_id: projectId, name, frame_rate: frameRate, width, height }),
    });
  };

  API.updateSequence = async function (seqId, fields) {
    return API.json('/api/v1/sequences/' + seqId, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(fields),
    });
  };

  API.pushClips = async function (seqId, clips) {
    const r = await API.request('/api/v1/sequences/' + seqId + '/clips', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(clips),
    });
    if (!r.ok) throw new Error('Clip push HTTP ' + r.status);
    return r.json();
  };

  API.startConform = async function (seqId, opts) {
    return API.json('/api/v1/sequences/' + seqId + '/conform', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(opts),
    });
  };

  API.getJob = async function (jobId) {
    return API.json('/api/v1/jobs/' + jobId);
  };

  API.batchTrim = async function (clips) {
    return API.json('/api/v1/assets/batch-trim', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ clips }),
    });
  };

  // Local Export trim job polling + segment retrieval.
  API.getTrimStatus = function (jobId) {
    return API.json('/api/v1/assets/trim-status/' + jobId);
  };
  API.getTempSegmentUrl = function (clipInstanceId) {
    return API.json('/api/v1/assets/temp-segment-url/' + clipInstanceId);
  };

  // ── Upload (ingest editor media into the MAM) ────────────────────
  // Single-shot multipart form upload (server caps simple at <50 MB).
  API.uploadSimple = async function (blob, meta) {
    const fd = new FormData();
    fd.append('file', blob, meta.filename);
    fd.append('filename', meta.filename);
    fd.append('projectId', meta.projectId);
    if (meta.binId) fd.append('binId', meta.binId);
    if (meta.contentType) fd.append('contentType', meta.contentType);
    const r = await API.request('/api/v1/upload/simple', { method: 'POST', body: fd });
    if (!r.ok) throw new Error('Upload HTTP ' + r.status + ' — ' + (await r.text().catch(() => '')).slice(0, 160));
    return r.json();
  };

  // Chunked multipart for large originals.
  API.uploadInit = function (meta) {
    return API.json('/api/v1/upload/init', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(meta),
    });
  };
  API.uploadPart = async function (blob, meta) {
    const fd = new FormData();
    fd.append('file', blob, 'part-' + meta.partNumber);
    fd.append('uploadId', meta.uploadId);
    fd.append('key', meta.key);
    fd.append('partNumber', String(meta.partNumber));
    const r = await API.request('/api/v1/upload/part', { method: 'POST', body: fd });
    if (!r.ok) throw new Error('Upload part HTTP ' + r.status);
    return r.json();
  };
  API.uploadComplete = function (meta) {
    return API.json('/api/v1/upload/complete', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(meta),
    });
  };
  API.uploadAbort = function (meta) {
    return API.json('/api/v1/upload/abort', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(meta),
    }).catch(() => {});
  };

  window.API = API;
})();
