// data.jsx - API client; populates window.ZAMPP_DATA from real endpoints

const API = '/api/v1';
window.ZAMPP_API_PREFIX = API;  // single source of truth (#115)

// Gated logger (#123). Production deploys ship muted; appending ?debug=1
// to the URL (or localStorage.df_debug = '1') re-enables full console output.
(function setupLogger() {
  let enabled = false;
  try {
    enabled =
      /(?:^|[?&])debug=1(?:&|$)/.test(location.search) ||
      localStorage.getItem('df_debug') === '1' ||
      location.hostname === 'localhost' || location.hostname === '127.0.0.1';
  } catch {}
  const noop = () => {};
  window.DF_LOG = {
    debug: enabled ? console.debug.bind(console) : noop,
    warn:  enabled ? console.warn.bind(console)  : noop,
    error: console.error.bind(console),   // errors always surface
  };
})();

// Premiere panel releases embedded in this deployment. Bumping the version
// here is the single source of truth - both the Editor download buttons and
// the Settings → Capture SDKs page read from this list (#125).
//
// The panel is now a UXP plugin (.ccx), replacing the legacy CEP/ZXP panel.
// Each entry carries a `ccx` URL; the older `zxp`/`installer` fields are gone.
window.PREMIERE_RELEASES = [
  {
    version: '2.2.3',
    ccx:       '/downloads/dragonflight-mam-2.2.3.ccx',
    installer: null,
    notes: 'Fix: streaming write for large imports (no more truncated files). UXP plugin: one-click Export Timeline, library + conform + auto-relink, growing-file mount.',
    latest: true,
  },
  {
    version: '2.2.2',
    ccx:       '/downloads/dragonflight-mam-2.2.2.ccx',
    installer: null,
    notes: 'UXP plugin: one-click Export Timeline, library + conform + auto-relink, growing-file mount, runtime version chip. Replaces the legacy CEP/ZXP panel.',
    latest: false,
  },
];
window.PREMIERE_LATEST = window.PREMIERE_RELEASES.find(r => r.latest) || window.PREMIERE_RELEASES[0];

// Teams ISO workstation installer. Placeholder slot: the .exe is not in the
// repo yet, so `available` is false and the Downloads modal renders the row
// disabled with a "coming soon" note. Drop the file into public/downloads/
// and flip `available: true` (set `version`) to finish it.
window.TEAMS_ISO = { version: null, url: '/downloads/TeamsISO.exe', available: false };

window.ZAMPP_DATA = {
  PROJECTS: [],
  ASSETS: [],
  RECORDERS: [],
  JOBS: [],
  NODES: [],
  USERS: [],
  BINS: [],
  COMMENTS: [],
  ME: null,
};

async function apiFetch(path, opts = {}) {
  const method = (opts.method || 'GET').toUpperCase();
  const headers = {
    ...(opts.headers || {}),
    'Content-Type': 'application/json',
  };
  if (method !== 'GET' && method !== 'HEAD') headers['X-Requested-With'] = 'dragonflight-ui';

  const res = await fetch(API + path, {
    credentials: 'include',
    ...opts,
    headers,
  });
  // 401: hand off to AuthGate, which will re-render Login (no full-page reload).
  if (res.status === 401) {
    if (window.AuthGate && typeof window.AuthGate.bounce === 'function') {
      window.AuthGate.bounce('apiFetch saw 401 on ' + path);
    }
    throw new Error('Unauthenticated');
  }
  if (!res.ok) throw new Error(res.status + ' ' + res.statusText);
  return res.json();
}

function fmtDuration(ms) {
  if (!ms) return '·';
  const s = Math.round(ms / 1000);
  const h = Math.floor(s / 3600);
  const m = Math.floor((s % 3600) / 60);
  const sec = s % 60;
  if (h > 0) return h + ':' + String(m).padStart(2, '0') + ':' + String(sec).padStart(2, '0');
  return m + ':' + String(sec).padStart(2, '0');
}

function fmtSize(bytes) {
  if (!bytes) return '·';
  if (bytes >= 1e12) return (bytes / 1e12).toFixed(1) + ' TB';
  if (bytes >= 1e9) return (bytes / 1e9).toFixed(1) + ' GB';
  if (bytes >= 1e6) return Math.round(bytes / 1e6) + ' MB';
  return Math.round(bytes / 1e3) + ' KB';
}

