#!/usr/bin/env node
/**
 * skill-contract-registry — сборка набора контрактов скилов. Чистый buildRegistry
 * (над записями {contract, currentContent?}) + loadRegistry (диск, fs инъектируется).
 * Валидирует каждый, для external сверяет дрейф G4. Вход для машины охвата 3-C.
 */
import fsDefault from 'node:fs';
import { validateContract, normalizeContract, checkContractDrift } from './skill-contract.mjs';

/** Чистая сборка: валидирует, ловит дубли, помечает дрейф external. */
export function buildRegistry(entries) {
  const contracts = [], errors = [], driftFlags = [], seen = new Set();
  for (const e of entries || []) {
    const c = normalizeContract(e.contract);
    const v = validateContract(c);
    if (!v.ok) { errors.push({ skill: c.skill || '(?)', errors: v.errors }); continue; }
    if (seen.has(c.skill)) { errors.push({ skill: c.skill, errors: ['duplicate skill contract'] }); continue; }
    seen.add(c.skill);
    if (c.kind === 'external') {
      const d = checkContractDrift({ contract: c, currentContent: e.currentContent });
      if (d.drifted) driftFlags.push({ skill: c.skill, reason: d.reason, fallback: d.fallback });
    }
    contracts.push(c);
  }
  return { contracts, errors, driftFlags };
}

/**
 * G3 — детерминированная диспетчеризация по id скила поверх собранного реестра:
 * скил в driftFlags (устарел/дрейф G4) ИЛИ нет адаптера → mode 'soft-reasoning'
 * (молча не доверять, откат на мягкое рассуждение судьи); есть+свежий → mode
 * 'exact' + сам контракт. Чистая функция над выходом buildRegistry/loadRegistry.
 */
export function dispatchContract(registry, skill) {
  const reg = registry || {};
  const drift = (reg.driftFlags || []).find((d) => d.skill === skill);
  if (drift) return { mode: 'soft-reasoning', skill, reason: drift.reason };
  const c = (reg.contracts || []).find((x) => x.skill === skill);
  if (!c) return { mode: 'soft-reasoning', skill, reason: 'нет адаптера для скила — мягкое рассуждение' };
  return { mode: 'exact', skill, contract: c };
}

/** Загрузка с диска: dir/*.contract.json. Для external читает source.path
 *  (актуальный SKILL.md) для дрейф-сверки G4, если путь задан и доступен. */
export function loadRegistry({ dir, fsImpl = fsDefault }) {
  const files = fsImpl.readdirSync(dir).filter((f) => f.endsWith('.contract.json'));
  const entries = files.map((f) => {
    const raw = JSON.parse(fsImpl.readFileSync(`${dir}/${f}`, 'utf8'));
    let currentContent;
    if (raw && raw.kind === 'external' && raw.source && raw.source.path) {
      try { currentContent = fsImpl.readFileSync(raw.source.path, 'utf8'); } catch { currentContent = undefined; }
    }
    return { contract: raw, currentContent };
  });
  return buildRegistry(entries);
}
