import { describe, it, expect } from 'vitest';
import { REFLECTION_TOOL_SCHEMA } from './reflection-schema.js';

// The schema is shipped verbatim to OpenAI-compatible endpoints as the tools[]
// entry, so its exact shape is load-bearing: these tests pin the contract.
describe('REFLECTION_TOOL_SCHEMA', () => {
  const fn = REFLECTION_TOOL_SCHEMA.function;
  const params = fn.parameters;

  it('is an OpenAI tools-format function definition named submit_reflection', () => {
    expect(REFLECTION_TOOL_SCHEMA.type).toBe('function');
    expect(fn.name).toBe('submit_reflection');
    expect(typeof fn.description).toBe('string');
    expect(fn.description.length).toBeGreaterThan(0);
  });

  it('is JSON-serializable (no functions / cycles)', () => {
    const roundTripped = JSON.parse(JSON.stringify(REFLECTION_TOOL_SCHEMA));
    expect(roundTripped).toEqual(REFLECTION_TOOL_SCHEMA);
  });

  it('requires memory_changes, piece_changes and reasoning at the top level', () => {
    expect(params.type).toBe('object');
    expect(params.required).toEqual(['memory_changes', 'piece_changes', 'reasoning']);
    expect(params.additionalProperties).toBe(false);
  });

  it('caps memory_changes at 3 items (matches the applier hard cap)', () => {
    const mc = params.properties.memory_changes;
    expect(mc.type).toBe('array');
    expect(mc.maxItems).toBe(3);
  });

  it('memory change items require op/name/type/description/body and forbid extras', () => {
    const item = params.properties.memory_changes.items;
    expect(item.required).toEqual(['op', 'name', 'type', 'description', 'body']);
    expect(item.additionalProperties).toBe(false);
    // merge_target is optional but declared
    expect(item.properties.merge_target).toBeDefined();
  });

  it('op enum matches ReflectionOp and type enum matches ReflectionMemoryType', () => {
    const item = params.properties.memory_changes.items;
    expect(item.properties.op.enum).toEqual(['add', 'update', 'merge_into', 'remove']);
    expect(item.properties.type.enum).toEqual(['user', 'feedback', 'project', 'reference']);
  });

  it('enforces size ceilings: name<=96, description<=240, body<=16384', () => {
    const item = params.properties.memory_changes.items;
    expect(item.properties.name.minLength).toBe(1);
    expect(item.properties.name.maxLength).toBe(96);
    expect(item.properties.description.maxLength).toBe(240);
    expect(item.properties.body.maxLength).toBe(16384);
    expect(item.properties.merge_target.maxLength).toBe(96);
  });

  it('piece_changes requires only should_edit and allows nullable new_yaml', () => {
    const pc = params.properties.piece_changes;
    expect(pc.type).toBe('object');
    expect(pc.required).toEqual(['should_edit']);
    expect(pc.additionalProperties).toBe(false);
    expect(pc.properties.should_edit.type).toBe('boolean');
    expect(pc.properties.new_yaml.type).toEqual(['string', 'null']);
    expect(pc.properties.diff_summary.maxLength).toBe(240);
  });

  it('reasoning and abstain_reason have length ceilings', () => {
    expect(params.properties.reasoning.maxLength).toBe(2000);
    expect(params.properties.abstain_reason.maxLength).toBe(500);
  });
});
