import { describe, it, expect } from 'vitest';
import { lenientJsonParse } from '@/lib/ai/lenientJson';

describe('lenientJsonParse', () => {
  it('returns null on empty input', () => {
    expect(lenientJsonParse('')).toBeNull();
  });

  it('returns null when no { is present', () => {
    expect(lenientJsonParse('hello world')).toBeNull();
  });

  it('returns the object when input is already valid', () => {
    expect(lenientJsonParse('{"a":1,"b":[2,3]}')).toEqual({ a: 1, b: [2, 3] });
  });

  it('strips ```json fences', () => {
    expect(lenientJsonParse('```json\n{"a":1}\n```')).toEqual({ a: 1 });
  });

  it('handles fences not yet closed (still streaming)', () => {
    expect(lenientJsonParse('```json\n{"a":1, "b":2')).toEqual({ a: 1, b: 2 });
  });

  it('finds the first { after preamble', () => {
    expect(lenientJsonParse('Here you go:\n{"name":"x"}')).toEqual({
      name: 'x',
    });
  });

  it('auto-closes a partial object missing its closing }', () => {
    const got = lenientJsonParse('{"name":"X","durationWeeks":4');
    expect(got).toEqual({ name: 'X', durationWeeks: 4 });
  });

  it('auto-closes a partial array missing its closing ]', () => {
    const got = lenientJsonParse('{"weeks":[1,2,3');
    expect(got).toEqual({ weeks: [1, 2, 3] });
  });

  it('drops a dangling property key with no value yet', () => {
    const got = lenientJsonParse('{"name":"X","notes":');
    expect(got).toEqual({ name: 'X' });
  });

  it('drops a trailing comma after a complete value', () => {
    const got = lenientJsonParse('{"a":1,"b":2,');
    expect(got).toEqual({ a: 1, b: 2 });
  });

  it('handles a partial nested structure typical of AI program output', () => {
    const partial = `{
      "name": "Test",
      "type": "hypertrophy",
      "durationWeeks": 4,
      "weeks": [
        {
          "weekNumber": 1,
          "days": [
            {
              "dayOfWeek": 1,
              "name": "Push",
              "exercises": [
                {"exerciseId": "abc", "exerciseName": "Bench", "order": 0, "sets": 4
    `;
    const got = lenientJsonParse(partial) as Record<string, any>;
    expect(got).toBeTruthy();
    expect(got.name).toBe('Test');
    expect(Array.isArray(got.weeks)).toBe(true);
    expect(got.weeks[0].weekNumber).toBe(1);
    // The dangling exercise object may or may not be present
    // depending on truncation; what matters is the parser didn't
    // throw.
  });

  it('handles an open string at the end', () => {
    const got = lenientJsonParse('{"description":"A long descrip');
    expect(got).toBeTruthy();
    expect((got as Record<string, string>).description).toMatch(
      /^A long descrip/,
    );
  });

  it('returns null for unrecoverable garbage', () => {
    // Mismatched closing brace before any opening is unrecoverable
    expect(lenientJsonParse('}}}')).toBeNull();
  });
});
