// R3 V-F1/V-F2 (MED, off-default vector arm): the v3.39.2 "vector-arm parity" fix added
// lesson_learned + search_aliases to the SAVE-path vector text, but (V-F2) buildVocabulary
// never counted those fields — so a term living only in a lesson/alias got no vocab dimension
// and was silently dropped — and (V-F1) six REBUILD paths still used [title,narrative,concepts],
// so the documented `maintain rebuild_vectors` step re-encoded every vector WITHOUT the lesson.
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createTestDb, insertSession, insertObs } from './test-helpers.mjs';

describe('vector field-set: lesson/aliases reach vocab + rebuild paths (R3 V-F1/V-F2)', () => {
  let db, prevVec;
  beforeEach(() => {
    prevVec = process.env.CLAUDE_MEM_VECTORS;
    process.env.CLAUDE_MEM_VECTORS = '1';
    db = createTestDb();
    insertSession(db, { id: 'sess-1', project: 'test' });
  });
  afterEach(() => {
    db.close();
    if (prevVec === undefined) delete process.env.CLAUDE_MEM_VECTORS; else process.env.CLAUDE_MEM_VECTORS = prevVec;
  });

  it('buildVocabulary gives a dimension to a term that appears only in lesson_learned (V-F2)', async () => {
    for (let i = 0; i < 3; i++) insertObs(db, {
      title: `database migration note ${i}`,
      narrative: `narrative about a schema migration rollout number ${i} touching several tables and indexes`,
      lessonLearned: 'always exercise the redisfailover path before deploy',
    });
    const { buildVocabulary, _resetVocabCache, tokenize } = await import('../tfidf.mjs');
    _resetVocabCache();
    const vocab = buildVocabulary(db);
    expect(vocab, 'vocab builds from corpus').toBeTruthy();
    const stem = tokenize('redisfailover')[0]; // tokenizer stems; assert the term in its stored form
    expect([...vocab.terms.keys()], 'lesson-only term must get a vocab dimension').toContain(stem);
  });

  it('maintain rebuild_vectors encodes the lesson field, byte-identical to the save path (V-F1)', async () => {
    // target carries a distinctive lesson-only term; 2 sibling docs give it a vocab dimension
    insertObs(db, { type: 'decision', title: 'coordination service choice', narrative: 'we picked a coordination service for leader election across the cluster nodes reliably', lessonLearned: 'zookeeper session expiry needs careful watch re-registration' });
    for (let i = 0; i < 2; i++) insertObs(db, { type: 'discovery', title: `zookeeper watch note ${i}`, narrative: `zookeeper ephemeral node and watch semantics explored ${i} for the cluster` });

    const { rebuildVectors } = await import('../lib/maintain-core.mjs');
    const { getVocabulary, _resetVocabCache, computeVector } = await import('../tfidf.mjs');
    const { buildVecText } = await import('../hook-llm.mjs');
    _resetVocabCache();
    const r = rebuildVectors(db);
    expect(r.ok).toBe(true);

    const target = db.prepare("SELECT * FROM observations WHERE title = 'coordination service choice'").get();
    const vocab = getVocabulary(db);
    const saveVec = computeVector(buildVecText({ title: target.title, narrative: target.narrative, concepts: target.concepts, lessonLearned: target.lesson_learned, searchAliases: target.search_aliases }), vocab);
    const stored = db.prepare('SELECT vector FROM observation_vectors WHERE observation_id = ?').get(target.id);
    expect(stored, 'rebuild wrote a vector').toBeTruthy();
    expect(saveVec, 'save-path vector non-null (encodes the lesson term)').toBeTruthy();
    // parity: rebuild must encode the SAME field set as save (incl. lesson) → identical vector bytes
    expect(Buffer.from(saveVec.buffer).equals(stored.vector)).toBe(true);
  });
});
