/* SynapIA Studio — runtime helpers (substitui useTweaks/TweaksPanel do design original) */

window.useTweaks = function (defaults) {
  const [state] = React.useState(defaults || {});
  return [state, () => {}];
};

window.TweaksPanel = function () { return null; };
window.TweakSection = function () { return null; };
window.TweakRadio = function () { return null; };
window.TweakToggle = function () { return null; };

/* Mapeia toolTrace do backend (array de {tool, args, result}) para o formato
   esperado pelo componente <Message/> (array de {name, status, args, result}). */
window.normalizeToolTrace = function (toolTrace) {
  if (!Array.isArray(toolTrace)) return [];
  return toolTrace.map(t => ({
    name: t.tool || t.name || 'tool',
    status: 'done',
    args: t.args || {},
    result: typeof t.result === 'string' ? t.result : JSON.stringify(t.result, null, 2),
  }));
};

/* Lê ?session= da URL — para integração com a extensão */
window.getSessionFromURL = function () {
  try {
    const u = new URL(window.location.href);
    return u.searchParams.get('session');
  } catch { return null; }
};

/* Persistência leve de perfil em localStorage */
window.loadProfile = function () {
  try {
    const raw = localStorage.getItem('synapia.profile');
    if (raw) return JSON.parse(raw);
  } catch {}
  return { name: '' };
};
window.saveProfile = function (p) {
  try { localStorage.setItem('synapia.profile', JSON.stringify(p || {})); } catch {}
};

window.styleBackupKey = function () {
  let user = null;
  try { user = window.readStoredUser ? window.readStoredUser() : null; } catch {}
  const id = user?.id || user?.email || 'local';
  return 'synapia.styleBackup.' + String(id).replace(/[^\w.@-]+/g, '_');
};

window.loadStyleBackup = function () {
  try {
    const raw = localStorage.getItem(window.styleBackupKey());
    return raw ? JSON.parse(raw) : null;
  } catch { return null; }
};

window.saveStyleBackup = function (payload) {
  if (!payload) return;
  const store = payload.client_store || payload.store || payload;
  const brief = payload.brief || store.brief || '';
  const backup = { store, brief, savedAt: new Date().toISOString() };
  try { localStorage.setItem(window.styleBackupKey(), JSON.stringify(backup)); } catch {}
};

window.styleListFromBackup = function () {
  const backup = window.loadStyleBackup();
  const store = backup?.store;
  if (!store?.samples?.length) return null;
  return {
    total: store.samples.length,
    updated: store.updated || backup.savedAt,
    profile_disponivel: !!store.profile,
    samples: store.samples.map(s => ({ id: s.id, titulo: s.titulo, tipo: s.tipo, chars: s.chars, addedAt: s.addedAt }))
  };
};

