/**
 * SET 37h / 37a — Failure-stack primitive helpers (NO runner wiring).
 *
 * Standing failure law across the pipeline:
 *   - Mechanical fixes run IN PLACE (0 tokens, immediate).
 *   - Model draws on failures run from the END of the queue, where
 *     accumulated context (warm superbuffer pages) is maximal.
 *   - Empty draws skip the stack (nothing to repair — Integration routes
 *     to clade template / fill path). Documented here; Integration enforces.
 *
 * Caps align with SET 32i: up to 3 re-entry instances per loop, then ONE
 * metaloop over everything still failing. Changed-world precondition
 * applies (re-enter only if artifact/lessons/buffer changed).
 */
import type { WorldSignature } from './fillRelay';
import { worldChanged } from './fillRelay';

/** Align 32i: 3 instances + metaloop. */
export const FAILURE_STACK_INSTANCE_CAP = 3;
export const FAILURE_STACK_METALOOP_CAP = 1;

export type FailureKind =
  | 'parse'
  | 'fill'
  | 'stalled'
  | 'empty-draw';

export interface DeferredFailure {
  featureId: string;
  caseName: string;
  kind: FailureKind;
  /** Broken draw body (omit / empty for empty-draw). */
  brokenDraw: string;
  parseError: string;
  /** 1-based line when known. */
  position?: { line: number; col?: number } | null;
  snippet: string;
  instance: number;
  stackedAt: string;
}

export interface FailureStackState {
  queue: DeferredFailure[];
  /** Instances consumed per feature (or feature|case). */
  instancesByKey: Record<string, number>;
  metaloopUsed: number;
  lastWorldByKey: Record<string, WorldSignature>;
}

export function emptyFailureStack(): FailureStackState {
  return {
    queue: [],
    instancesByKey: {},
    metaloopUsed: 0,
    lastWorldByKey: {},
  };
}

export function failureKey(f: Pick<DeferredFailure, 'featureId' | 'caseName'>): string {
  return `${f.featureId}|${f.caseName}`;
}

/**
 * Empty-draw skips the stack — nothing to repair.
 * Integration routes empty-draw to clade template / fill instead.
 */
export function shouldSkipFailureStack(kind: FailureKind, brokenDraw: string): boolean {
  if (kind === 'empty-draw') return true;
  return !brokenDraw.trim();
}

/**
 * Mechanical-in-place vs model-from-end law.
 * `shouldDrawInPlace` is true ONLY for mechanical fixes; model draws
 * always enqueue / run from the end.
 */
export function shouldDrawInPlace(mode: 'mechanical' | 'model'): boolean {
  return mode === 'mechanical';
}

export function canEnqueueInstance(
  state: FailureStackState,
  key: string,
): boolean {
  return (state.instancesByKey[key] ?? 0) < FAILURE_STACK_INSTANCE_CAP;
}

export function canRunMetaloop(state: FailureStackState): boolean {
  return state.metaloopUsed < FAILURE_STACK_METALOOP_CAP;
}

/**
 * Changed-world precondition: re-enter only when substrate advanced.
 * Re-exports fillRelay.worldChanged for a single import surface.
 */
export function failureWorldChanged(prev: WorldSignature, now: WorldSignature): boolean {
  return worldChanged(prev, now);
}

export function pushDeferredFailure(
  state: FailureStackState,
  entry: Omit<DeferredFailure, 'instance' | 'stackedAt'> & {
    instance?: number;
    stackedAt?: string;
  },
): { state: FailureStackState; pushed: DeferredFailure | null; reason?: string } {
  if (shouldSkipFailureStack(entry.kind, entry.brokenDraw)) {
    return { state, pushed: null, reason: 'empty-draw skips stack' };
  }
  const key = failureKey(entry);
  if (!canEnqueueInstance(state, key)) {
    return { state, pushed: null, reason: `instance cap ${FAILURE_STACK_INSTANCE_CAP}` };
  }
  const instance = entry.instance ?? ((state.instancesByKey[key] ?? 0) + 1);
  const pushed: DeferredFailure = {
    ...entry,
    instance,
    stackedAt: entry.stackedAt ?? new Date().toISOString(),
  };
  return {
    state: {
      ...state,
      queue: [...state.queue, pushed],
      instancesByKey: { ...state.instancesByKey, [key]: instance },
    },
    pushed,
  };
}

