#!/usr/bin/env node
/**
 * judge-orchestrator (§3, §6, §12, J1/J2/J5/J8/J9, F8/F10, A0) — машинерия судьи:
 * тупо-механическая сборка из пола (4-A/4-C), думающей части (4-D) и печати (4-B).
 *  - gateFires (A0): ворота срабатывают СТРУКТУРНО от состояния, судью не зовут по желанию.
 *  - runGateLadder: 3-шаговая лесенка (существование $0 → верность → судья); провал → стоп раньше.
 *  - finalGate (J9): «да» судьи НЕ отпирает детерминированный пол; пол перевешивает.
 *  - appealStep (F8): 3 круга на ОДНО несогласие → эскалация владельцу.
 *  - judgeHealth (J5): упал/деградировал → громкий крик + деградация до пола (молча не пропускает).
 *  - gate3Terminal (F10): Гейт-3 ничего не печатает вниз — только принято/не принято.
 *  - logVerdict (J8): каждый вердикт append-only в журнал (инъекция).
 * Сам вызов модели и persist — в 4-D (мок) и хук-обёртках 4-G; здесь чистая логика.
 */
import { classifyDestructive } from './classify-destructive.mjs';
import { criteriaFromSealedPlan, criteriaGreenMatched, fingerprintFresh, greenSignaturesValid, specToPlanCoverage, k5CriterionCheck } from './judge-gate-floor.mjs';

/** A0: ворота срабатывают от состояния (готово И не запечатано), не «по желанию». */
export function gateFires({ ready, alreadySealed }) {
  return !!ready && !alreadySealed;
}

/** Лесенка: шаги по порядку; первый ok===false → стоп раньше (остальные не запускаются). */
export function runGateLadder(steps = []) {
  for (const step of steps) {
    // Аудит M1-M4 (свежий объектив): шаг пола может УПАСТЬ (I/O, битый ввод). Раньше step.run()
    // без try/catch пробрасывал исключение наверх → решение становилось неопределённым (в инертной
    // обёртке catch→block:false = fail-open). Теперь бросок = «пол не пройден» (fail-closed → блок),
    // последующие шаги не запускаются. Сам чистый модуль гарантирует безопасную сторону.
    let result;
    try {
      result = step.run();
    } catch {
      return { passed: false, stoppedAt: step.name, detail: 'шаг пола упал (fail-closed → блок)' };
    }
    if (!result || result.ok !== true) {
      return { passed: false, stoppedAt: step.name, detail: result && result.detail };
    }
  }
  return { passed: true };
}

/**
 * J9: пол перевешивает «да» судьи. Снять вето пола может ТОЛЬКО явный floorBlocked===false.
 * (Аудит M1-M4: раньше любой falsy — undefined/null от упавшей проверки пола — трактовался
 * как «пол чист» → fail-open. Теперь недоказанный пол → block.)
 */
export function finalGate({ judgeDecision, floorBlocked }) {
  if (floorBlocked !== false) return 'block';
  return judgeDecision === 'GO' ? 'allow' : 'block';
}

/** F8: следующий круг апелляции; >3 кругов на одно несогласие → к владельцу. */
export function appealStep({ round, maxRounds = 3 }) {
  const next = round + 1;
  return { round: next, toOwner: next > maxRounds };
}

/** J5: здоровье судьи. Недоступен/деградировал → пост пустой + крик + режим пола. */
export function judgeHealth({ available, degraded }) {
  const healthy = !!available && !degraded;
  return healthy
    ? { onPost: true, cry: false, mode: 'judge' }
    : { onPost: false, cry: true, mode: 'floor-only' };
}

/** F10: Гейт-3 терминален — печати-вниз нет, только принято/не принято. */
export function gate3Terminal(verdict) {
  return { accepted: !!verdict && verdict.decision === 'GO', sealDown: null };
}

/** J8: записать вердикт append-only (журнал инъектируется). */
export function logVerdict({ verdict, nowMs, journal }) {
  const entry = {
    kind: 'verdict',
    functionName: verdict && verdict.functionName,
    decision: verdict && verdict.decision,
    at: typeof nowMs === 'number' ? nowMs : null,
  };
  if (typeof journal === 'function') journal(entry);
  return entry;
}

