import { randomUUID } from 'node:crypto';
import type { OpenAICompatClient } from '../../llm/openai-compat.js';
import type { SafetyConfig } from '../../config.js';
import { summarizeToolInput } from '../../progress/log-format.js';
import { ContextManager } from '../context-manager.js';
import { Conversation } from '../context/conversation.js';
import { ToolResultCache } from '../context/tool-result-cache.js';
import type { ToolContext } from '../tools/index.js';
import type {
  AgentLoopCallbacks,
  ExecuteMovementOptions,
  Movement,
  MovementResult,
} from './types.js';

type ExecuteSubMovement = (
  movement: Movement,
  taskInstruction: string,
  client: OpenAICompatClient,
  ctx: ToolContext,
  options: ExecuteMovementOptions,
) => Promise<MovementResult>;

/**
 * Run an isolated sub-agent for the `delegate` tool.
 *
 * Each sub-agent gets its own Conversation, ContextManager, ToolResultCache,
 * and mcpQuotaState — none of those are shared with the parent. The parent's
 * conversation.messages are never touched by the sub-run (isolation invariant).
 */
export async function runDelegateSubAgent(
  params: { description: string; prompt: string },
  opts: {
    client: OpenAICompatClient;
    parentCtx: ToolContext;
    parentMovement: Movement;
    depth: number;
    maxDelegationDepth: number;
    maxIterations?: number;
    safetyConfig?: SafetyConfig;
    cancelSignal?: AbortSignal;
    cancelCheck?: () => boolean;
    contextLimit: number;
    parentCallbacks?: AgentLoopCallbacks;
    executeSubMovement: ExecuteSubMovement;
  },
): Promise<{ result: string; status: 'success' | 'aborted' | 'needs_user_input' }> {
  const {
    client, parentCtx, parentMovement,
    depth, maxDelegationDepth, maxIterations, safetyConfig,
    cancelSignal, cancelCheck, contextLimit,
    parentCallbacks, executeSubMovement,
  } = opts;

  const atLeafDepth = depth >= maxDelegationDepth;
  const subAllowedTools = atLeafDepth
    ? parentMovement.allowedTools.filter((t) => t !== 'delegate')
    : parentMovement.allowedTools;

  const subMovement: Movement = {
    ...parentMovement,
    name: `delegate:${params.description.slice(0, 40)}`,
    instruction: params.prompt,
    rules: [],
    allowedTools: subAllowedTools,
    defaultNext: undefined,
  };

  const parentRunId = parentCtx.delegateRunId ?? null;
  const delegateRunId = randomUUID();

  const delegateCallbacks: AgentLoopCallbacks | undefined = parentCallbacks && {
    onText: (text) => parentCallbacks.onDelegateText?.(delegateRunId, text),
    onToolUse: (name, input) =>
      parentCallbacks.onDelegateTool?.(delegateRunId, name, summarizeToolInput(name, input)),
    onDelegateLifecycle: parentCallbacks.onDelegateLifecycle,
    onDelegateText: parentCallbacks.onDelegateText,
    onDelegateTool: parentCallbacks.onDelegateTool,
  };

  const subConversation = new Conversation(undefined);
  const subContextManager = new ContextManager({ limitTokens: contextLimit });
  const subCache = new ToolResultCache();
  const subEvents = parentCtx.eventLogger?.child({
    movement: `delegate:${params.description.slice(0, 40)}`,
    correlationId: delegateRunId,
  });

  const subCtx: ToolContext = {
    ...parentCtx,
    delegationDepth: depth,
    delegateRunId,
    runDelegate: undefined,
    contextManager: subContextManager,
    mcpQuotaState: { files: 0, bytes: 0 },
    eventLogger: subEvents ?? parentCtx.eventLogger,
  };

  parentCtx.eventLogger?.emit('delegate_start', {
    delegateRunId, parentRunId, description: params.description, depth,
  });
  parentCallbacks?.onDelegateLifecycle?.({
    delegateRunId, parentRunId, depth, description: params.description, status: 'running',
  });

  const res = await executeSubMovement(subMovement, params.prompt, client, subCtx, {
    maxIterations,
    safetyConfig,
    contextManager: subContextManager,
    cancelSignal,
    cancelCheck,
    toolResultCache: subCache,
    conversation: subConversation,
    visitCount: 1,
    maxVisits: 1,
    callbacks: delegateCallbacks,
  });

  const status: 'success' | 'aborted' | 'needs_user_input' =
    res.next === 'COMPLETE' ? 'success'
    : res.next === 'ASK'    ? 'needs_user_input'
    :                         'aborted';

  const resultPreview = (res.output ?? '').slice(0, 2000);

  parentCtx.eventLogger?.emit('delegate_complete', {
    delegateRunId, parentRunId, description: params.description, depth, next: res.next, status, resultPreview,
  });
  parentCallbacks?.onDelegateLifecycle?.({
    delegateRunId, parentRunId, depth, description: params.description, status,
  });

  return { result: res.output ?? '', status };
}
