import { describe, expect, it } from 'vitest';
import {
  irCaseFixUser,
  repairFeaturesUser,
  rhaUser,
  stubFixUser,
} from '../src/core/prompts';
import type { FeatureNode, FeatureSpec, RepairInput } from '../src/core/types';

const SENTINEL = '__BROKEN_UNIT_BODY_SENTINEL_9f3a__';

function feature(id: string): FeatureNode {
  return {
    id,
    name: id,
    description: 'desc',
    parentId: null,
    deps: [],
    files: [`src/${id.toLowerCase()}.ts`],
    testFile: `tests/${id.toLowerCase()}.test.ts`,
    edgeCases: ['e'],
    exports: [],
    envVars: [],
    stubs: [{ path: `src/${id.toLowerCase()}.ts`, content: 'export {}' }],
  };
}

describe('prompt purity (fresh-only)', () => {
  it('stubFixUser never embeds broken stub content', () => {
    const prompt = stubFixUser('src/foo.ts', {
      diagnostics: 'TS2305: missing bar',
      callingLines: 'foo.bar()',
      contractText: 'exports: bar',
    });
    expect(prompt).not.toContain(SENTINEL);
    expect(prompt).not.toContain('Current content');
  });

  it('irCaseFixUser regenerates without broken case body', () => {
    const spec: FeatureSpec = {
      title: 't',
      prompt: 'p',
      features: [feature('F1')],
    };
    const prompt = irCaseFixUser(
      spec,
      spec.features[0],
      [{ name: 'does x', kind: 'behavior', errors: ['TS2322'] }],
      [],
      [],
    );
    expect(prompt).not.toContain(SENTINEL);
    expect(prompt).not.toContain('current body');
    // Even if a caller tried to plant a sentinel as "body", the builder
    // no longer accepts that field — plant it in errors to prove isolation.
    const planted = irCaseFixUser(
      spec,
      spec.features[0],
      [{ name: 'x', kind: 'behavior', errors: ['err'] }],
      [],
      [],
    );
    expect(planted).not.toContain(SENTINEL);
  });

  it('repairFeaturesUser strips broken offender fields', () => {
    const broken = {
      ...feature('F2'),
      files: ['src/bad.ts'],
      stubs: [{ path: 'src/bad.ts', content: SENTINEL }],
    };
    const good = feature('F1');
    const prompt = repairFeaturesUser(
      { title: 't', features: [good, broken] },
      ['F2'],
      ['F2 has no edge cases'],
    );
    expect(prompt).not.toContain(SENTINEL);
  });

  it('rhaUser withholds the broken impl when NO ownedFiles are supplied (entropy reset)', () => {
    const f = feature('F1');
    const input: RepairInput = {
      spec: { title: 't', prompt: 'p', features: [f] },
      feature: f,
      findings: [{
        featureId: 'F1',
        bugClass: 'test-failure',
        file: 'src/f1.ts',
        line: 1,
        message: 'expected 1',
      }],
      parallelInstances: [],
      frozenTest: 'describe("x", () => { it("y", () => {}); });',
      ownedFiles: [{ path: 'src/f1.ts', content: SENTINEL }],
      dependencyContext: [],
      failureOutput: 'fail',
      attempt: 1,
      lineage: [],
    };
    // The reset path: caller supplies nothing, prompt shows nothing.
    const prompt = rhaUser({ ...input, ownedFiles: [] });
    expect(prompt).not.toContain(SENTINEL);
    expect(prompt).not.toContain('Current owned files');
    expect(prompt).toContain('fresh from the frozen contract');
  });

  /**
   * SET 80 — the purity invariant is CALLER-CONTROLLED, not absolute. A
   * targeted repair supplies the node deliberately and asks for an edit;
   * withholding it there forced the model to re-derive working code.
   */
  it('rhaUser SHOWS the node when ownedFiles are supplied (repair-in-place)', () => {
    const f = feature('F1');
    const input: RepairInput = {
      spec: { title: 't', prompt: 'p', features: [f] },
      feature: f,
      findings: [{
        featureId: 'F1', bugClass: 'test-failure', file: 'src/f1.ts', line: 1, message: 'expected 1',
      }],
      parallelInstances: [],
      frozenTest: 'describe("x", () => { it("y", () => {}); });',
      ownedFiles: [{ path: 'src/f1.ts', content: SENTINEL }],
      dependencyContext: [],
      failureOutput: 'fail',
      attempt: 1,
      lineage: [],
    };
    const prompt = rhaUser(input);
    expect(prompt).toContain(SENTINEL);
    expect(prompt).toContain('this is the node you are repairing');
    expect(prompt).toContain('Keep every exported signature');
    expect(prompt).not.toContain('fresh from the frozen contract');
  });
});
