/**
 * SET 38d — planner-bypass residue.
 * Bypass must NOT fire for soft/buffer-only matches; consumable unused still fires.
 * Wire remaining families (determinism, rejects-empty, roundtrip, model-schema).
 */
import { describe, expect, it } from 'vitest';
import type { FeatureNode, FeatureSpec } from '../src/core/types';
import {
  countMatchingTemplatesForPlan,
  countSoftMatchingTemplatesForPlan,
  formatPlannerBypass,
} from '../src/tier1/reportEvidence';
import {
  edgeCladeFamilyHint,
  planSuite,
  injectClassAnchor,
  type PlannedCase,
} from '../src/tier1/planSuite';
import { applyTemplates, matchTemplate, inferEdgeFamily } from '../src/tier1/testgen';

function feature(partial: Partial<FeatureNode> & { id: string }): FeatureNode {
  return {
    name: partial.name ?? partial.id,
    description: partial.description ?? 'd',
    parentId: null,
    deps: partial.deps ?? [],
    files: partial.files ?? [`src/${partial.id.toLowerCase()}.ts`],
    testFile: partial.testFile ?? `tests/${partial.id.toLowerCase()}.test.ts`,
    edgeCases: partial.edgeCases ?? [],
    envVars: [],
    exports: partial.exports ?? [{ file: `src/${partial.id.toLowerCase()}.ts`, symbol: 'fn' }],
    stubs: partial.stubs ?? [{
      path: `src/${partial.id.toLowerCase()}.ts`,
      content: 'export function fn(x: string): string { throw new Error("stub: not implemented"); }\n',
    }],
    ...partial,
  };
}

describe('SET 38d planner-consumable wiring', () => {
  it('tags edge cases with consumable family hints', () => {
    expect(edgeCladeFamilyHint('roundtrip')).toBe('roundtrip');
    expect(edgeCladeFamilyHint('rejects-empty')).toBe('rejects-empty');
    expect(edgeCladeFamilyHint('empty input')).toBe('rejects-empty');
    expect(edgeCladeFamilyHint('determinism')).toBe('determinism');
    expect(inferEdgeFamily('roundtrip')).toBe('roundtrip');

    const f = feature({
      id: 'F1',
      edgeCases: ['roundtrip', 'empty', 'determinism'],
    });
    const spec: FeatureSpec = { title: 'T', prompt: 'p', features: [f] };
    const plan = planSuite(spec, f);
    expect(plan.cases.find(c => c.name === 'roundtrip')?.cladeFamily).toBe('roundtrip');
    expect(plan.cases.find(c => c.name === 'empty')?.cladeFamily).toBe('rejects-empty');
    expect(plan.cases.find(c => c.name === 'determinism')?.cladeFamily).toBe('determinism');
  });

  it('applyTemplates substrate-fills roundtrip / rejects-empty / determinism / model-schema', () => {
    const f = feature({
      id: 'F1',
      edgeCases: ['roundtrip', 'empty'],
    });
    const spec: FeatureSpec = { title: 'T', prompt: 'p', features: [f] };
    const plan = planSuite(spec, f);
    const fill = applyTemplates(plan, f.edgeCases);
    expect(fill.templated).toBeGreaterThan(0);
    expect(fill.events.some(e => /roundtrip|rejects-empty/.test(e))).toBe(true);

    const schema: FeatureNode = {
      id: 'F9',
      name: 'Schema',
      description: 'models',
      parentId: null,
      deps: [],
      files: ['src/schema.ts'],
      testFile: 'tests/schema.test.ts',
      edgeCases: [],
      envVars: [],
      exports: [{ file: 'src/schema.ts', symbol: 'ArticleModel' }],
      stubs: [{
        path: 'src/schema.ts',
        content: 'export class ArticleModel { constructor(_f: Record<string, unknown>) { throw new Error("stub"); } }\n',
      }],
    };
    const sSpec: FeatureSpec = { title: 'S', prompt: 'p', features: [schema] };
    const { plan: sPlan } = injectClassAnchor(planSuite(sSpec, schema), schema);
    const sFill = applyTemplates(sPlan, []);
    expect(sFill.templated).toBeGreaterThan(0);
    expect(sFill.events.some(e => e.includes('model-schema-construct'))).toBe(true);
  });
});

describe('SET 38d planner bypass tripwire (no false alarms)', () => {
  it('bypass does not fire for non-consumable-only soft matches', () => {
    // Soft-match can hit determinism smoke via name overlap on unrelated text,
    // but consumable count stays 0 when nothing matchTemplate can apply.
    const cases: PlannedCase[] = [{
      name: 'weird-unrelated-case',
      kind: 'edge',
      license: 'unicode emoji handling',
      source: 'edge',
      symbol: 'fn',
      // Signature incompatible with all bank templates (0 params vs 1/2).
      signature: { name: 'fn', params: [], returnType: 'void' },
    }];
    const soft = countSoftMatchingTemplatesForPlan(cases, ['unicode emoji handling']);
    const consumable = countMatchingTemplatesForPlan(cases, ['unicode emoji handling']);
    // Soft may still find name hits; consumable must be 0 → no bypass.
    expect(soft).toBeGreaterThanOrEqual(0);
    expect(consumable).toBe(0);
    // Runner only fires when consumable >= 1 && templated === 0.
    expect(consumable >= 1).toBe(false);
  });

  it('consumable unused still fires bypass vocabulary', () => {
    const cases: PlannedCase[] = [{
      name: 'roundtrip',
      kind: 'edge',
      license: 'roundtrip',
      source: 'edge',
      symbol: 'fn',
      cladeFamily: 'roundtrip',
      signature: { name: 'fn', params: [{ name: 'x', type: 'string' }], returnType: 'string' },
    }];
    const n = countMatchingTemplatesForPlan(cases, ['roundtrip']);
    expect(n).toBeGreaterThanOrEqual(1);
    expect(matchTemplate(cases[0]!, ['roundtrip'])?.family).toBe('roundtrip');
    expect(formatPlannerBypass(n)).toBe(`planner bypass: ${n} matching templates unused`);
  });
});
