import type { ILlm } from './llm.js';
import type { ISkillMeta } from './skill.js';
import type {
  ISubAgent,
  ISubAgentResult,
  SubAgentRegistry,
} from './subagent.js';
import type { LlmTool, LlmUsage } from './types.js';

export interface PlanStep {
  id: string;
  goal: string;
  agent?: string;
  inputTemplate?: string;
  /**
   * When true, the Coordinator embeds the client request (`ctx.inputText`)
   * verbatim as delimited data inside the composed `task`. Default false —
   * no material is forwarded unless the planner asks for it.
   */
  needsInput?: boolean;
  expectedTools?: string[];
  status: 'pending' | 'in_progress' | 'done' | 'failed';
}

export interface Plan {
  steps: PlanStep[];
  /**
   * Shared objective for the whole plan ("why"), authored once by the planner.
   * Forwarded into every dispatched step's `task` so subagents act as a team.
   */
  objective?: string;
  /**
   * Set by the initial planner when it cannot form an unambiguous plan. When
   * present, the Coordinator streams it to the consumer and dispatches nothing.
   */
  clarification?: string;
  rationale?: string;
  createdAt: number;
  source: 'planner-llm' | 'skill-steps' | 'manual';
}

/**
 * Flat diagnostic trace for an epicfail failure. Captures a single frame:
 * which step failed, which agent, the retry attempts, and the root error
 * message. There is no nesting — each failure is represented independently.
 */
export interface EpicFailTrace {
  stepId: string;
  agentName: string;
  attempts: Array<{
    kind: 'transient' | 'replan' | 'hint';
    error: string;
    durationMs: number;
  }>;
  originalError: string;
}

export interface StepResult {
  stepId: string;
  output: string;
  toolCalls?: ISubAgentResult['toolCalls'];
  usage?: LlmUsage;
  durationMs: number;
  ok: boolean;
  error?: string;
  /**
   * Populated when a child subagent returned `errorClass: 'epicfail'`.
   * Carries a flat diagnostic trace (single frame) upward so consumers
   * can inspect which step and agent failed.
   */
  epicFailTrace?: EpicFailTrace;
}

export interface ICoordinatorContext {
  inputText: string;
  systemPrompt?: string;
  skillContent?: string;
  /**
   * Metadata of the first active skill that declares structured `steps:` in
   * its frontmatter. Populated by CoordinatorHandler from `ctx.selectedSkills`.
   * Consumed by SkillStepsPlanning to derive the plan from the skill itself
   * instead of asking a planner LLM.
   */
  activeSkillMeta?: ISkillMeta;
  registry: SubAgentRegistry;
  plan?: Plan;
  stepResults: Record<string, StepResult>;
  signal?: AbortSignal;
  sessionId: string;
  /**
   * Per-request RAG-selected tools (MCP + client external) available to a
   * self-dispatched step's tool-loop (#157). Empty/absent → the step runs a
   * single toolless `llm.chat()` (legacy behaviour).
   */
  selectedTools?: readonly LlmTool[];
  /**
   * Executes a tool by name and returns its textual result. Built by the
   * coordinator handler from the connected MCP clients. With `selectedTools`,
   * this lets a self-dispatched step actually CALL MCP (read a table, list a
   * package, …) instead of hallucinating a toolless answer (#157). Absent →
   * no tool-loop.
   */
  callTool?: (
    name: string,
    args: unknown,
    signal?: AbortSignal,
  ) => Promise<string>;
}

export interface IPlanningStrategy {
  readonly name: string;
  buildInitialPlan(ctx: ICoordinatorContext): Promise<Plan>;
  shouldReplan(ctx: ICoordinatorContext, lastResult: StepResult): boolean;
  rebuildPlan(ctx: ICoordinatorContext, remaining: PlanStep[]): Promise<Plan>;
}

export interface IDispatchStrategy {
  readonly name: string;
  dispatch(step: PlanStep, ctx: ICoordinatorContext): Promise<StepResult>;
}

export interface IActivationStrategy {
  readonly name: string;
  shouldActivate(ctx: {
    hasSubAgents: boolean;
    hasStructuredSkill: boolean;
  }): boolean;
}

export interface ICoordinatorConfig {
  planning?: IPlanningStrategy;
  dispatch?: IDispatchStrategy;
  activation?: IActivationStrategy;
  plannerLlm?: ILlm;
  maxSteps?: number;
  maxRetriesPerStep?: number;
  failPolicy?: 'abort' | 'continue';
}

export type SubAgentWithDescription = ISubAgent & { description: string };
