/**
 * SET 3a — permutation-select rung. K candidates of a bounded unit at
 * decorrelated temps; the ALGORITHM picks by mechanical fitness.
 */
export const PERMUTATION_TEMPS = [0.3, 0.7, 1.0] as const;

/** SET 36e — widened temp within common OpenAI-compat provider limits (0..2). */
export const ESCALATED_TEMP = 1.2;

export interface PermutationCandidate<T> {
  value: T;
  temp: number;
  /** Higher is better. */
  score: number;
  /** Secondary: lower findings / errors wins ties after score. */
  findings: number;
  label?: string;
}

export interface PermutationResult<T> {
  picked: PermutationCandidate<T>;
  candidates: PermutationCandidate<T>[];
}

/** SET 36e — batch structure for a repair pass (temps + K). */
export interface BatchParams {
  temps: number[];
  k: number;
  /** True after stall-adaptive escalation widened/raised the batch. */
  escalated: boolean;
}

export function defaultBatchParams(): BatchParams {
  return { temps: [...PERMUTATION_TEMPS], k: PERMUTATION_TEMPS.length, escalated: false };
}

/**
 * SET 36e — after 2 consecutive no-improvement passes on the same finding,
 * escalate BEFORE the next identical batch: widen temp spread (+1.2) and
 * raise K by 1. Entropy reset remains the final rung (converge.onStall).
 */
export function escalateBatchParams(prev: BatchParams): BatchParams {
  const temps = prev.temps.includes(ESCALATED_TEMP)
    ? [...prev.temps]
    : [...prev.temps, ESCALATED_TEMP];
  const k = Math.max(temps.length, prev.k + 1);
  while (temps.length < k) {
    // Pad with ceiling temp so raised-K draws stay at the wide end.
    temps.push(ESCALATED_TEMP);
  }
  return { temps: temps.slice(0, k), k, escalated: true };
}

export function escalateEntropyEvent(pass: number, kind: 'spread widened' | 'K raised' | 're-scoped' = 'spread widened'): string {
  return `pass ${pass}: escalating entropy (${kind})`;
}

/**
 * Select among K scored candidates: highest score, then fewest findings,
 * then lowest temperature.
 */
export function selectPermutation<T>(
  candidates: PermutationCandidate<T>[],
): PermutationResult<T> {
  if (!candidates.length) throw new Error('permutation-select: no candidates');
  const ranked = [...candidates].sort((a, b) => {
    if (b.score !== a.score) return b.score - a.score;
    if (a.findings !== b.findings) return a.findings - b.findings;
    return a.temp - b.temp;
  });
  return { picked: ranked[0]!, candidates };
}

export function permutationEvent(r: PermutationResult<unknown>): string {
  const passLabel = r.picked.label ?? `${r.picked.score}`;
  return `permutation-select: ${r.candidates.length} candidates, picked t=${r.picked.temp} (${passLabel})`;
}

/** SET 35c — all-worse batch: keep incumbent, pass still counts toward stall. */
export function incumbentKeptEvent(pass: number): string {
  return `pass ${pass}: no improvement — incumbent kept`;
}

/**
 * SET 35c — true when the picked candidate is strictly better than the
 * incumbent fitness (pass count / score). Equal or worse → keep incumbent.
 */
export function isStrictImprovement(pickedScore: number, incumbentScore: number): boolean {
  return pickedScore > incumbentScore;
}

/**
 * SET 37a — fresh entropy for relay re-entry batches.
 * Never returns a byte-identical temp list vs a prior batch.
 */
export function freshRelayBatchParams(
  prev?: BatchParams | null,
  opts?: { salt?: number; k?: number; instance?: number },
): BatchParams {
  const salt = (opts?.salt ?? (Date.now() % 997) + 1) + (opts?.instance ?? 0) * 17;
  const base = prev?.temps?.length ? [...prev.temps] : [...PERMUTATION_TEMPS];
  const temps = base.map((t, i) => {
    const nudge = ((salt * (i + 1)) % 17) * 0.01;
    return Math.min(2, Math.max(0.05, Number((t + nudge).toFixed(3))));
  });
  if (prev && temps.length === prev.temps.length
    && temps.every((t, i) => t === prev.temps[i])) {
    temps[temps.length - 1] = Math.min(2, (temps[temps.length - 1] ?? 1) + 0.05);
  }
  const k = opts?.k ?? Math.max(temps.length, prev?.k ?? PERMUTATION_TEMPS.length);
  while (temps.length < k) {
    temps.push(Math.min(2, ESCALATED_TEMP + ((salt % 5) * 0.02)));
  }
  return { temps: temps.slice(0, k), k, escalated: !!prev?.escalated };
}

/** True when two batch param sets are byte-identical (forbidden for relay). */
export function batchParamsIdentical(a: BatchParams, b: BatchParams): boolean {
  return a.k === b.k
    && a.temps.length === b.temps.length
    && a.temps.every((t, i) => t === b.temps[i]);
}
