/**
 * Step-conventions — `create` pattern reserved-word regression.
 *
 * Surfaced 2026-05-14 by the 9-cell comparison matrix: sonnet's
 * idle-meta realize γ-stubbed `PlayersController.post0` with the
 * precise diagnostic "pre-baked step 7 uses reserved keyword 'new' as
 * a variable identifier — cannot emit coherently". The pre-baked
 * snippet was:
 *
 *   // Step 7: Create new
 *   const new = await prisma.new.create({ data: data });
 *
 * Root cause: prisma's `create` convention pattern was
 * `/^create\s+(\w+).../i` — for the step "Create new Player with level 1",
 * m[1] captured the adjective "new" rather than the noun "Player".
 * Then toVar("new") = "new" (a JS reserved word) became both the
 * variable name AND the prisma model name in the generated snippet,
 * neither of which is legal/correct.
 *
 * mongo and pg conventions already had `(?:new\s+)?` in their create
 * patterns — this was a parity gap. Fix: add the same optional group
 * to prisma's create pattern. After the fix, m[1] = "Player" → snippet
 * uses `prisma.player.create`.
 */

import { describe, it, expect } from 'vitest';
import { STEP_CONVENTIONS } from '../step-conventions.js';
import type { StepContext } from '../step-conventions.js';

function findCreateConvention() {
  const c = STEP_CONVENTIONS.find((x) => x.name === 'create');
  expect(c).toBeDefined();
  return c!;
}

function makeCtx(overrides: Partial<StepContext> = {}): StepContext {
  return {
    modelName: 'Player',
    serviceName: 'PlayersController',
    operationName: 'post0',
    stepNum: 7,
    parameterNames: [],
    declaredVars: new Set<string>(),
    ...overrides,
  } as any;
}

describe('prisma step-convention `create` — adjective-skip + entity-name correctness', () => {
  it('extracts the entity noun, not the adjective, from "Create new Player with …"', () => {
    const convention = findCreateConvention();
    const m = 'Create new Player with level 1, experience 0'.match(convention.pattern)!;
    expect(m[1]).toBe('Player');
  });

  it('emits a snippet referencing `prisma.player` (not `prisma.new`) for the adjective case', () => {
    const convention = findCreateConvention();
    const m = 'Create new Player with level 1, experience 0'.match(convention.pattern)!;
    const call = convention.generateCall(m, makeCtx())!;
    expect(call).toContain('prisma.player.create');
    expect(call).not.toContain('prisma.new');
    expect(call).not.toContain('const new ');
  });

  it('still works for the no-adjective case "Create Player with …" (no regression)', () => {
    const convention = findCreateConvention();
    const m = 'Create Player with level 1'.match(convention.pattern)!;
    expect(m[1]).toBe('Player');
    const call = convention.generateCall(m, makeCtx())!;
    expect(call).toContain('prisma.player.create');
  });

  it('uses parameter names for the data shape when provided', () => {
    const convention = findCreateConvention();
    const m = 'Create new Player with level 1'.match(convention.pattern)!;
    const call = convention.generateCall(m, makeCtx({ parameterNames: ['userId', 'gameId'] }))!;
    expect(call).toContain('{ userId, gameId }');
  });

  it('emits a syntactically valid `const <var> = await prisma.<var>.create(...)` shape', () => {
    const convention = findCreateConvention();
    const m = 'Create new Player with level 1'.match(convention.pattern)!;
    const call = convention.generateCall(m, makeCtx())!;
    // const <var> = await prisma.<var>.create(...);
    // Variable name must NOT be a JS reserved word; both the variable
    // and the prisma model reference should be the lowercased entity.
    expect(call).toMatch(/const\s+player\s+=\s+await\s+prisma\.player\.create/);
  });

  // Wider adjective list (engines 6.60.4) — kept in sync with
  // NL_LEADING_ADJECTIVES in _shared/step-matching.ts.
  it.each([
    ['existing', 'Create existing Player with level 1'],
    ['current',  'Create current Player'],
    ['the',      'Create the Player with level 1'],
    ['a',        'Create a Player with level 1'],
    ['an',       'Create an Order with total 100'],
  ])('skips the leading adjective %s (no regression for any in the list)', (_adj, step) => {
    const convention = findCreateConvention();
    const m = step.match(convention.pattern)!;
    // m[1] is the noun (Player/Order), never the adjective.
    expect(['Player', 'Order']).toContain(m[1]);
    const call = convention.generateCall(m, makeCtx())!;
    expect(call).not.toMatch(/const\s+(existing|current|the|a|an)\s+=/);
    expect(call).not.toMatch(/prisma\.(existing|current|the|a|an)\./);
  });
});