/* API helpers — todas as chamadas ao backend ficam aqui */
window.api = {
  async chat({ conversationId, message, mode, processSession, profile, attachmentId, attachmentIds, selectedSkills, agentSupplement }) {
    const clientStyleBrief = window.loadStyleBackup?.()?.brief || '';
    const r = await fetch('/api/chat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', ...authHeaders() },
      body: JSON.stringify({ conversationId, message, mode, processSession, profile, attachmentId, attachmentIds, selectedSkills, agentSupplement, clientStyleBrief }),
    });
    if (!r.ok) throw new Error('chat_failed_' + r.status);
    return r.json();
  },

  /* Versão streaming: consome SSE de /api/chat e despacha callbacks
     - onMeta({conversationId,mode,engine,canvasActive})
     - onDelta(textChunk)            → conteúdo do assistant (concatenar)
     - onThinking(textChunk)         → cadeia de raciocínio incremental
    - onStatus(payload)             → fase amigável do backend
    - onDone(payload)               → reply final, latency, canvas etc
     - onError(message)
     Retorna um AbortController.abort() para cancelar.
  */
  chatStream({ conversationId, message, mode, processSession, profile, attachmentId, attachmentIds, selectedSkills, agentSupplement },
             { onMeta, onDelta, onThinking, onStatus, onDone, onError } = {}) {
    const ctl = new AbortController();
    (async () => {
      try {
        const clientStyleBrief = window.loadStyleBackup?.()?.brief || '';
        const r = await fetch('/api/chat', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream', ...authHeaders() },
          body: JSON.stringify({ conversationId, message, mode, processSession, profile, attachmentId, attachmentIds, selectedSkills, agentSupplement, clientStyleBrief, stream: true }),
          signal: ctl.signal
        });
        if (!r.ok || !r.body) throw new Error('chat_failed_' + r.status);
        const reader = r.body.getReader();
        const decoder = new TextDecoder('utf-8');
        let buffer = '';
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          buffer += decoder.decode(value, { stream: true });
          let blockEnd;
          while ((blockEnd = buffer.indexOf('\n\n')) !== -1) {
            const block = buffer.slice(0, blockEnd);
            buffer = buffer.slice(blockEnd + 2);
            let event = 'message', dataLines = [];
            for (const line of block.split('\n')) {
              if (!line || line.startsWith(':')) continue;
              if (line.startsWith('event:')) event = line.slice(6).trim();
              else if (line.startsWith('data:')) dataLines.push(line.slice(5).trim());
            }
            if (!dataLines.length) continue;
            let payload;
            try { payload = JSON.parse(dataLines.join('\n')); } catch { continue; }
            if (event === 'meta')   onMeta && onMeta(payload);
            else if (event === 'delta') onDelta && onDelta(payload.text || '');
            else if (event === 'think') onThinking && onThinking(payload.text || '');
            else if (event === 'status') onStatus && onStatus(payload);
            else if (event === 'done')  onDone && onDone(payload);
            else if (event === 'error') onError && onError(payload.error || 'erro');
          }
        }
      } catch (e) {
        if (e.name === 'AbortError') return;
        onError && onError(e.message || String(e));
      }
    })();
    return ctl;
  },
  async judicialCalc(payload) {
    const r = await fetch('/api/calculos/judiciais', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', ...authHeaders() },
      body: JSON.stringify(payload || {}),
    });
    const j = await r.json().catch(() => ({}));
    if (!r.ok || !j.ok) throw new Error(j.error || 'calculo_failed_' + r.status);
    return j.result;
  },
  async systemNow() {
    const r = await fetch('/api/system/now');
    if (!r.ok) return null;
    return r.json();
  },
  async downloadDocument({ format = 'word', content, title }) {
    const r = await fetch(`/api/download/${format}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', ...authHeaders() },
      body: JSON.stringify({ content, title }),
    });
    if (!r.ok) throw new Error('download_failed_' + r.status);
    const blob = await r.blob();
    const disposition = r.headers.get('Content-Disposition') || '';
    const filenameMatch = disposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
    const filename = filenameMatch ? decodeURIComponent(filenameMatch[1].replace(/['"]/g, '')) : `${title || 'documento'}.${format === 'word' ? 'docx' : 'pdf'}`;
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.style.display = 'none';
    document.body.appendChild(a);
    a.click();
    a.remove();
    URL.revokeObjectURL(url);
    return { ok: true, filename };
  },
  async listConversations() {
    const r = await fetch('/api/conversations', { headers: authHeaders() });
    if (!r.ok) return { ok: false, items: [] };
    return r.json();
  },
  async getConversation(id) {
    const r = await fetch('/api/conversations/' + encodeURIComponent(id), { headers: authHeaders() });
    if (!r.ok) return null;
    const j = await r.json();
    return j.conversation || null;
  },
  async deleteConversation(id) {
    return fetch('/api/conversations/' + encodeURIComponent(id), { method: 'DELETE', headers: authHeaders() });
  },
  async renameConversation(id, title) {
    return fetch('/api/conversations/' + encodeURIComponent(id), {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json', ...authHeaders() },
      body: JSON.stringify({ title }),
    });
  },
  async getBridge(sessionId) {
    const r = await fetch('/api/bridge/session/' + encodeURIComponent(sessionId), { headers: authHeaders() });
    if (!r.ok) return null;
    const j = await r.json();
    return j.summary || null;
  },
  async upload(files) {
    const allFiles = Array.from(files || []);
    const CHUNK_THRESHOLD = 3 * 1024 * 1024;
    const CHUNK_SIZE = 700 * 1024;
    const aggregate = { ok: true, arquivos: [], attachmentIds: [] };

    const merge = (j) => {
      if (!j?.ok) throw new Error(j?.error || 'upload_failed');
      const files = (j.arquivos || []).map(a => ({ ...a, attachmentId: a.attachmentId || j.attachmentId }));
      aggregate.arquivos.push(...files);
      if (j.attachmentId) aggregate.attachmentIds.push(j.attachmentId);
      if (!aggregate.attachmentId && j.attachmentId) aggregate.attachmentId = j.attachmentId;
    };

    const uploadRegular = async (file) => {
      const fd = new FormData();
      fd.append('files', file);
      const r = await fetch('/api/upload', { method: 'POST', headers: authHeaders(), body: fd });
      if (!r.ok) throw new Error('upload_failed_' + r.status);
      merge(await r.json());
    };

    const uploadChunked = async (file) => {
      const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
      const init = await fetch('/api/upload/chunk/init', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authHeaders() },
        body: JSON.stringify({ name: file.name, type: file.type || 'application/octet-stream', size: file.size, totalChunks })
      });
      if (!init.ok) throw new Error('upload_init_failed_' + init.status);
      const { uploadId } = await init.json();
      for (let index = 0; index < totalChunks; index++) {
        const slice = file.slice(index * CHUNK_SIZE, Math.min(file.size, (index + 1) * CHUNK_SIZE));
        const bytes = new Uint8Array(await slice.arrayBuffer());
        let binary = '';
        for (let i = 0; i < bytes.length; i += 0x8000) {
          binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
        }
        const chunk = await fetch('/api/upload/chunk', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', ...authHeaders() },
          body: JSON.stringify({ uploadId, index, data: btoa(binary) })
        });
        if (!chunk.ok) throw new Error('upload_chunk_failed_' + chunk.status);
      }
      const finish = await fetch('/api/upload/chunk/finish', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authHeaders() },
        body: JSON.stringify({ uploadId })
      });
      if (!finish.ok) throw new Error('upload_finish_failed_' + finish.status);
      merge(await finish.json());
    };

    for (const file of allFiles) {
      if (file.size > CHUNK_THRESHOLD) await uploadChunked(file);
      else await uploadRegular(file);
    }
    return aggregate;
  },
  async refine(rawPrompt, context) {
    const r = await fetch('/api/refine', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', ...authHeaders() },
      body: JSON.stringify({ rawPrompt, context }),
    });
    if (!r.ok) throw new Error('refine_failed_' + r.status);
    return r.json();
  },
  async health() {
    try { const r = await fetch('/api/health'); return r.ok ? r.json() : null; }
    catch { return null; }
  },
};

/* Atalho: tempo relativo curto */
window.relativeTime = function (iso) {
  if (!iso) return '';
  const d = new Date(iso);
  const diff = (Date.now() - d.getTime()) / 1000;
  if (diff < 60) return 'agora';
  if (diff < 3600) return `há ${Math.floor(diff / 60)}min`;
  if (diff < 86400) return `há ${Math.floor(diff / 3600)}h`;
  if (diff < 86400 * 2) return 'ontem';
  if (diff < 86400 * 7) return `há ${Math.floor(diff / 86400)}d`;
  return d.toLocaleDateString('pt-BR');
};