// #4 (§3.3): A2 просыпается на двух поводах — детерминированный разбор по СУТИ команды.
// Разрушительное → destructive (несколько голосов: радиус/обратимость + атакующий);
// иначе → divergence (1 голос). Единый источник — classifyDestructive (§4, Δ9-б):
// локальный DESTRUCTIVE_RE удалён, suspicious-набор берётся из классификатора.
export function a2CaseSelect(action) {
  const cmd = String((action && (action.object || action.command)) || '');
  return classifyDestructive(cmd).suspicious ? 'destructive' : 'divergence';
}

/**
 * #4 — упаковка функции-гейта: сперва механический пол ($0, лесенка floorSteps);
 * провал любого шага → NO-GO на стадии 'floor' (судья не зовётся, экономия + лесенка).
 * Весь пол ок → runEngine() (думающая часть, инъекция) → решение на стадии 'judge'.
 * Один runner для всех пяти функций; конкретные floorSteps/runEngine задаёт вызывающий.
 */
export function runGateFunction({ floorSteps = [], runEngine }) {
  const ladder = runGateLadder(floorSteps);
  if (!ladder.passed) {
    return { decision: 'NO-GO', stage: 'floor', stoppedAt: ladder.stoppedAt, detail: ladder.detail };
  }
  const verdict = typeof runEngine === 'function' ? runEngine() : { decision: 'NO-GO' };
  return { decision: verdict.decision, stage: 'judge', verdict };
}

/**
 * 5.5 (Δ6): шаги критерий-гейта — РОВНО 4 проверки поверх существующей runGateLadder
 * (НЕ плодим criterionFullyProven; лекарство «забыл пол» уже встроено в лесенку). Порядок
 * И-семантики: id ∈ печать (целостность) → green-присутствие → свежесть отпечатка (Δ2) →
 * подпись подписанта (Δ5, подлинность). Структурный тест «ровно эти 4 шага» ловит пропуск.
 * Каждый шаг — {name, run:()=>{ok,...}}; runGateLadder короткозамыкается на первом провале.
 */
export function criterionGateSteps({ criteria = [], greenRuns = [], sealedCriterionIds = [], currentFingerprints = {}, signerKey } = {}) {
  return [
    { name: 'criteria-from-sealed-plan', run: () => criteriaFromSealedPlan({ criteria, sealedCriterionIds }) },
    { name: 'criteria-green-matched', run: () => criteriaGreenMatched({ criteria, greenRuns }) },
    { name: 'fingerprint-fresh', run: () => fingerprintFresh({ greenRuns, currentFingerprints }) },
    { name: 'green-signatures-valid', run: () => greenSignaturesValid({ greenRuns, key: signerKey }) },
  ];
}

/** 5.5 (Δ6): прогнать критерий-гейт через И-лесенку (короткое замыкание на первом провале). */
export function runCriterionGate(input) {
  return runGateLadder(criterionGateSteps(input));
}

/**
 * Гейт-2 (§3.2, М7 Фаза 4c): шаги план-гейта — РОВНО 2 проверки через runGateLadder (зеркало
 * criterionGateSteps). Поглощает tdd-gate Rule #6 «план перед prod-кодом» в ЗАПЕЧАТАННОЙ форме:
 * спека покрыта шагом плана (specToPlanCoverage) + значимый шаг несёт конкретный критерий
 * (k5CriterionCheck). НЕ text-mention пути плана (Класс 1 hasPlanIndicator закрыт). Синергия с
 * М2-стеной: стена = членство действия в плане, Гейт-2 = полнота покрытия спеки + критерий.
 */
export function planGateSteps({ specSections = [], planSteps = [] } = {}) {
  return [
    { name: 'spec-to-plan-coverage', run: () => specToPlanCoverage({ specSections, planSteps }) },
    { name: 'k5-criterion-check', run: () => k5CriterionCheck({ planSteps }) },
  ];
}

/** Гейт-2: прогнать план-гейт через И-лесенку (короткое замыкание на первом провале). */
export function runPlanGate(input) {
  return runGateLadder(planGateSteps(input));
}
