/**
 * api.js — REST + WebSocket client for tono backend.
 *
 * Uses relative URLs so the Vite dev proxy (port 5173 → 8188)
 * and production same-origin serving both work transparently.
 */

const SESSION_STORAGE_KEY = 'tono-session-id';

let _sessionId: string | null = null;
let _ws: WebSocket | null = null;
let _handler: ((msg: any) => void) | null = null;
let _reconnectTimer: ReturnType<typeof setTimeout> | undefined = undefined;

function generateSessionId() {
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
    return crypto.randomUUID();
  }
  return `session-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
}

export function getSessionId() {
  if (_sessionId) return _sessionId;

  if (typeof window === 'undefined') {
    _sessionId = 'session-test-runner';
    return _sessionId;
  }

  try {
    const stored = window.sessionStorage?.getItem(SESSION_STORAGE_KEY);
    if (stored) {
      _sessionId = stored;
      return _sessionId;
    }
  } catch {
    // Fall through to in-memory session id generation.
  }

  _sessionId = generateSessionId();
  try {
    window.sessionStorage?.setItem(SESSION_STORAGE_KEY, _sessionId);
  } catch {
    // Ignore storage failures and keep the in-memory id.
  }
  return _sessionId;
}

function withSessionHeaders(init: RequestInit = {}) {
  const headers = new Headers(init.headers || {});
  headers.set('X-Argonode-Session', getSessionId());
  return { ...init, headers };
}

async function sessionFetch(input: string, init?: RequestInit) {
  return fetch(input, withSessionHeaders(init));
}

export async function getNodes() {
  const r = await sessionFetch('/nodes');
  if (!r.ok) throw new Error(`GET /nodes failed: ${r.status}`);
  return r.json();
}

export async function getNodeDoc(displayName: string) {
  const r = await sessionFetch(`/docs?name=${encodeURIComponent(displayName)}`);
  if (!r.ok) return null;
  return r.text();
}

export async function getFiles() {
  const r = await sessionFetch('/files');
  if (!r.ok) return [];
  return r.json();
}

export async function createUploadFolder(relativePath: string) {
  const r = await sessionFetch('/upload-folder', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ path: relativePath }),
  });
  if (!r.ok) throw new Error(`Create folder failed: ${r.status}`);
  return r.json();
}

export async function uploadFile(file: File, { relativePath = '' } = {}) {
  const fd = new FormData();
  if (relativePath) fd.append('relative_path', relativePath);
  fd.append('file', file);
  const r = await sessionFetch('/upload', { method: 'POST', body: fd });
  if (!r.ok) {
    const text = await r.text();
    throw new Error(`Upload failed (${r.status}): ${text}`);
  }
  return r.json();
}

export async function uploadPlugin(file: File) {
  const fd = new FormData();
  fd.append('file', file);
  const r = await fetch('/upload-plugin', { method: 'POST', body: fd });
  if (r.status === 404) {
    throw new Error('Plugin upload is not available in this build.');
  }
  if (!r.ok) {
    const text = await r.text();
    throw new Error(text || `Upload failed (${r.status})`);
  }
  return r.json();
}

export async function getChannels(filepath: string) {
  const r = await sessionFetch(`/channels?file=${encodeURIComponent(filepath)}`);
  if (!r.ok) return [{ name: 'field', type: 'DATA_FIELD' }];
  return r.json();
}

export async function getFileContent(path: string) {
  const r = await sessionFetch(`/file-content?path=${encodeURIComponent(path)}`);
  if (!r.ok) {
    const text = await r.text();
    throw new Error(`Failed to read file (${r.status}): ${text}`);
  }
  return r.arrayBuffer();
}

export async function getFolderFiles(folderpath: string) {
  const r = await sessionFetch(`/folder-files?folder=${encodeURIComponent(folderpath)}`);
  if (!r.ok) return [];
  return r.json();
}

export async function runPrompt(prompt: Record<string, unknown>) {
  const r = await sessionFetch('/prompt', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ prompt }),
  });
  if (!r.ok) {
    const text = await r.text();
    throw new Error(`POST /prompt failed (${r.status}): ${text}`);
  }
  return r.json();
}

export function setMessageHandler(fn: ((msg: any) => void) | null) {
  _handler = fn;
}

export function initWS() {
  if (_ws && _ws.readyState < 2) return;

  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  const session = encodeURIComponent(getSessionId());
  _ws = new WebSocket(`${protocol}//${window.location.host}/ws?session=${session}`);

  _ws.onopen = () => {
    console.log('[tono] WebSocket connected');
  };

  _ws.onclose = () => {
    console.log('[tono] WebSocket closed, reconnecting in 3s…');
    clearTimeout(_reconnectTimer);
    _reconnectTimer = setTimeout(() => initWS(), 3000);
  };

  _ws.onerror = (e) => {
    console.error('[tono] WebSocket error', e);
  };

  _ws.onmessage = (e) => {
    try {
      const msg = JSON.parse(e.data);
      if (_handler) _handler(msg);
    } catch {
      // ignore malformed messages
    }
  };
}

export function closeWS() {
  clearTimeout(_reconnectTimer);
  if (_ws) _ws.close();
}
