/**
 * Contract test for the `memory` function's OpenAI schema — specifically
 * that it advertises the `reminisce` action and its sub-mode params.
 *
 * Why this exists: the OpenAI function schema is what the model sees on
 * the Chat Completions path. If `reminisce` is missing from the enum or
 * the reminisce-specific params are absent, the model can't invoke it
 * correctly regardless of what memoryTool's own validators say. This
 * tripwire catches that class of drift without booting the full CLI.
 */

import { describe, test, expect } from '@jest/globals';
import { OPENAI_FUNCTION_SCHEMAS } from '../openaiFunctionSchemas.js';

const memory = OPENAI_FUNCTION_SCHEMAS.find(s => s.name === 'memory');
// The schema is now plural-canonical: per-action fields live under
// `parameters.properties.actions.items.properties`. The action's discriminant
// is `type` (matching filesystem / terminal / taskmanager). The singular
// shape is accepted at runtime via _normalizeToActions but is no longer
// advertised in the schema — the model is taught the plural form.
const actionItem = memory.parameters.properties.actions.items.properties;

describe('OPENAI_FUNCTION_SCHEMAS — memory function (plural canonical)', () => {
  test('memory function schema exists with plural actions[] envelope', () => {
    expect(memory).toBeDefined();
    expect(memory.type).toBe('function');
    expect(memory.parameters?.type).toBe('object');
    expect(memory.parameters.properties.actions?.type).toBe('array');
    expect(memory.parameters.required).toContain('actions');
  });

  test('action type enum includes reminisce', () => {
    expect(actionItem.type.enum).toContain('reminisce');
  });

  test('action type enum covers every sanctioned action (incl. stats, append)', () => {
    for (const a of ['add', 'update', 'append', 'delete', 'read', 'list', 'search', 'stats', 'reminisce']) {
      expect(actionItem.type.enum).toContain(a);
    }
  });

  test('mode field is present and enumerates the six sub-modes', () => {
    expect(actionItem.mode).toBeDefined();
    expect(actionItem.mode.enum).toEqual(
      expect.arrayContaining(['overview', 'range', 'search', 'around', 'byTool', 'read'])
    );
  });

  test('every reminisce-specific parameter is documented in the schema', () => {
    const required = [
      'mode',
      'messageId',
      'from', 'to',
      'offset', 'limit', 'maxResults',
      'role', 'cursor', 'toolId',
      'before', 'after',
      'lineFrom', 'lineTo', 'contentFrom', 'contentTo',
      'detail',
    ];
    for (const field of required) {
      const prop = actionItem[field];
      expect(prop).toBeDefined();
      expect(typeof prop.description).toBe('string');
      expect(prop.description.length).toBeGreaterThan(0);
    }
  });

  test('around before/after descriptions say MESSAGES (not chars/lines)', () => {
    expect(actionItem.before.description).toMatch(/MESSAGES/);
    expect(actionItem.after.description).toMatch(/MESSAGES/);
  });

  test('read line/char params describe their index convention and cap', () => {
    expect(actionItem.lineFrom.description).toMatch(/line/);
    expect(actionItem.lineTo.description).toMatch(/500 lines/);
    expect(actionItem.contentFrom.description).toMatch(/character offset/);
    expect(actionItem.contentTo.description).toMatch(/16000 chars/);
  });

  test('top-level description mentions reminisce so the model knows the tool does both', () => {
    expect(memory.description.toLowerCase()).toMatch(/reminisce|archive/);
  });
});