function fmtRelative(iso) {
  if (!iso) return '·';
  const diff = (Date.now() - new Date(iso)) / 1000;
  if (diff < 60) return 'just now';
  if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
  if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
  return Math.floor(diff / 86400) + 'd ago';
}

const PROJECT_COLORS = ['#5B7CFA', '#2DD4A8', '#FF5B5B', '#F5A623', '#B57CFA', '#6B7280'];
window.PROJECT_COLORS = PROJECT_COLORS;

function normalizeAsset(a, projectMap) {
  return {
    ...a,
    name: a.display_name || a.filename || 'Untitled',
    type: a.media_type || 'video',
    duration: fmtDuration(a.duration_ms),
    size: fmtSize(a.file_size),
    res: a.resolution || '·',
    updated: fmtRelative(a.updated_at),
    project: (projectMap && projectMap[a.project_id]) || '',
    comments: 0,
    progress: 0,
    tc: a.start_tc || null,
    seed: a.id ? Array.from(a.id.replace(/-/g, '')).reduce((acc, c) => acc + c.charCodeAt(0), 0) % 6 : 1,
  };
}

function normalizeRecorder(r) {
  let elapsed = '·';
  if (r.status === 'recording' && r.started_at) {
    const s = Math.floor((Date.now() - new Date(r.started_at)) / 1000);
    elapsed = String(Math.floor(s / 3600)).padStart(2, '0') + ':' +
              String(Math.floor((s % 3600) / 60)).padStart(2, '0') + ':' +
              String(s % 60).padStart(2, '0');
  }
  const cfg = r.source_config || {};
  return {
    ...r,
    source: r.source_type || '·',
    url: cfg.url || cfg.address || cfg.srt_url || cfg.rtmp_url || r.source_type || '·',
    codec: r.recording_codec || '·',
    res: r.recording_resolution || '·',
    node: r.node_id ? r.node_id.slice(0, 8) : 'primary',
    elapsed,
    bitrate: '·',
    health: 100,
    audio: false,
  };
}

function normalizeJob(j) {
  const statusMap = { waiting: 'queued', active: 'running', completed: 'done', failed: 'failed' };
  const kindMap = { proxy: 'Proxy', thumbnail: 'Thumbnail', conform: 'Conform', transcode: 'Transcode' };
  const meta = j.metadata || {};
  return {
    ...j,
    status: statusMap[j.status] || j.status,
    kind: kindMap[j.type] || j.type || 'Job',
    asset: j.asset_name || meta.filename || '·',
    eta: '·',
    node: meta.node || '·',
    priority: meta.priority || 'normal',
    error: j.error || null,
    progress: j.progress || 0,
  };
}

async function refreshAssets() {
  const raw = await apiFetch('/assets?limit=500');
  const list = Array.isArray(raw) ? raw : (raw.assets || []);
  const projectMap = {};
  (window.ZAMPP_DATA.PROJECTS || []).forEach(function(p) { projectMap[p.id] = p.name; });
  const normalized = list.map(function(a) { return normalizeAsset(a, projectMap); });
  window.ZAMPP_DATA.ASSETS = normalized;
  if (window.ZAMPP_DATA.PROJECTS && window.ZAMPP_DATA.PROJECTS.length) {
    const counts = {};
    list.forEach(function(a) { if (a.project_id) counts[a.project_id] = (counts[a.project_id] || 0) + 1; });
    window.ZAMPP_DATA.PROJECTS = window.ZAMPP_DATA.PROJECTS.map(function(p) {
      return { ...p, assets: counts[p.id] || 0 };
    });
  }
  return normalized;
}

