/**
 * Spine factory: turns a declarative `Spine` (a Han skill expressed as data)
 * into a runnable `Flow`. The shape is the one ~most Han skills share —
 *
 *   angle₁ ─┐
 *   angle₂ ─┼─▶ fold (code) ─▶ [synthesizer] ─▶ adversarial gate ─▶ render
 *   angle₃ ─┘   (fan-in)        (optional)        (validator)
 *
 * — so new skills are added as config (flows/*.ts), not new code. Band gating
 * selects how many angles fan out (small = core only; large = all). Skills
 * with a genuinely different shape (code-review's per-finding verify pipeline)
 * get a bespoke Flow instead of a Spine.
 */
import type { Band, Flow, Spine, Step, StepContext } from './types.js';
import { produceContract, reviewContract, type Contract } from './contracts.js';
import { slugify } from './render.js';

const BAND_ORDER: Record<Band, number> = { small: 0, medium: 1, large: 2 };

export function readBand(input: StepContext['input']): Band {
  const b = input.band;
  return typeof b === 'string' && b in BAND_ORDER ? (b as Band) : 'small';
}

function bandAtLeast(ctx: StepContext, min: Band = 'small'): boolean {
  return BAND_ORDER[readBand(ctx.input)] >= BAND_ORDER[min];
}

/** Appended to every worker when --fast is set — caps the slow tool loop. */
export function fastNote(ctx: StepContext): string {
  if (!ctx.input.concise) return '';
  return '\n\nFAST MODE — optimise for speed over exhaustiveness: limit external/tool calls to the few that matter, cite only decisive evidence, keep every section short, return quickly.';
}

interface ResolvedGate {
  agent: string;
  label: string;
  task: (ctx: StepContext) => string;
}

/** The adversarial gate, built with the Han review checklists for the spine's contracts. */
function defaultValidator(contracts: Contract[]): ResolvedGate {
  return {
    agent: 'adversarial-validator',
    label: 'Validation (adversarial-validator)',
    task: (ctx) =>
      [
        `Adversarially validate the analysis below, for: "${String(ctx.input.question)}".`,
        'Attack the evidence, the framing, the conclusion, and the integrity of how the evidence was gathered.',
        'Emit findings as V1, V2, … each with a severity and whether it changes the conclusion.',
        reviewContract(contracts).trim(),
        'End with, in this order: a one-line VERDICT (does the conclusion survive?); a plain-language SUMMARY (2–3 sentences, no jargon or IDs); and a CONFIDENCE rating on its own line — `Confidence: High | Medium | Low`.',
        '',
        '----- ANALYSIS TO ATTACK -----',
        ctx.results.synthesis ?? ctx.results.fold ?? '',
      ].join('\n'),
  };
}

export function buildSpineFlow(spine: Spine): Flow {
  const contracts: Contract[] = spine.contracts ?? ['evidence'];
  const validator: ResolvedGate = spine.validator ?? defaultValidator(contracts);
  const angleIds = spine.angles.map((a) => a.id);
  const steps: Step[] = [];

  for (const angle of spine.angles) {
    steps.push({
      id: angle.id,
      kind: 'agent',
      agent: angle.agent,
      when: (ctx) => bandAtLeast(ctx, angle.minBand) && (angle.when ? angle.when(ctx) : true),
      run: (ctx) => angle.task(ctx) + produceContract(contracts) + fastNote(ctx),
    });
  }

  // Code fold: concatenate whatever angles produced (skipped angles absent).
  steps.push({
    id: 'fold',
    kind: 'code',
    deps: angleIds,
    run: (ctx) => foldAngles(spine, ctx),
  });

  if (spine.synthesizer) {
    steps.push({
      id: 'synthesis',
      kind: 'agent',
      agent: spine.synthesizer.agent,
      deps: ['fold'],
      run: (ctx) => spine.synthesizer!.task(ctx) + produceContract(contracts) + fastNote(ctx),
    });
  }

  steps.push({
    id: 'validation',
    kind: 'agent',
    agent: validator.agent,
    deps: [spine.synthesizer ? 'synthesis' : 'fold'],
    run: (ctx) => validator.task(ctx) + fastNote(ctx),
  });

  return {
    name: spine.name,
    description: spine.description,
    steps,
    render: (ctx) => renderSpine(spine, validator, contracts, ctx),
    output: (ctx) => `conductor-report-${spine.name}-${slugify(String(ctx.input.question))}.md`,
  };
}

function foldAngles(spine: Spine, ctx: StepContext): string {
  const blocks: string[] = [];
  for (const angle of spine.angles) {
    const out = ctx.results[angle.id];
    if (out) blocks.push(`### ${angle.label}\n\n${out}`);
  }
  return blocks.join('\n\n') || '_(no angle produced output)_';
}

function renderSpine(spine: Spine, validator: ResolvedGate, contracts: Contract[], ctx: StepContext): string {
  const question = String(ctx.input.question ?? '');
  const model = process.env.CONDUCTOR_MODEL ?? 'llama-swap/qwen3.6-35b-a3b-mxfp4';
  const band = readBand(ctx.input);
  const chain: string[] = [];
  const rules = [
    contracts.includes('evidence') ? 'evidence-rule (trust classes · single-source web gate · no-evidence labeling)' : '',
    contracts.includes('yagni') ? 'YAGNI gate' : '',
  ]
    .filter(Boolean)
    .join(' · ');

  const parts: string[] = [
    `# Conductor Report — ${spine.name}: ${question}`,
    `> BooCode code conductor · band=${band}${ctx.input.concise ? ' · fast' : ''} · workers on \`${model}\`. Sequencing, fan-out, and fold are deterministic code; each agent ran as a bounded single-task worker. Han rules applied: ${rules}. The plain-language summary, the **Confidence** rating, and any \`## Deferred (YAGNI)\` items are in the **Validation** section — read it before trusting the conclusion.`,
  ];

  for (const angle of spine.angles) {
    if (ctx.results[angle.id]) {
      parts.push(`## ${angle.label}\n\n${ctx.results[angle.id]}`);
      chain.push(angle.agent);
    }
  }
  if (spine.synthesizer && ctx.results.synthesis) {
    parts.push(`## ${spine.synthesizer.label}\n\n${ctx.results.synthesis}`);
    chain.push(spine.synthesizer.agent);
  }
  parts.push(`## ${validator.label}\n\n${ctx.results.validation ?? '_no validation output_'}`);
  chain.push(validator.agent);

  parts.push(
    `---\n\n_Conducted by the code conductor: ${chain.join(' → ')}. Band=${band}. The conductor chose every step and passed full outputs forward; no model decided the sequence._`,
  );

  return parts.join('\n\n') + '\n';
}