describe('prisma step-convention `create` — reserved-word safety net', () => {
  // The pattern's leading-adjective skip catches the common cases. The
  // safety net catches anything that still leaks a JS reserved word as
  // the entity name (an adjective not in the skip list, or a mis-parse).
  // generateCall returns '' → the matcher falls through to the AI
  // [WRITE] fallback instead of emitting `const class = await prisma.class...`.
  it.each([
    ['class',    'Create class Foo'],
    ['function', 'Create function Bar'],
    ['delete',   'Create delete Marker'],
    ['return',   'Create return Receipt'],
  ])('returns empty string when m[1] would be reserved word "%s"', (reserved, step) => {
    const convention = findCreateConvention();
    const m = step.match(convention.pattern);
    // The pattern still matches structurally; the generateCall guard refuses.
    if (m) {
      expect(m[1]!.toLowerCase()).toBe(reserved);
      const call = convention.generateCall(m, makeCtx());
      expect(call).toBe('');
    }
  });

  it('matches but refuses "Create new class" (post-adjective-skip the captured noun is reserved)', () => {
    const convention = findCreateConvention();
    const m = 'Create new class'.match(convention.pattern)!;
    expect(m[1]).toBe('class');
    expect(convention.generateCall(m, makeCtx())).toBe('');
  });
});

describe('prisma step-conventions — extracted-input reserved-word safety (transition + call-service)', () => {
  // 2026-05-15 audit of all toVar(extracted) sites. Same shape as the
  // create-convention safety net above: when the captured noun lowercases
  // to a TS reserved word, generateCall returns '' so the matcher falls
  // through to the AI [WRITE] fallback instead of emitting illegal TS.

  function findConvention(name: string) {
    const c = STEP_CONVENTIONS.find((x) => x.name === name);
    expect(c).toBeDefined();
    return c!;
  }

  it.each([
    ['delete',  'transition delete to active'],
    ['return',  'transition return to closed'],
    ['class',   'transition class to graduated'],
  ])('transition: refuses reserved-word model "%s"', (_reserved, step) => {
    const convention = findConvention('transition');
    const m = step.match(convention.pattern)!;
    expect(convention.generateCall(m, makeCtx())).toBe('');
  });

  it('transition: still works for a real model name (no regression)', () => {
    const convention = findConvention('transition');
    const m = 'transition Player to active'.match(convention.pattern)!;
    const ctx = makeCtx({ declaredVars: new Set(['player']) });
    const call = convention.generateCall(m, ctx)!;
    expect(call).toContain('prisma.player.update');
    expect(call).toContain("'active'");
  });

  it.each([
    ['delete',  'call delete.purge'],
    ['return',  'call return.process'],
  ])('call-service: refuses reserved-word service name "%s"', (_reserved, step) => {
    const convention = findConvention('call-service');
    const m = step.match(convention.pattern)!;
    expect(convention.generateCall(m, makeCtx())).toBe('');
  });

  it('call-service: still works for a real service name (no regression)', () => {
    const convention = findConvention('call-service');
    const m = 'call notificationService.send'.match(convention.pattern)!;
    const call = convention.generateCall(m, makeCtx())!;
    expect(call).toContain('notificationService.send');
  });
});
