// API client — talks to the FastAPI backend. Every request carries the
// Supabase bearer token so the FastAPI auth gate can identify the user.

const _authHeader = () => {
  const s = window.palette_auth && window.palette_auth.getSession();
  const tok = s && s.access_token;
  return tok ? { 'authorization': 'Bearer ' + tok } : {};
};

const _authQuery = () => {
  const s = window.palette_auth && window.palette_auth.getSession();
  const tok = s && s.access_token;
  return tok ? ('access_token=' + encodeURIComponent(tok)) : '';
};

const _fetch = async (url, init = {}) => {
  const headers = { ...(init.headers || {}), ..._authHeader() };
  const res = await fetch(url, { ...init, headers });
  if (res.status === 401) {
    // Token expired mid-session — push the user back to login.
    if (window.palette_auth) { try { await window.palette_auth.signOut(); } catch(e) {} }
    window.dispatchEvent(new CustomEvent('palette:unauth'));
  }
  return res;
};

const API = {
  async me() {
    const r = await _fetch('/api/auth/me');
    if (!r.ok) throw new Error('me ' + r.status);
    return r.json();
  },
  async createInvite({ email, role } = {}) {
    const r = await _fetch('/api/auth/invites', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ email, role: role || 'member' }),
    });
    if (!r.ok) throw new Error('invite ' + r.status + ' ' + await r.text());
    return r.json();
  },
  async checkInvite(token) {
    const r = await fetch('/api/auth/invites/' + encodeURIComponent(token));
    if (!r.ok) throw new Error('invite ' + r.status + ' ' + await r.text());
    return r.json();
  },
  async state() {
    const r = await _fetch('/api/state');
    if (!r.ok) throw new Error('state ' + r.status);
    return r.json();
  },
  async createProject({client, name, brief}) {
    const r = await _fetch('/api/projects', {
      method:'POST',
      headers:{'content-type':'application/json'},
      body: JSON.stringify({client, name, brief}),
    });
    if (!r.ok) throw new Error('createProject ' + r.status);
    return r.json();
  },
  async deleteProject(pid) {
    const r = await _fetch('/api/projects/' + encodeURIComponent(pid), {method:'DELETE'});
    if (!r.ok) throw new Error('deleteProject ' + r.status);
    return r.json();
  },
  async createFolder(pid, name) {
    const r = await _fetch(`/api/projects/${encodeURIComponent(pid)}/folders`, {
      method:'POST',
      headers:{'content-type':'application/json'},
      body: JSON.stringify({name}),
    });
    if (!r.ok) throw new Error('createFolder ' + r.status);
    return r.json();
  },
  async uploadFile(pid, fid, file, onProgress) {
    // Use XHR to get progress; fetch doesn't expose upload progress.
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.open('POST', `/api/projects/${encodeURIComponent(pid)}/folders/${encodeURIComponent(fid)}/upload`);
      const h = _authHeader();
      if (h.authorization) xhr.setRequestHeader('Authorization', h.authorization);
      xhr.upload.onprogress = (e) => {
        if (e.lengthComputable && onProgress) onProgress(e.loaded / e.total);
      };
      xhr.onload = () => {
        if (xhr.status >= 200 && xhr.status < 300) {
          try { resolve(JSON.parse(xhr.responseText)); } catch(e){ reject(e); }
        } else reject(new Error('upload ' + xhr.status + ': ' + xhr.responseText));
      };
      xhr.onerror = () => reject(new Error('upload network'));
      const fd = new FormData();
      fd.append('file', file);
      xhr.send(fd);
    });
  },
  async deleteFile(file_id) {
    const r = await _fetch('/api/files/' + encodeURIComponent(file_id), {method:'DELETE'});
    if (!r.ok) throw new Error('deleteFile ' + r.status);
    return r.json();
  },
  async analyzeFile(file_id) {
    const r = await _fetch(`/api/files/${encodeURIComponent(file_id)}/analyze`, {method:'POST'});
    if (!r.ok) throw new Error('analyze ' + r.status + ' ' + await r.text());
    return r.json();
  },
  streamAnalyze(file_id, { onParsed, onBlock, onDone, onError } = {}) {
    // Live per-block progress via SSE. Returns a close() handle.
    // EventSource can't set headers — pass token as ?access_token=
    const q = _authQuery();
    const url = `/api/files/${encodeURIComponent(file_id)}/analyze/stream` + (q ? '?' + q : '');
    const es = new EventSource(url);
    const safe = (fn) => (e) => {
      try { fn && fn(JSON.parse(e.data)); } catch (err) { console.warn('sse parse', err); }
    };
    es.addEventListener('parsed', safe(onParsed));
    es.addEventListener('block', safe(onBlock));
    es.addEventListener('done', (e) => { safe(onDone)(e); es.close(); });
    es.addEventListener('error', (e) => {
      // e.data is only present for server-sent 'error' events; native EventSource
      // errors (network) fire with no data.
      try { onError && onError(e.data ? JSON.parse(e.data) : { error: '연결 끊김' }); }
      catch (err) { onError && onError({ error: '스트림 파싱 실패' }); }
      es.close();
    });
    return () => es.close();
  },
  async analyzeProject(pid) {
    const r = await _fetch(`/api/projects/${encodeURIComponent(pid)}/analyze`, {method:'POST'});
    if (!r.ok) throw new Error('analyzeProject ' + r.status + ' ' + await r.text());
    return r.json();
  },
  async saveBoard(pid, board) {
    const r = await _fetch(`/api/projects/${encodeURIComponent(pid)}/board`, {
      method:'PUT',
      headers:{'content-type':'application/json'},
      body: JSON.stringify(board),
    });
    if (!r.ok) throw new Error('saveBoard ' + r.status);
    return r.json();
  },
  async brainstorm(project_id, model, messages) {
    const r = await _fetch('/api/brainstorm', {
      method:'POST',
      headers:{'content-type':'application/json'},
      body: JSON.stringify({project_id, model, messages}),
    });
    if (!r.ok) throw new Error('brainstorm ' + r.status + ' ' + await r.text());
    return r.json();
  },
  async saveBrainstorm(pid, messages) {
    const r = await _fetch(`/api/projects/${encodeURIComponent(pid)}/brainstorm`, {
      method:'PUT',
      headers:{'content-type':'application/json'},
      body: JSON.stringify({messages}),
    });
    if (!r.ok) throw new Error('saveBrainstorm ' + r.status);
    return r.json();
  },
  async distill(pid) {
    const r = await _fetch(`/api/projects/${encodeURIComponent(pid)}/distill`, {method:'POST'});
    if (!r.ok) throw new Error('distill ' + r.status);
    return r.json();
  },
  async exportProject(pid) {
    const r = await _fetch(`/api/projects/${encodeURIComponent(pid)}/export`, {method:'POST'});
    if (!r.ok) throw new Error('export ' + r.status);
    return r.json();
  },
  async getJob(job_id) {
    const r = await _fetch('/api/jobs/' + encodeURIComponent(job_id));
    if (!r.ok) throw new Error('job ' + r.status);
    return r.json();
  },
  async patchBlock(file_id, block_id, patch) {
    const r = await _fetch(`/api/files/${encodeURIComponent(file_id)}/blocks/${encodeURIComponent(block_id)}`, {
      method:'PATCH',
      headers:{'content-type':'application/json'},
      body: JSON.stringify(patch),
    });
    if (!r.ok) throw new Error('patchBlock ' + r.status + ' ' + await r.text());
    return r.json();
  },
  async mutateKeyword(body) {
    const r = await _fetch('/api/keywords/mutate', {
      method:'POST',
      headers:{'content-type':'application/json'},
      body: JSON.stringify(body),
    });
    if (!r.ok) throw new Error('mutate ' + r.status + ' ' + await r.text());
    return r.json();
  },
  async searchReferences(pid, body) {
    const r = await _fetch(`/api/projects/${encodeURIComponent(pid)}/references/search`, {
      method:'POST',
      headers:{'content-type':'application/json'},
      body: JSON.stringify(body),
    });
    if (!r.ok) throw new Error('refSearch ' + r.status + ' ' + await r.text());
    return r.json();
  },
  async archiveReference(pid, candidate) {
    const r = await _fetch(`/api/projects/${encodeURIComponent(pid)}/references/archive`, {
      method:'POST',
      headers:{'content-type':'application/json'},
      body: JSON.stringify({candidate}),
    });
    if (!r.ok) throw new Error('refArchive ' + r.status);
    return r.json();
  },
  async downloadReference(pid, body) {
    // body = { ref_id? | candidate?, folder_id? }
    const r = await _fetch(`/api/projects/${encodeURIComponent(pid)}/references/download`, {
      method:'POST',
      headers:{'content-type':'application/json'},
      body: JSON.stringify(body),
    });
    if (!r.ok) throw new Error('refDownload ' + r.status + ' ' + await r.text());
    return r.json();
  },
  async removeReference(pid, rid) {
    const r = await _fetch(`/api/projects/${encodeURIComponent(pid)}/references/${encodeURIComponent(rid)}`, {method:'DELETE'});
    if (!r.ok) throw new Error('refRemove ' + r.status);
    return r.json();
  },
  async keywordFeedback(pid, term, ko, verdict) {
    const r = await _fetch(`/api/projects/${encodeURIComponent(pid)}/feedback`, {
      method:'POST',
      headers:{'content-type':'application/json'},
      body: JSON.stringify({term, ko, verdict}),
    });
    if (!r.ok) throw new Error('feedback ' + r.status);
    return r.json();
  },
};

window.API = API;
