/**
 * SET 2b/2c — apply verified templates; leave holes for per-case model fill.
 * SET 31d — planner half: model-schema (*Model) + uses-dep structural clades
 * are substrate-templated without edge-text licenses.
 * SET 38d — wire remaining consumable families (determinism, rejects-empty,
 * roundtrip, model-schema) into plan templating so bypass is not a false alarm.
 */
import type { IRCaseGen } from '../core/irScaffold';
import { TESTGEN_TEMPLATES } from './testgenOracle.generated';
import type { TestgenTemplate } from './testgenOracle.generated';
import type { PlannedCase, SuitePlan } from './planSuite';
import { edgeCladeFamilyHint } from './planSuite';
import { acceptCaseBody } from './substrate';
import { cladeFromTemplate, plannedCladeFamily } from './clades';

export interface TemplatedFill {
  cases: IRCaseGen[];
  templated: number;
  holes: PlannedCase[];
  events: string[];
}

/** SET 38d — families matchTemplate can apply (planner-consumable). */
export const CONSUMABLE_TEMPLATE_FAMILIES = [
  'determinism',
  'rejects-empty',
  'roundtrip',
  'model-schema',
  'uses-dep',
] as const;

function typesMatch(sigTypes: string[], wanted: string[]): boolean {
  if (sigTypes.length !== wanted.length) return false;
  return sigTypes.every((t, i) => {
    const a = t.replace(/\s+/g, '');
    const b = wanted[i]!.replace(/\s+/g, '');
    return a === b || a.startsWith(b) || b.startsWith(a);
  });
}

/**
 * Licensing rule (SET 2b): a template applies only when its property is
 * declared in the feature's edge-case text, and only to the EDGE case that
 * carries that text — never to a behavior/export case (those are model-filled
 * so they can assert real intendedness).
 *
 * SET 31d structural licenses (`model-schema`, `uses-dep`) are handled in
 * matchTemplate before this runs — they do not require edge text.
 *
 * SET 38d — determinism smoke (license "export") may apply to behavior cases
 * with a matching signature (see matchDeterminismSmoke).
 */
function licensed(t: TestgenTemplate, planned: PlannedCase, _edgeTexts: string[]): boolean {
  if (t.license === 'export') return false; // bare export — determinism path only
  if (t.license === 'model-schema' || t.license === 'uses-dep') return false; // structural path only
  if (planned.kind !== 'edge' && planned.source !== 'edge') return false;
  const needle = t.license.toLowerCase();
  return planned.license.toLowerCase().includes(needle);
}

function matchStructuralClade(planned: PlannedCase): TestgenTemplate | null {
  const family = plannedCladeFamily(planned) ?? planned.cladeFamily ?? null;
  if (family !== 'model-schema' && family !== 'uses-dep') return null;
  return TESTGEN_TEMPLATES.find(t => t.family === family) ?? null;
}

/**
 * SET 38d — edge-tagged clade families (planSuite sets cladeFamily from
 * edge text keywords) match by family without relying solely on license
 * substring when the edge name is the family itself.
 */
function matchEdgeFamily(planned: PlannedCase): TestgenTemplate | null {
  const family = planned.cladeFamily
    ?? (planned.kind === 'edge' ? edgeCladeFamilyHint(planned.license) : undefined);
  if (!family) return null;
  if (family === 'model-schema' || family === 'uses-dep') return null; // structural path
  const candidates = TESTGEN_TEMPLATES.filter(t => t.family === family);
  if (!candidates.length) return null;
  if (!planned.signature) {
    return candidates[0] ?? null;
  }
  const sig = planned.signature;
  for (const t of candidates) {
    if (sig.params.length === t.paramCount
      && typesMatch(sig.params.map(p => p.type), t.paramTypes)) {
      return t;
    }
  }
  return candidates.find(t => t.paramCount === sig.params.length) ?? candidates[0] ?? null;
}

/** @deprecated use edgeCladeFamilyHint from planSuite — kept as alias for tests. */
export function inferEdgeFamily(text: string): string | null {
  return edgeCladeFamilyHint(text) ?? null;
}

