import { DynamicStructuredTool } from '@langchain/core/tools';
import { z } from 'zod';
import type { FastifyBaseLogger } from 'fastify';
import type { SpawnService } from '../../harness/spawn/spawn-service.js';
import type { MCPClientConnector } from '../../harness/mcp-client.js';
import type { WorkspaceManager } from '../../workspace/workspace-manager.js';
import type { MCPToolInfo } from '../mcp/mcp-tool-wrapper.js';
import type { HarnessEvent } from '../../harness/harness-events.js';

export interface SpawnToolConfig {
  spawnService: SpawnService;
  mcpClient: MCPClientConnector;
  availableMCPTools: MCPToolInfo[];
  workspaceManager?: WorkspaceManager;
  logger: FastifyBaseLogger;
}

/**
 * Creates the `spawn` tool for the main agent.
 *
 * The tool accepts an agent name and instruction and delegates to SpawnService,
 * which runs an isolated subagent and returns only its final result.
 *
 * Implements the `streamFunc` protocol so the harness can forward intermediate
 * subagent events (subagent_chunk, subagent_thinking, subagent_tool_call) to
 * the WebSocket client during tool execution.
 */
export function createSpawnTool(config: SpawnToolConfig): DynamicStructuredTool & {
  streamFunc: (args: { agent: string; instruction: string }, signal?: AbortSignal) => AsyncGenerator<HarnessEvent, string>;
} {
  const { spawnService, mcpClient, availableMCPTools, workspaceManager, logger } = config;

  async function* streamFunc(
    { agent, instruction }: { agent: string; instruction: string },
    signal?: AbortSignal,
  ): AsyncGenerator<HarnessEvent, string> {
    logger.info({ agent, instruction: instruction.substring(0, 100) }, 'spawn: streaming subagent');
    const gen = spawnService.streamSpawn({
      agentName: agent,
      instruction,
      mcpClient,
      availableMCPTools,
      workspaceManager,
      signal,
    });

    let step = await gen.next();
    while (!step.done) {
      if (signal?.aborted) {
        await gen.return('');
        break;
      }
      yield step.value;
      step = await gen.next();
    }
    return (step.value as string) ?? '';
  }

  const tool = new DynamicStructuredTool({
    name: 'Spawn',
    description: `Delegate a specialized task to a subagent and return its result.

Available agents:
- **research** — statistical analysis, data visualization, Python scripting, charting
- **indicator** — manage chart indicators: add, remove, modify, create custom indicators
- **strategy** — write PandasStrategy trading strategies, run backtests, activate paper trading
- **web-explore** — search the web, fetch pages, find academic papers

The subagent runs in isolation. Only its final answer is returned — intermediate steps do not appear in this context.

Examples:
- spawn({ agent: "research", instruction: "Plot BTC/USDT RSI over the last 2 years" })
- spawn({ agent: "indicator", instruction: "Add Bollinger Bands with std=1.5 to the chart" })
- spawn({ agent: "strategy", instruction: "Write an RSI mean-reversion strategy and backtest it" })
- spawn({ agent: "web-explore", instruction: "Find documentation on the Donchian Channel indicator" })`,
    schema: z.object({
      agent: z.enum(['research', 'indicator', 'strategy', 'web-explore']).describe(
        'The specialized subagent to invoke'
      ),
      instruction: z.string().describe(
        'Detailed instruction for the subagent. Include all relevant context from the conversation.'
      ),
    }),
    func: async ({ agent, instruction }: { agent: string; instruction: string }): Promise<string> => {
      logger.info({ agent, instruction: instruction.substring(0, 100) }, 'spawn: invoking subagent');
      try {
        const gen = spawnService.streamSpawn({
          agentName: agent,
          instruction,
          mcpClient,
          availableMCPTools,
          workspaceManager,
        });

        // Drain the generator and collect the return value
        let result = '';
        let step = await gen.next();
        while (!step.done) {
          step = await gen.next();
        }
        result = step.value ?? '';
        return result;
      } catch (error) {
        logger.error({ error, agent }, 'spawn: subagent failed');
        throw error;
      }
    },
  });

  return Object.assign(tool, { streamFunc });
}
