/**
 * Brain-API-Client fuer die App.
 *
 * Die App hat keinen direkten HTTP-Zugriff aufs Brain (nur via RVS). Wir
 * tunneln alle Memory-Operationen ueber den generischen brain_request /
 * brain_response RVS-Channel den die Bridge implementiert.
 *
 * Pattern: pro Call eine eindeutige requestId, Listener wartet auf passende
 * brain_response, Promise loest auf / wird abgelehnt bei status>=400.
 */

import rvs from './rvs';

type AnyJson = any;

interface PendingRequest {
  resolve: (data: AnyJson) => void;
  reject: (err: Error) => void;
  timer: ReturnType<typeof setTimeout>;
  expectBinary?: boolean;
}

const pending = new Map<string, PendingRequest>();
let installed = false;

function _ensureListener() {
  if (installed) return;
  installed = true;
  rvs.onMessage((msg: any) => {
    if (!msg || msg.type !== 'brain_response') return;
    const p = msg.payload || {};
    const reqId: string = p.requestId || '';
    const handler = pending.get(reqId);
    if (!handler) return;
    pending.delete(reqId);
    clearTimeout(handler.timer);
    const status: number = Number(p.status || 0);
    if (status >= 200 && status < 300) {
      if (handler.expectBinary) {
        handler.resolve({ base64: p.base64 || '', contentType: p.contentType || '' });
      } else {
        handler.resolve(p.json !== undefined ? p.json : (p.text !== undefined ? p.text : null));
      }
    } else {
      const detail = (p.json && p.json.detail) || p.text || `HTTP ${status}`;
      handler.reject(new Error(`Brain ${status}: ${detail}`));
    }
  });
}

let _nextId = 0;
function _newRequestId(): string {
  _nextId += 1;
  return `brain_${Date.now().toString(36)}_${_nextId}`;
}

interface SendOpts {
  method?: 'GET' | 'POST' | 'PATCH' | 'DELETE';
  body?: AnyJson;
  bodyBase64?: string;
  contentType?: string;
  expectBinary?: boolean;
  timeoutMs?: number;
}

function _send(path: string, opts: SendOpts = {}): Promise<AnyJson> {
  _ensureListener();
  return new Promise((resolve, reject) => {
    const requestId = _newRequestId();
    const timer = setTimeout(() => {
      if (pending.delete(requestId)) {
        reject(new Error(`Brain-Timeout fuer ${path}`));
      }
    }, opts.timeoutMs || 30000);
    pending.set(requestId, { resolve, reject, timer, expectBinary: opts.expectBinary });
    rvs.send('brain_request' as any, {
      requestId,
      method: opts.method || 'GET',
      path,
      ...(opts.body !== undefined ? { body: opts.body } : {}),
      ...(opts.bodyBase64 ? { bodyBase64: opts.bodyBase64 } : {}),
      ...(opts.contentType ? { contentType: opts.contentType } : {}),
    });
  });
}

// ── Typen ────────────────────────────────────────────────────────────

export interface MemoryAttachment {
  name: string;
  mime: string;
  size: number;
  path: string;
}

export interface Memory {
  id: string;
  type: string;
  title: string;
  content: string;
  pinned: boolean;
  category: string;
  source: string;
  tags: string[];
  created_at: string;
  updated_at: string;
  conversation_id?: string | null;
  score?: number | null;
  attachments?: MemoryAttachment[];
}

// ── Memory CRUD ──────────────────────────────────────────────────────

export const brainApi = {
  /** Einzelne Memory holen (mit allen Feldern inkl. Anhaenge) */
  getMemory(id: string): Promise<Memory> {
    return _send(`/memory/get/${encodeURIComponent(id)}`);
  },

  /** Liste aller Memories, optional nach Type gefiltert. */
  listMemories(opts: { type?: string; limit?: number } = {}): Promise<Memory[]> {
    const qs = new URLSearchParams();
    if (opts.type) qs.set('type', opts.type);
    qs.set('limit', String(opts.limit || 500));
    return _send(`/memory/list?${qs.toString()}`);
  },

  /** Volltext-Substring-Suche. */
  searchText(q: string, opts: { type?: string; includePinned?: boolean; k?: number } = {}): Promise<Memory[]> {
    const qs = new URLSearchParams({ q });
    if (opts.type) qs.set('type', opts.type);
    qs.set('include_pinned', String(opts.includePinned !== false));
    qs.set('k', String(opts.k || 50));
    return _send(`/memory/search-text?${qs.toString()}`);
  },

  /** Semantische Suche (Embedder). */
  searchSemantic(q: string, opts: { type?: string; includePinned?: boolean; k?: number; threshold?: number } = {}): Promise<Memory[]> {
    const qs = new URLSearchParams({ q });
    if (opts.type) qs.set('type', opts.type);
    qs.set('include_pinned', String(opts.includePinned !== false));
    qs.set('k', String(opts.k || 10));
    qs.set('score_threshold', String(opts.threshold ?? 0.30));
    return _send(`/memory/search?${qs.toString()}`);
  },

  /** Memory anlegen. */
  saveMemory(body: {
    type: string;
    title: string;
    content: string;
    pinned?: boolean;
    category?: string;
    tags?: string[];
  }): Promise<Memory> {
    return _send('/memory/save', {
      method: 'POST',
      body: { source: 'app', ...body },
    });
  },

  /** Memory aktualisieren (Patch — nur uebergebene Felder werden geaendert). */
  updateMemory(id: string, body: Partial<Pick<Memory, 'title' | 'content' | 'pinned' | 'category' | 'tags'>>): Promise<Memory> {
    return _send(`/memory/update/${encodeURIComponent(id)}`, {
      method: 'PATCH',
      body,
    });
  },

  /** Memory loeschen. */
  deleteMemory(id: string): Promise<{ deleted: string }> {
    return _send(`/memory/delete/${encodeURIComponent(id)}`, {
      method: 'DELETE',
      timeoutMs: 15000,
    });
  },

  // ── Anhaenge ────────────────────────────────────────────────────────

  /** Datei als Anhang an die Memory haengen (Base64-Upload). */
  uploadAttachment(memoryId: string, name: string, base64: string): Promise<Memory> {
    return _send(`/memory/${encodeURIComponent(memoryId)}/attachments`, {
      method: 'POST',
      body: { name, data_base64: base64 },
      timeoutMs: 120000,
    });
  },

  /** Anhang loeschen. */
  deleteAttachment(memoryId: string, filename: string): Promise<Memory> {
    return _send(
      `/memory/${encodeURIComponent(memoryId)}/attachments/${encodeURIComponent(filename)}`,
      { method: 'DELETE' },
    );
  },

  /** Anhang-Bytes holen (fuer Vorschau / Download). Liefert Base64. */
  getAttachmentBytes(memoryId: string, filename: string): Promise<{ base64: string; contentType: string }> {
    return _send(
      `/memory/${encodeURIComponent(memoryId)}/attachments/${encodeURIComponent(filename)}`,
      { expectBinary: true, timeoutMs: 60000 },
    );
  },
};

export default brainApi;
