#!/usr/bin/env node
/**
 * router-learning-queue — очередь обучения роутера (3.4 примеры / 3.5 долгая память).
 * НЕСУЩИЙ HARD-RULE (решение владельца 2026-06-03): наполнение фонда ТОЛЬКО по явному
 * «да» владельца, ИНАЧЕ НИКАК. Система только ПРЕДЛАГАЕТ (enqueue → pending); в фонд
 * переводит ТОЛЬКО applyApprovalBatch с явным id в approve. Без авто-обучения.
 */
import fsDefault from 'node:fs';

/** Запись очереди — ВСЕГДА pending (нет авто-одобрения). */
export function makeQueueEntry(candidate) {
  return { id: candidate.id, kind: candidate.kind, summary: candidate.summary || '', why_proposed: candidate.why_proposed || '', status: 'pending' };
}

/** Добавить предложение (pending). Никогда не одобряет. */
export function enqueue(queue, candidate) {
  return [...(queue || []), makeQueueEntry(candidate)];
}

/** Сколько ждёт одобрения. */
export function pendingCount(queue) {
  return (queue || []).filter((e) => e.status === 'pending').length;
}

/**
 * Одобрение пачкой — ЕДИНСТВЕННЫЙ путь в фонд. approve → 'approved' + в fund;
 * reject → 'rejected' (помечен, не предлагать повторно); defer → остаётся 'pending'.
 * Без явного решения id остаётся как был. Возвращает {queue, fund, rejected}.
 *
 * Приоритет reject > approve: конфликт «и да, и нет» по одному id — двусмысленный
 * сигнал → по hard-rule «без явного да НИКАК» в фонд НЕ пускаем (безопасная сторона).
 */
export function applyApprovalBatch(queue, { approve = [], reject = [], defer = [] } = {}) {
  const aSet = new Set(approve), rSet = new Set(reject), dSet = new Set(defer);
  const fund = [], rejected = [];
  const newQueue = (queue || []).map((e) => {
    // G (аудит M1-M4): только pending-запись переходит. Повторное approve/reject уже
    // решённого id (или reuse id) НЕ перепроводит её снова → нет дублей в фонде.
    if (e.status !== 'pending') return e;
    if (rSet.has(e.id)) { const ne = { ...e, status: 'rejected' }; rejected.push(ne); return ne; }
    if (aSet.has(e.id)) { const ne = { ...e, status: 'approved' }; fund.push(ne); return ne; }
    if (dSet.has(e.id)) return { ...e, status: 'pending' };
    return e;
  });
  return { queue: newQueue, fund, rejected };
}

/** Рендер раздела «На одобрение» для /brain-retro (только pending). */
export function renderApprovalSection(queue) {
  const pend = (queue || []).filter((e) => e.status === 'pending');
  if (pend.length === 0) return '## На одобрение\n\n_нет кандидатов_';
  const lines = pend.map((e) => `- [${e.id}] (${e.kind}) ${e.summary} — почему: ${e.why_proposed}`);
  return `## На одобрение (${pend.length})\n\n${lines.join('\n')}\n\nОтвет: «одобряю 1,3 / отклоняю 2 / отложить 4».`;
}

/** Сигнал для STATUS.md: «ждут одобрения: N». */
export function statusSignal(queue) {
  return `ждут одобрения: ${pendingCount(queue)}`;
}

/** Персист очереди (fs инъектируется). */
export function saveQueue({ queue, path, fsImpl = fsDefault }) {
  fsImpl.writeFileSync(path, JSON.stringify(queue));
}
export function loadQueue({ path, fsImpl = fsDefault }) {
  try { return JSON.parse(fsImpl.readFileSync(path, 'utf8')); }
  catch (e) { if (e && e.code === 'ENOENT') return []; throw e; }
}
