/**
 * Core types for the code conductor.
 *
 * The conductor is a DETERMINISTIC orchestrator: code decides the order, the
 * fan-out, and the fold. Each `agent` step dispatches one Han persona as a
 * bounded single-task worker — the model never sequences itself, which is the
 * failure mode that sinks loose self-orchestration on weak local models.
 * `code` steps run pure TS (fold / synthesis / transform).
 */

/** The original input to a flow run (e.g. { question, repoPath? }). */
export type FlowInput = Record<string, unknown>;

/**
 * Capability injected by the flow-runner so that code steps can dispatch
 * sub-agents without importing the subprocess dispatcher directly.
 * The standalone Phase-1 CLI populates this from its own dispatch module;
 * the coder's flow-runner injects the DB-backed dispatcher path instead.
 */
export type DispatchFn = (agent: string, task: string) => Promise<string>;

export interface StepContext {
  /** the original flow input, verbatim */
  readonly input: FlowInput;
  /** completed step results, keyed by step id (full output, no truncation) */
  readonly results: Readonly<Record<string, string>>;
  /**
   * Injected by the runner for code steps that need to dispatch sub-agents
   * (e.g. code-review's per-dimension adversarial verify).
   * Undefined in contexts that don't support dynamic dispatch.
   */
  readonly dispatch?: DispatchFn;
  /**
   * The run-configured model string for report headers.
   * Injected by the flow-runner from flow_runs.model.
   * Falls back to a default in render functions when absent.
   */
  readonly model?: string;
  /**
   * Inter-agent messaging within the same flow run.
   * `publish` broadcasts on the user WS channel and delivers to in-process
   * subscribers via the broker. `subscribe` registers a handler scoped to the
   * run and channel; returns an unsubscribe function.
   * Undefined in contexts without a run id (manifest-only contexts).
   */
  readonly messaging?: {
    publish(channel: string, message: unknown): void;
    subscribe(channel: string, handler: (msg: unknown) => void): () => void;
  };
}

export type StepKind = 'agent' | 'code' | 'approval' | 'switch' | 'do_while';

/**
 * One branch of a SWITCH step. The first case whose condition evaluates to true
 * is selected; all other branches' stepIds are excluded from execution.
 */
export interface SwitchCase {
  /** Human-readable label for this branch (reported in switch output). */
  label: string;
  /** Pure guard — called with the current step context to decide this branch. */
  condition: (ctx: StepContext) => boolean;
  /** stepIds belonging to this branch. */
  stepIds: string[];
}

export type TriggerRule = 'all_success' | 'one_success' | 'all_done';

/** Possible statuses for a flow step (persisted in flow_steps.status). */
export type StepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped' | 'cancelled' | 'timed_out';

/** Retry policy for a step that times out. */
export interface RetryConfig {
  maxRetries: number;
}

export interface Step {
  /** unique id within the flow; other steps depend on it by this id */
  id: string;
  kind: StepKind;
  /** ids that must complete (or skip) before this step runs */
  deps?: string[];
  /** how dependency satisfaction is evaluated (default: all_success) */
  trigger_rule?: TriggerRule;
  /** for kind:'agent' — the persona file name under conductor/agents (no .md) */
  agent?: string;
  /**
   * For kind:'agent', returns the worker PROMPT (task + any prior outputs).
   * For kind:'code', returns the step RESULT directly (the fold/transform).
   * For kind:'switch', unused (the runner evaluates cases internally).
   */
  run: (ctx: StepContext) => string | Promise<string>;
  /** optional guard — when it returns false the step is skipped (e.g. no repo) */
  when?: (ctx: StepContext) => boolean;
  /** max retries on timeout (0 or unset = no retry) */
  maxRetries?: number;
  /** batch group id; steps sharing the same batch are gated by batchConfig.maxConcurrent */
  batch?: string;
  /** for kind:'switch' — ordered list of branches evaluated in declaration order */
  cases?: SwitchCase[];
  /** for kind:'switch' — fallback step ids when no case matches */
  defaultBranch?: string[];
  /** for kind:'do_while' — step IDs in the loop body (re-evaluated each iteration) */
  loopBody?: string[];
  /** for kind:'do_while' — guard evaluated each iteration; terminates when false */
  loopCondition?: (ctx: StepContext) => boolean;
  /** for kind:'do_while' — cap on total iterations (default 100) */
  loopMaxIterations?: number;
}

export interface Flow {
  name: string;
  description: string;
  steps: Step[];
  /** assemble the final artifact from all step results */
  render: (ctx: StepContext) => string;
  /** optional output filename for the artifact, derived from input */
  output?: (ctx: StepContext) => string;
  /** batch parallelism control — gates concurrent dispatch of steps sharing the same batch id */
  batchConfig?: { maxConcurrent: number; timeoutMs?: number; joinRule?: TriggerRule };
}

export interface RunResult {
  results: Record<string, string>;
  artifact: string;
  outputPath?: string;
}

import type { Contract } from './contracts.js';

/** Han's sizing bands — select how many angles fan out. */
export type Band = 'small' | 'medium' | 'large';

/** One parallel discovery/analysis angle in a spine (a fan-out worker). */
export interface Angle {
  /** step id (also the section label in the report) */
  id: string;
  /** persona dispatched for this angle */
  agent: string;
  /** human label for the report heading */
  label: string;
  /** smallest band at which this angle runs (default 'small') */
  minBand?: Band;
  /** extra guard, e.g. only when a repo was given */
  when?: (ctx: StepContext) => boolean;
  /** build the worker task prompt */
  task: (ctx: StepContext) => string;
}

/**
 * A Han-style skill as data. The factory (spine.ts) turns this into a Flow:
 * angles fan out in parallel → code fold → optional synthesizer agent →
 * adversarial gate → render. This is the shape ~most Han skills share
 * (research, investigate, architectural-analysis, gap-analysis, security…);
 * skills with a genuinely different shape (e.g. code-review's per-finding
 * verify pipeline) get a bespoke Flow instead.
 */
export interface Spine {
  name: string;
  description: string;
  /** the parallel angles (fan-out) */
  angles: Angle[];
  /** optional agent that synthesises the folded angles (e.g. software-architect) */
  synthesizer?: { agent: string; label: string; task: (ctx: StepContext) => string };
  /** the adversarial gate; defaults to adversarial-validator if omitted */
  validator?: { agent: string; label: string; task: (ctx: StepContext) => string };
  /**
   * Han rule contracts injected into every worker brief and the validator
   * charter. Defaults to ['evidence']. Add 'yagni' for flows that PRODUCE a
   * committable artifact (plans, specs, standards, ADRs, runbooks, tests).
   */
  contracts?: Contract[];
}
