/**
 * Step-conventions — `return` pattern safety tests.
 *
 * The 2026-05-11 per-owner trial surfaced a defect: when a spec step
 * like `Return logout confirmation` matched the `^return\s+(.+)/i`
 * pattern, the convention emitted `return logout confirmation;` —
 * two bare identifiers after `return`, a parse error. Per-action
 * passed this through because each body parsed in isolation only
 * checked the wrapping body, not the surrounding file. Per-owner
 * caught it because the whole-file parse check failed. Either way,
 * runtime would have broken.
 *
 * Fix: the convention now ONLY emits when the value is a single
 * bareword identifier (or simple property accessor) AND was
 * previously declared in the action's emitted body. Otherwise
 * returns the empty string so the matcher falls through to the AI
 * fallback (LLM authors the return).
 */

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

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

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

describe("prisma step-convention `return` — only emits parseable TS", () => {
  it("emits the model var for `Return updated order` (model-pattern matches)", () => {
    const convention = findReturnConvention();
    const m = 'Return updated order'.match(convention.pattern)!;
    const call = convention.generateCall(m, makeCtx());
    expect(call).toContain('return order;');
  });

  it("emits `return <name>;` when the value is a single identifier already declared", () => {
    const convention = findReturnConvention();
    const m = 'Return user'.match(convention.pattern)!;
    const ctx = makeCtx({ declaredVars: new Set(['user']) });
    const call = convention.generateCall(m, ctx);
    expect(call).toContain('return user;');
  });

  it("emits `return <obj>.<prop>;` for a simple property accessor when head is declared", () => {
    const convention = findReturnConvention();
    const m = 'Return result.payload'.match(convention.pattern)!;
    const ctx = makeCtx({ declaredVars: new Set(['result']) });
    const call = convention.generateCall(m, ctx);
    expect(call).toContain('return result.payload;');
  });

  it("REGRESSION: prose phrase 'logout confirmation' returns empty (bounces to LLM)", () => {
    // Pre-fix this emitted `return logout confirmation;` — two bare
    // identifiers, parse error. New behaviour: empty string → matcher
    // treats as no match → AI fallback path.
    const convention = findReturnConvention();
    const m = 'Return logout confirmation'.match(convention.pattern)!;
    const call = convention.generateCall(m, makeCtx());
    expect(call).toBe('');
  });

  it("REGRESSION: '204 No Content' returns empty (number + identifiers is a parse error)", () => {
    const convention = findReturnConvention();
    const m = 'Return 204 No Content'.match(convention.pattern)!;
    const call = convention.generateCall(m, makeCtx());
    expect(call).toBe('');
  });

  it("REGRESSION: a PascalCase type name that was NEVER declared returns empty", () => {
    // `return AuthRegisterResponse;` looks like valid TS but references
    // a type as a value — won't resolve at runtime. Only emit when the
    // identifier was actually declared in the body.
    const convention = findReturnConvention();
    const m = 'Return AuthRegisterResponse'.match(convention.pattern)!;
    const call = convention.generateCall(m, makeCtx()); // declaredVars empty
    expect(call).toBe('');
  });

  it("when the identifier IS declared, it WILL emit (the LLM put the value in scope)", () => {
    const convention = findReturnConvention();
    const m = 'Return AuthRegisterResponse'.match(convention.pattern)!;
    const ctx = makeCtx({ declaredVars: new Set(['AuthRegisterResponse']) });
    const call = convention.generateCall(m, ctx);
    expect(call).toContain('return AuthRegisterResponse;');
  });

  it("falls back for whitespace-y multi-word values that aren't valid identifiers", () => {
    const convention = findReturnConvention();
    const m = 'Return the cached invoice if still fresh'.match(convention.pattern)!;
    const call = convention.generateCall(m, makeCtx());
    // "the cached invoice..." DOES start with "the" so model-pattern
    // applies — emits the model var. That's the existing behaviour
    // and intentional.
    expect(call).toContain('return order;');
  });

  it("regression: empty result string causes the matcher to fall through", async () => {
    // Smoke-test the integration: import the shared matcher and feed
    // it a prose 'Return X' step. Expect matched: false (AI fallback).
    const { matchAgainstConventions } = await import('../../_shared/step-matching.js');
    const ctx = makeCtx();
    const result = matchAgainstConventions(
      'Return logout confirmation',
      ctx,
      STEP_CONVENTIONS as any,
      (inputs: string[]) => (inputs.length > 0 ? `{ ${inputs.join(', ')} }` : '{}'),
    );
    expect(result.matched).toBe(false);
  });
});
