/**
 * SET 31d residual — planner half.
 *
 * Retrieval already puts templates in the superbuffer; plans still showed
 * `0 substrate-templated` because model-schema (*Model) and uses-dep cases
 * were never matched by applyTemplates. These fixtures prove M>0.
 */
import { describe, expect, it } from 'vitest';
import type { FeatureNode, FeatureSpec } from '../src/core/types';
import {
  caseFamily,
  classifyCaseClade,
  isModelSchemaName,
  MODEL_SCHEMA_CLADE,
  plannedCladeFamily,
  rebindCladeBody,
  USES_DEP_CLADE,
} from '../src/tier1/clades';
import { injectClassAnchor, planSuite } from '../src/tier1/planSuite';
import { applyTemplates, matchTemplate } from '../src/tier1/testgen';
import { TESTGEN_TEMPLATES } from '../src/tier1/testgenOracle.generated';

function blogSpec(): FeatureSpec {
  const schema: FeatureNode = {
    id: 'F1',
    name: 'Data Model Definition (Schema)',
    description: 'core models',
    parentId: null,
    deps: [],
    files: ['src/schema.ts'],
    testFile: 'tests/f1.test.ts',
    edgeCases: ['unique-constraint'],
    envVars: [],
    exports: [
      { file: 'src/schema.ts', symbol: 'ArticleModel' },
      { file: 'src/schema.ts', symbol: 'UserModel' },
    ],
    stubs: [{
      path: 'src/schema.ts',
      content:
        'export class ArticleModel { constructor(_f: Record<string, unknown>) { throw new Error("stub"); } }\n'
        + 'export class UserModel { constructor(_f: Record<string, unknown>) { throw new Error("stub"); } }\n',
    }],
  };
  const auth: FeatureNode = {
    id: 'F2',
    name: 'Auth Session',
    description: 'sessions over schema',
    parentId: null,
    deps: ['F1'],
    files: ['src/session.ts'],
    testFile: 'tests/f2.test.ts',
    edgeCases: [],
    envVars: [],
    exports: [{ file: 'src/session.ts', symbol: 'SessionModel' }],
    stubs: [{
      path: 'src/session.ts',
      content: 'export class SessionModel { constructor(_f: Record<string, unknown>) { throw new Error("stub"); } }\n',
    }],
  };
  return { title: 'Blog', prompt: 'blog with models', features: [schema, auth] };
}

describe('SET 31d planner half — oracle templates present', () => {
  it('freezes model-schema and uses-dep templates in the oracle', () => {
    expect(TESTGEN_TEMPLATES.some(t => t.family === MODEL_SCHEMA_CLADE)).toBe(true);
    expect(TESTGEN_TEMPLATES.some(t => t.family === USES_DEP_CLADE)).toBe(true);
    expect(TESTGEN_TEMPLATES.find(t => t.family === MODEL_SCHEMA_CLADE)?.id).toBe('model-schema-construct');
    expect(TESTGEN_TEMPLATES.find(t => t.family === USES_DEP_CLADE)?.id).toBe('uses-dep-seam');
  });
});

describe('SET 31d planner half — planSuite tags structural clades', () => {
  it('tags *Model export cases as model-schema and dep cases as uses-dep', () => {
    const spec = blogSpec();
    const plan = planSuite(spec, spec.features[0]!);
    const article = plan.cases.find(c => c.name === 'ArticleModel');
    const user = plan.cases.find(c => c.name === 'UserModel');
    expect(article?.cladeFamily).toBe(MODEL_SCHEMA_CLADE);
    expect(user?.cladeFamily).toBe(MODEL_SCHEMA_CLADE);
    expect(isModelSchemaName('SessionModel')).toBe(true);
    expect(isModelSchemaName('calc')).toBe(false);

    const f2 = planSuite(spec, spec.features[1]!);
    const uses = f2.cases.find(c => c.kind === 'integration');
    expect(uses?.name).toBe('uses Data Model Definition (Schema)');
    expect(uses?.cladeFamily).toBe(USES_DEP_CLADE);
    expect(uses?.symbol).toBe('ArticleModel');
    expect(plannedCladeFamily(uses!)).toBe(USES_DEP_CLADE);
  });

  it('injectClassAnchor tags injected *Model anchors as model-schema', () => {
    const feature: FeatureNode = {
      id: 'F9', name: 'Extra', description: 'd', parentId: null, deps: [],
      files: ['src/extra.ts'], testFile: 'tests/extra.test.ts',
      edgeCases: [], envVars: [],
      exports: [],
      stubs: [{
        path: 'src/extra.ts',
        content: 'export class CategoryModel { constructor(_f: Record<string, unknown>) {}\n}\n',
      }],
    };
    const spec: FeatureSpec = { title: 'S', prompt: 'p', features: [feature] };
    const { plan, injected } = injectClassAnchor(planSuite(spec, feature), feature);
    expect(injected?.name).toBe('CategoryModel');
    expect(injected?.cladeFamily).toBe(MODEL_SCHEMA_CLADE);
    expect(plan.cases[0]?.cladeFamily).toBe(MODEL_SCHEMA_CLADE);
  });
});

