/**
 * SET 43e — Lesson→handler promotion (self-growing substrate).
 *
 * When the same classKey heals with the same editShape ≥minSupport times,
 * auto-draft a mechanical rule + fixture pairs into a handler-candidate list.
 *
 * Candidates are NOT admitted into the repair oracle. Admission goes through
 * the mutation-oracle gate (same discipline as clade admission). This module
 * is pure and deterministic: no filesystem writes; the caller persists.
 *
 * Optional Integration wiring: emit `handler-candidate` events (mirror
 * `clade-candidate`) when a candidate is drafted — host/storage concern.
 */

export type HealedInstance = {
  classKey: string;
  editShape: string; // normalized note / edit fingerprint
  beforeSnippet?: string;
  afterSnippet?: string;
  path?: string;
};

export type HandlerCandidate = {
  classKey: string;
  editShape: string;
  support: number;
  draftRule: string;
  fixtures: { before: string; after: string }[];
  status: 'handler-candidate';
  ts: string;
};

/** Stable cluster key — classKey + editShape must both match to merge. */
function clusterKey(classKey: string, editShape: string): string {
  return `${classKey}\u0000${editShape}`;
}

/** Deterministic draft rule text from a healed cluster. */
function draftMechanicalRule(
  classKey: string,
  editShape: string,
  support: number,
): string {
  return (
    `mechanical-candidate: when classKey="${classKey}" and editShape="${editShape}" `
    + `(support=${support}), apply the healed before→after transform. `
    + `Admission requires mutation-oracle verification — not auto-admitted.`
  );
}

/**
 * When same classKey heals with same editShape ≥minSupport times → draft candidate.
 * Pure, deterministic (sorted keys, stable fixture order, fixed ts epoch).
 */
export function promoteLessonsToHandlerCandidates(
  instances: HealedInstance[],
  minSupport = 3,
): HandlerCandidate[] {
  const clusters = new Map<string, HealedInstance[]>();
  for (const inst of instances) {
    if (!inst.classKey || !inst.editShape) continue;
    const key = clusterKey(inst.classKey, inst.editShape);
    const arr = clusters.get(key) ?? [];
    arr.push(inst);
    clusters.set(key, arr);
  }

  const keys = [...clusters.keys()].sort();
  const out: HandlerCandidate[] = [];
  for (const key of keys) {
    const group = clusters.get(key)!;
    if (group.length < minSupport) continue;
    const classKey = group[0]!.classKey;
    const editShape = group[0]!.editShape;
    const fixtures: { before: string; after: string }[] = [];
    for (const g of group) {
      if (g.beforeSnippet != null && g.afterSnippet != null) {
        fixtures.push({ before: g.beforeSnippet, after: g.afterSnippet });
      }
    }
    out.push({
      classKey,
      editShape,
      support: group.length,
      draftRule: draftMechanicalRule(classKey, editShape, group.length),
      fixtures,
      status: 'handler-candidate',
      // Fixed epoch — pure/deterministic; caller may stamp wall-clock on persist.
      ts: '1970-01-01T00:00:00.000Z',
    });
  }
  return out;
}

/**
 * SET 62 — distill a before→after transform into a clusterable edit shape.
 * Coarse and deterministic: identifier-set delta + size direction. Whole-file
 * heals (surgery, entropy reset) get a minable fingerprint so they feed
 * promotion instead of vanishing — the flywheel's missing fuel line.
 */
export function distillEditShape(before: string, after: string): string {
  const ids = (t: string): Set<string> => new Set(t.match(/[A-Za-z_$][\w$]{2,}/g) ?? []);
  const b = ids(before);
  const a = ids(after);
  const added = [...a].filter(x => !b.has(x)).sort().slice(0, 5);
  const removed = [...b].filter(x => !a.has(x)).sort().slice(0, 5);
  const dir = after.length > before.length ? '+' : after.length < before.length ? '-' : '=';
  return `Δ${dir} +[${added.join(',')}] -[${removed.join(',')}]`;
}