async function loadData() {
  const [projectsR, assetsR, recordersR, jobsR, clusterR, usersR, binsR, meR] = await Promise.allSettled([
    apiFetch('/projects'),
    apiFetch('/assets?limit=500'),
    apiFetch('/recorders'),
    apiFetch('/jobs'),
    apiFetch('/cluster'),
    apiFetch('/users'),
    apiFetch('/bins'),
    apiFetch('/auth/me'),
  ]);

  const projectMap = {};
  if (projectsR.status === 'fulfilled') {
    window.ZAMPP_DATA.PROJECTS = (projectsR.value || []).map((p, i) => ({
      ...p,
      color: PROJECT_COLORS[i % PROJECT_COLORS.length],
      assets: 0,
      updated: fmtRelative(p.updated_at),
    }));
    window.ZAMPP_DATA.PROJECTS.forEach(p => { projectMap[p.id] = p.name; });
  }

  if (assetsR.status === 'fulfilled') {
    const raw = assetsR.value;
    const list = Array.isArray(raw) ? raw : (raw.assets || []);
    window.ZAMPP_DATA.ASSETS = list.map(a => normalizeAsset(a, projectMap));
    const counts = {};
    list.forEach(a => { if (a.project_id) counts[a.project_id] = (counts[a.project_id] || 0) + 1; });
    window.ZAMPP_DATA.PROJECTS = window.ZAMPP_DATA.PROJECTS.map(p => ({ ...p, assets: counts[p.id] || 0 }));
  }

  if (recordersR.status === 'fulfilled') {
    window.ZAMPP_DATA.RECORDERS = (recordersR.value || []).map(normalizeRecorder);
  }

  if (jobsR.status === 'fulfilled') {
    window.ZAMPP_DATA.JOBS = (jobsR.value || []).map(normalizeJob);
  }

  if (clusterR.status === 'fulfilled') {
    window.ZAMPP_DATA.NODES = clusterR.value || [];
  }

  if (usersR.status === 'fulfilled') {
    window.ZAMPP_DATA.USERS = (usersR.value || []).map(u => ({
      ...u,
      name: u.display_name || u.username || u.email || 'Unknown',
      initials: (u.display_name || u.username || '?').slice(0, 2).toUpperCase(),
      role: u.role || 'viewer',
      joined: fmtRelative(u.created_at),
      lastSeen: fmtRelative(u.last_seen || u.updated_at),
    }));
  }

  if (binsR.status === 'fulfilled') {
    window.ZAMPP_DATA.BINS = (binsR.value || []).map(b => ({
      ...b,
      count: b.asset_count || 0,
      icon: b.type || 'grid',
    }));
  }

  if (meR.status === 'fulfilled' && meR.value) {
    const me = meR.value;
    const label = me.display_name || me.username || 'User';
    window.ZAMPP_DATA.ME = {
      id:        me.id,
      username:  me.username,
      name:      label,
      initials:  label.slice(0, 2).toUpperCase(),
      role:      me.role || 'viewer',
      // True when the server returned a synthetic user (AUTH_ENABLED=false).
      // Surfaced as a small "auth off" hint in the sidebar so the operator
      // understands why the corner shows the OS user instead of a login.
      synthetic: !!me.synthetic,
    };
  }
}

// ── Sequence API ────────────────────────────────────────────────

async function getSequences(projectId) {
  return apiFetch('/sequences?project_id=' + projectId);
}

async function createSequence(data) {
  return apiFetch('/sequences', {
    method: 'POST',
    body: JSON.stringify(data),
  });
}

async function getSequence(sequenceId) {
  return apiFetch('/sequences/' + sequenceId);
}

async function updateSequence(sequenceId, data) {
  return apiFetch('/sequences/' + sequenceId, {
    method: 'PUT',
    body: JSON.stringify(data),
  });
}

async function deleteSequence(sequenceId) {
  return apiFetch('/sequences/' + sequenceId, { method: 'DELETE' });
}

async function syncSequenceClips(sequenceId, clips) {
  return apiFetch('/sequences/' + sequenceId + '/clips', {
    method: 'PUT',
    body: JSON.stringify(clips),
  });
}

async function exportSequenceEDL(sequenceId, filename) {
  const res = await fetch(API + '/sequences/' + sequenceId + '/export/edl', {
    method: 'POST',
    credentials: 'include',
    headers: { 'X-Requested-With': 'dragonflight-ui' },
  });
  if (!res.ok) throw new Error('EDL export failed: ' + res.status);
  const blob = await res.blob();
  const url  = URL.createObjectURL(blob);
  const a    = document.createElement('a');
  a.href     = url;
  a.download = filename || 'sequence.edl';
  a.click();
  URL.revokeObjectURL(url);
}

window.ZAMPP_API = {
  fetch: apiFetch, loadData, refreshAssets, fmtDuration, fmtSize, fmtRelative,
  getSequences, createSequence, getSequence, updateSequence,
  deleteSequence, syncSequenceClips, exportSequenceEDL,
};
// Library re-renders after mutations: expose normalizeAsset so the screen
// can re-fetch /assets and produce rows with the same shape as the boot load.
window.normalizeAsset = normalizeAsset;