/**
 * SET 38d — determinism smoke (license "export") only when the plan tags
 * the case as determinism (edge text / cladeFamily). Never auto-apply to
 * every matching behavior signature — that would weaken suites to typeof
 * checks and hide real failures (SET 36e stall fixture).
 */
function matchDeterminismSmoke(planned: PlannedCase): TestgenTemplate | null {
  const tagged = planned.cladeFamily === 'determinism'
    || /determin/i.test(planned.license)
    || /determin/i.test(planned.name);
  if (!tagged) return null;
  if (!planned.signature) {
    return TESTGEN_TEMPLATES.find(t => t.family === 'determinism') ?? null;
  }
  const sig = planned.signature;
  for (const t of TESTGEN_TEMPLATES) {
    if (t.family !== 'determinism') continue;
    if (sig.params.length !== t.paramCount) continue;
    if (!typesMatch(sig.params.map(p => p.type), t.paramTypes)) continue;
    const ret = sig.returnType.replace(/\s+/g, '');
    const want = t.returnType.replace(/\s+/g, '');
    if (!ret.startsWith(want) && !want.startsWith(ret)) continue;
    return t;
  }
  return TESTGEN_TEMPLATES.find(t => t.family === 'determinism') ?? null;
}

export function matchTemplate(
  planned: PlannedCase,
  edgeTexts: string[],
): TestgenTemplate | null {
  // SET 31d planner half — structural clades beat edge licensing.
  const structural = matchStructuralClade(planned);
  if (structural) return structural;

  // SET 38d — edge family tags (roundtrip / rejects-empty / determinism).
  const edgeFam = matchEdgeFamily(planned);
  if (edgeFam) return edgeFam;

  // SET 38d — determinism smoke on matching behavior signatures.
  const smoke = matchDeterminismSmoke(planned);
  if (smoke) return smoke;

  if (planned.kind === 'integration') return null;
  for (const t of TESTGEN_TEMPLATES) {
    if (!licensed(t, planned, edgeTexts)) continue;
    if (planned.kind === 'behavior' && planned.signature) {
      const sig = planned.signature;
      if (sig.params.length !== t.paramCount) continue;
      if (!typesMatch(sig.params.map(p => p.type), t.paramTypes)) continue;
      if (!sig.returnType.replace(/\s+/g, '').startsWith(t.returnType.replace(/\s+/g, ''))) continue;
      return t;
    }
    if (planned.kind === 'edge' && licensed(t, planned, edgeTexts) && t.license !== 'export') {
      // Edge templates still need a symbol + compatible sig from the feature's first export.
      if (planned.symbol && planned.signature) {
        const sig = planned.signature;
        if (sig.params.length === t.paramCount
          && typesMatch(sig.params.map(p => p.type), t.paramTypes)) {
          return t;
        }
      }
    }
  }
  return null;
}

/**
 * Fill planned cases from the verified template bank. Unfilled cases become
 * holes for fixTestCases (SET 2c). Bodies that don't parse are rejected.
 *
 * Signature stays `(plan, edgeTexts)` so runner.ts call sites keep working.
 * Optional 3rd arg is reserved/additive and currently unused.
 */
export function applyTemplates(
  plan: SuitePlan,
  edgeTexts: string[],
  _opts?: { featureId?: string },
): TemplatedFill {
  const cases: IRCaseGen[] = [];
  const holes: PlannedCase[] = [];
  const events: string[] = [];
  let templated = 0;
  for (const p of plan.cases) {
    const t = matchTemplate(p, edgeTexts);
    if (t && p.symbol) {
      const body = t.body.replace(/SYM/g, p.symbol);
      if (acceptCaseBody(body)) {
        cases.push({
          name: p.name, kind: p.kind, body,
          clade: cladeFromTemplate(t, p.symbol),
        });
        templated++;
        events.push(`templated ${p.name} via ${t.id}`);
        continue;
      }
    }
    holes.push(p);
  }
  return { cases, templated, holes, events };
}