/** Pop next deferred failure from the END (oldest first / FIFO). */
export function popDeferredFailure(
  state: FailureStackState,
): { state: FailureStackState; entry: DeferredFailure | null } {
  if (!state.queue.length) return { state, entry: null };
  const [entry, ...rest] = state.queue;
  return { state: { ...state, queue: rest }, entry: entry ?? null };
}

export function markMetaloopUsed(state: FailureStackState): FailureStackState {
  return { ...state, metaloopUsed: state.metaloopUsed + 1 };
}

export function rememberWorld(
  state: FailureStackState,
  key: string,
  world: WorldSignature,
): FailureStackState {
  return {
    ...state,
    lastWorldByKey: { ...state.lastWorldByKey, [key]: world },
  };
}

/** True when re-entry is allowed under changed-world + caps. */
export function mayReenter(
  state: FailureStackState,
  key: string,
  now: WorldSignature,
): { ok: boolean; reason?: string } {
  if (!canEnqueueInstance(state, key) && (state.instancesByKey[key] ?? 0) >= FAILURE_STACK_INSTANCE_CAP) {
    // Already at instance cap — metaloop may still apply separately.
    if (!canRunMetaloop(state)) {
      return { ok: false, reason: 'instance + metaloop caps exhausted' };
    }
  }
  const prev = state.lastWorldByKey[key];
  if (prev && !failureWorldChanged(prev, now)) {
    return { ok: false, reason: 'world unchanged' };
  }
  return { ok: true };
}

/** Drop after both stages failed. */
export function formatStackDropReason(opts?: {
  oraclePass?: boolean;
  warmRedraw?: boolean;
}): string {
  const oracle = opts?.oraclePass !== false;
  const warm = opts?.warmRedraw !== false;
  const parts: string[] = [];
  if (oracle) parts.push('1 oracle pass');
  if (warm) parts.push('1 warm targeted redraw');
  return `dropped after parse-loop (${parts.join(', ')})`;
}

/** Prompt fragment for warm stack retry. */
export function formatStackRepairPrompt(entry: DeferredFailure): string {
  const loc = entry.position?.line != null
    ? `line ${entry.position.line}${entry.position.col != null ? `:${entry.position.col}` : ''}`
    : 'unknown position';
  return [
    `your draw, broken at ${loc}: \`${entry.snippet}\` — repair it.`,
    `parse error: ${entry.parseError}`,
    'Broken draw (repair in place; preserve assertion intent):',
    entry.brokenDraw.slice(0, 800),
  ].join('\n');
}

/**
 * SET 37a — repair-phase relay event.
 * `relay: F3 re-entering (instance 2/3, world changed: +2 heals, +3 lessons)`
 */
export function formatRelayReenter(opts: {
  featureId: string;
  instance: number;
  instanceCap?: number;
  healsDelta?: number;
  lessonsDelta?: number;
  worldChanged?: boolean;
}): string {
  const cap = opts.instanceCap ?? FAILURE_STACK_INSTANCE_CAP;
  const heals = opts.healsDelta ?? 0;
  const lessons = opts.lessonsDelta ?? 0;
  const world = opts.worldChanged === false
    ? 'world unchanged'
    : `world changed: +${heals} heals, +${lessons} lessons`;
  return `relay: ${opts.featureId} re-entering (instance ${opts.instance}/${cap}, ${world})`;
}

/** Diff two world signatures into heal/lesson-style deltas for relay events. */
export function worldDelta(
  prev: WorldSignature,
  now: WorldSignature,
): { heals: number; lessons: number; siblings: number } {
  return {
    heals: Math.max(0, now.frozenCount - prev.frozenCount),
    lessons: Math.max(0, now.lessonCount - prev.lessonCount),
    siblings: Math.max(0, now.siblingCaseCount - prev.siblingCaseCount),
  };
}