describe('SET 31d planner half — applyTemplates substrate-fills M>0', () => {
  it('templates *Model behavior cases via model-schema (M>0, no model draw for anchors)', () => {
    const spec = blogSpec();
    const feature = spec.features[0]!;
    const plan = planSuite(spec, feature);
    const fill = applyTemplates(plan, feature.edgeCases);

    expect(fill.templated).toBeGreaterThan(0);
    const modelCases = fill.cases.filter(c => /Model$/.test(c.name));
    expect(modelCases.length).toBe(2);
    expect(modelCases.every(c => c.clade?.family === MODEL_SCHEMA_CLADE)).toBe(true);
    expect(modelCases.every(c => /new ArticleModel|new UserModel/.test(c.body))).toBe(true);
    expect(fill.events.some(e => e.includes('model-schema-construct'))).toBe(true);
    // Anchors are no longer holes.
    expect(fill.holes.some(h => h.name === 'ArticleModel')).toBe(false);
    expect(fill.holes.some(h => h.name === 'UserModel')).toBe(false);
  });

  it('templates uses-dep integration cases via uses-dep-seam', () => {
    const spec = blogSpec();
    const feature = spec.features[1]!;
    const plan = planSuite(spec, feature);
    const usesCase = plan.cases.find(c => c.source === 'dep')!;
    expect(matchTemplate(usesCase, feature.edgeCases)?.id).toBe('uses-dep-seam');

    const fill = applyTemplates(plan, feature.edgeCases);
    expect(fill.templated).toBeGreaterThan(0);
    const uses = fill.cases.find(c => c.kind === 'integration');
    expect(uses).toBeDefined();
    expect(uses!.clade?.family).toBe(USES_DEP_CLADE);
    expect(uses!.body).toContain('ArticleModel');
    expect(fill.holes.some(h => h.kind === 'integration')).toBe(false);
  });

  it('a schema+consumer plan reports M>0 substrate-templated for both clades', () => {
    const spec = blogSpec();
    let totalTemplated = 0;
    for (const feature of spec.features) {
      const { plan } = injectClassAnchor(planSuite(spec, feature), feature);
      const fill = applyTemplates(plan, feature.edgeCases);
      totalTemplated += fill.templated;
    }
    expect(totalTemplated).toBeGreaterThan(0);
    // F1: 2 *Model; F2: SessionModel + uses-dep ≥ 3 templated across the run.
    expect(totalTemplated).toBeGreaterThanOrEqual(3);
  });

  it('keeps bare non-Model behavior exports as holes (SET 2 license rule intact)', () => {
    const feature: FeatureNode = {
      id: 'F1', name: 'calc', description: 'adds', parentId: null, deps: [],
      files: ['src/calc.ts'], testFile: 'tests/f1.test.ts',
      edgeCases: [], envVars: [],
      exports: [{ file: 'src/calc.ts', symbol: 'calc' }],
      stubs: [{
        path: 'src/calc.ts',
        content: 'export function calc(a: number, b: number): number { throw new Error("stub"); }\n',
      }],
    };
    const spec: FeatureSpec = { title: 'C', prompt: 'c', features: [feature] };
    const plan = planSuite(spec, feature);
    const fill = applyTemplates(plan, []);
    expect(fill.templated).toBe(0);
    expect(fill.holes.some(h => h.name === 'calc' && h.kind === 'behavior')).toBe(true);
  });
});

describe('SET 31d planner half — clade classify / rebind / caseFamily', () => {
  it('classifyCaseClade + rebindCladeBody cover model-schema and uses-dep', () => {
    const model = classifyCaseClade({ name: 'ArticleModel', kind: 'behavior', body: 'expect(true).toBe(true);' }, 'ArticleModel');
    expect(model.family).toBe(MODEL_SCHEMA_CLADE);
    expect(rebindCladeBody({ name: 'ArticleModel', kind: 'behavior', body: '', clade: model })).toMatch(/new ArticleModel/);

    const uses = classifyCaseClade({
      name: 'uses Data Model Definition (Schema)',
      kind: 'integration',
      body: 'expect(true).toBe(true);',
    }, 'ArticleModel');
    expect(uses.family).toBe(USES_DEP_CLADE);
    expect(rebindCladeBody({
      name: 'uses Data Model Definition (Schema)',
      kind: 'integration',
      body: '',
      clade: { ...uses, bindings: { symbol: 'ArticleModel' } },
    })).toMatch(/ArticleModel/);
  });

  it('caseFamily collapses uses <dep> names onto uses-dep', () => {
    expect(caseFamily('uses Data Model Definition (Schema)')).toBe(USES_DEP_CLADE);
    expect(caseFamily('uses Auth')).toBe(USES_DEP_CLADE);
    expect(caseFamily('ArticleModel')).toBe('model-schema:*Model');
  });
});
