#!/usr/bin/env node
/**
 * nox-memory.js — Mémoire vectorielle de Nox via Qdrant
 * Utilise node fetch (Node 22+) + OpenAI embeddings
 *
 * Usage:
 *   node nox-memory.js add "texte" --type fact --tags "ha,lumiere" --importance 3
 *   node nox-memory.js search "question naturelle" [--limit 5] [--type fact]
 *   node nox-memory.js list [--type fact] [--limit 20]
 *   node nox-memory.js delete <id>
 *   node nox-memory.js import-md <fichier.md>
 *   node nox-memory.js stats
 */

const QDRANT_URL  = 'http://192.168.1.150:6333';
const COLLECTION  = 'nox-memory';
const OPENAI_URL  = 'https://api.openai.com/v1/embeddings';
const EMBED_MODEL = 'text-embedding-3-small';
const EMBED_DIM   = 1536;

// ─── Helpers ──────────────────────────────────────────────────────────────────

async function qdrant(method, path, body) {
  const opts = {
    method,
    headers: { 'Content-Type': 'application/json' }
  };
  if (body) opts.body = JSON.stringify(body);
  const r = await fetch(`${QDRANT_URL}${path}`, opts);
  return r.json();
}

async function embed(text) {
  const r = await fetch(OPENAI_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`
    },
    body: JSON.stringify({ model: EMBED_MODEL, input: text })
  });
  const json = await r.json();
  if (!json.data) throw new Error('Embedding failed: ' + JSON.stringify(json));
  return json.data[0].embedding;
}

function genId() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = Math.random() * 16 | 0;
    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
  });
}

// ─── Commandes ────────────────────────────────────────────────────────────────

async function cmdAdd(text, opts) {
  const vector = await embed(text);
  const id = genId();
  const payload = {
    text,
    type:       opts.type       || 'fact',
    tags:       opts.tags       ? opts.tags.split(',').map(t => t.trim()) : [],
    importance: parseInt(opts.importance || '3'),
    timestamp:  Date.now(),
    date:       new Date().toISOString()
  };
  const res = await qdrant('PUT', `/collections/${COLLECTION}/points`, {
    points: [{ id, vector, payload }]
  });
  if (res.status === 'ok') {
    console.log(`✅ Mémorisé [${id}]`);
    console.log(`   type: ${payload.type} | importance: ${payload.importance} | tags: ${payload.tags.join(', ') || 'aucun'}`);
    console.log(`   "${text.slice(0, 80)}${text.length > 80 ? '...' : ''}"`);
  } else {
    console.error('❌ Erreur:', JSON.stringify(res));
  }
}

async function cmdSearch(query, opts) {
  const vector = await embed(query);
  const limit = parseInt(opts.limit || '5');
  const body = {
    vector,
    limit,
    with_payload: true,
    score_threshold: 0.3
  };
  if (opts.type) {
    body.filter = { must: [{ key: 'type', match: { value: opts.type } }] };
  }
  const res = await qdrant('POST', `/collections/${COLLECTION}/points/search`, body);
  const results = res.result || [];
  if (results.length === 0) {
    console.log('🔍 Aucun résultat pertinent trouvé.');
    return;
  }
  console.log(`🔍 ${results.length} résultat(s) pour : "${query}"\n`);
  results.forEach((p, i) => {
    const pl = p.payload;
    const score = (p.score * 100).toFixed(1);
    console.log(`[${i+1}] ${score}% — ${pl.type} | imp:${pl.importance} | ${pl.date?.slice(0,10) || '?'}`);
    if (pl.tags?.length) console.log(`     Tags: ${pl.tags.join(', ')}`);
    console.log(`     ${pl.text}`);
    console.log(`     ID: ${p.id}\n`);
  });
}

async function cmdList(opts) {
  const limit = parseInt(opts.limit || '20');
  const body = { limit, with_payload: true };
  if (opts.type) {
    body.filter = { must: [{ key: 'type', match: { value: opts.type } }] };
  }
  const res = await qdrant('POST', `/collections/${COLLECTION}/points/scroll`, body);
  const points = res.result?.points || [];
  if (points.length === 0) { console.log('📭 Collection vide.'); return; }
  console.log(`📋 ${points.length} souvenir(s) :\n`);
  points.forEach(p => {
    const pl = p.payload;
    console.log(`• [${pl.type}|${pl.importance}] ${pl.date?.slice(0,10)} — ${pl.text?.slice(0,100)}${pl.text?.length>100?'...':''}`);
  });
}

async function cmdDelete(id) {
  const res = await qdrant('POST', `/collections/${COLLECTION}/points/delete`, { points: [id] });
  console.log(res.status === 'ok' ? `🗑️  Point ${id} supprimé.` : `❌ Erreur: ${JSON.stringify(res)}`);
}

async function cmdStats() {
  const res = await qdrant('GET', `/collections/${COLLECTION}`);
  const info = res.result;
  console.log('📊 Collection nox-memory :');
  console.log(`   Points    : ${info.points_count}`);
  console.log(`   Segments  : ${info.segments_count}`);
  console.log(`   Statut    : ${info.status}`);
  console.log(`   Modèle    : ${EMBED_MODEL} (${EMBED_DIM} dims)`);
}

async function cmdImportMd(file) {
  const fs = require('fs');
  const text = fs.readFileSync(file, 'utf8');
  const chunks = [];
  let current = '';
  for (const line of text.split('\n')) {
    if ((line.startsWith('## ') || line.startsWith('### ')) && current.trim()) {
      chunks.push(current.trim());
      current = line;
    } else {
      current += '\n' + line;
    }
  }
  if (current.trim()) chunks.push(current.trim());
  const valid = chunks.filter(c => c.length > 20);
  console.log(`📥 Import de ${valid.length} chunks depuis ${file}...`);
  for (let i = 0; i < valid.length; i++) {
    process.stdout.write(`  [${i+1}/${valid.length}] `);
    try {
      await cmdAdd(valid[i], { type: 'semantic', importance: '4', tags: 'memory.md,import' });
    } catch(e) {
      console.error('Erreur:', e.message);
    }
    await new Promise(r => setTimeout(r, 300));
  }
  console.log('\n✅ Import terminé !');
}

// ─── Main ──────────────────────────────────────────────────────────────────────

async function main() {
  const args = process.argv.slice(2);
  const cmd  = args[0];
  const opts = {};
  const positional = [];
  for (let i = 1; i < args.length; i++) {
    if (args[i].startsWith('--')) {
      opts[args[i].slice(2)] = args[i+1];
      i++;
    } else {
      positional.push(args[i]);
    }
  }

  switch (cmd) {
    case 'add':
      if (!positional[0]) { console.error('Usage: add "texte"'); process.exit(1); }
      await cmdAdd(positional[0], opts);
      break;
    case 'search':
      if (!positional[0]) { console.error('Usage: search "query"'); process.exit(1); }
      await cmdSearch(positional[0], opts);
      break;
    case 'list':
      await cmdList(opts);
      break;
    case 'delete':
      if (!positional[0]) { console.error('Usage: delete <id>'); process.exit(1); }
      await cmdDelete(positional[0]);
      break;
    case 'stats':
      await cmdStats();
      break;
    case 'import-md':
      if (!positional[0]) { console.error('Usage: import-md <fichier.md>'); process.exit(1); }
      await cmdImportMd(positional[0]);
      break;
    default:
      console.log(`
nox-memory — Mémoire vectorielle (Qdrant + OpenAI embeddings)

  add "texte"        Ajouter un souvenir
    --type           fact | semantic | preference | episodic  (défaut: fact)
    --tags           "tag1,tag2"
    --importance     1-5  (défaut: 3)

  search "query"     Recherche sémantique
    --limit          Résultats (défaut: 5)
    --type           Filtrer par type

  list               Lister les souvenirs
    --limit          Nombre (défaut: 20)
    --type           Filtrer par type

  delete <id>        Supprimer un point
  stats              Stats de la collection
  import-md <file>   Importer un fichier Markdown
`);
  }
}

main().catch(e => { console.error('Fatal:', e.message); process.exit(1); });
