/**
 * Action Executor
 * Executes individual actions (tool calls, delegation)
 */

import type { IterationContext } from '../core/types'
import type { ToolCallAction } from './types'
import { addToolError } from '../memory/utils'
import { ToolingError, timeoutError } from '../../tools/types'
import { DEFAULT_TOOL_TIMEOUT } from '../../../../platform/constants/timeouts'

/**
 * Execute a single tool call action
 * Handles tool lookup, validation, execution, and error handling
 *
 * Results (success or error) are added to memory as tool-result messages
 *
 * @param iterationContext - Agent execution context
 * @param action - Tool call action to execute
 */
export async function executeToolCall(iterationContext: IterationContext, action: ToolCallAction): Promise<void> {
  // Emit tool call event (before execution)
  await iterationContext.executionContext.onMessageEvent?.({
    type: 'agent:tool_call',
    toolName: action.name,
    args: action.input
  })

  // Track tool call timing
  const toolStartTime = Date.now()

  // Look up tool via registry (O(1) instead of O(n))
  const tool = iterationContext.toolRegistry.get(action.name)
  if (!tool) {
    const toolEndTime = Date.now()
    const toolDuration = toolEndTime - toolStartTime

    // Emit tool error event
    await iterationContext.executionContext.onMessageEvent?.({
      type: 'agent:tool_result',
      toolName: action.name,
      success: false,
      error: `Tool '${action.name}' not found`
    })

    // Log tool call event (failed - tool not found)
    iterationContext.logger.toolCall(
      action.name,
      iterationContext.iteration,
      toolStartTime,
      toolEndTime,
      toolDuration,
      false,
      `Tool '${action.name}' not found`,
      action.input,
      undefined
    )

    addToolError(
      iterationContext.memoryManager,
      action,
      `Tool '${action.name}' not found`,
      iterationContext.iteration,
      iterationContext.executionContext.sessionTurnNumber ?? null
    )
    return
  }

  try {
    // Validate input against tool's schema
    const validatedInput = tool.inputSchema.parse(action.input)

    // Compose timeout signal: per-tool timeout OR execution ceiling, whichever fires first
    const toolTimeout = tool.timeout ?? DEFAULT_TOOL_TIMEOUT
    const signals: AbortSignal[] = [AbortSignal.timeout(toolTimeout)]
    if (iterationContext.executionContext.signal) {
      signals.push(iterationContext.executionContext.signal)
    }
    const composedSignal = AbortSignal.any(signals)

    // Execute tool with Promise.race to enforce timeout even if tool ignores the signal
    const rawResult = await Promise.race([
      tool.execute({
        input: validatedInput,
        executionContext: iterationContext.executionContext,
        iterationContext,
        signal: composedSignal
      }),
      new Promise<never>((_, reject) => {
        if (composedSignal.aborted) {
          reject(timeoutError(action.name))
          return
        }
        composedSignal.addEventListener('abort', () => reject(timeoutError(action.name)), { once: true })
      })
    ])

    // Validate output against schema
    const validatedResult = tool.outputSchema.parse(rawResult)

    const toolEndTime = Date.now()
    const toolDuration = toolEndTime - toolStartTime

    // Emit tool success event
    await iterationContext.executionContext.onMessageEvent?.({
      type: 'agent:tool_result',
      toolName: action.name,
      success: true,
      result: validatedResult
    })

    // Log tool call event (success)
    iterationContext.logger.toolCall(
      action.name,
      iterationContext.iteration,
      toolStartTime,
      toolEndTime,
      toolDuration,
      true,
      undefined,
      action.input,
      validatedResult
    )

    // Add validated result to memory
    const memoryStartTime = Date.now()

    iterationContext.memoryManager.addToHistory({
      type: 'tool-result',
      content: JSON.stringify(validatedResult),
      turnNumber: iterationContext.executionContext.sessionTurnNumber ?? null,
      iterationNumber: iterationContext.iteration
    })

    const memoryEndTime = Date.now()
    const memoryDuration = memoryEndTime - memoryStartTime

    iterationContext.logger.action(
      'memory-tool-result',
      `Stored tool-result for ${action.name} (${JSON.stringify(validatedResult).length} chars), history size: ${iterationContext.memoryManager.getHistoryLength()}`,
      iterationContext.iteration,
      memoryStartTime,
      memoryEndTime,
      memoryDuration
    )
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error)
    const toolEndTime = Date.now()
    const toolDuration = toolEndTime - toolStartTime

    // Emit tool error event
    await iterationContext.executionContext.onMessageEvent?.({
      type: 'agent:tool_result',
      toolName: action.name,
      success: false,
      error: errorMessage
    })

    // Log tool call event (failed)
    iterationContext.logger.toolCall(
      action.name,
      iterationContext.iteration,
      toolStartTime,
      toolEndTime,
      toolDuration,
      false,
      errorMessage,
      action.input,
      undefined
    )

    // Add error to memory (don't throw - continue with other actions)
    const memoryStartTime = Date.now()

    // Extract structured metadata if available
    const metadata = error instanceof ToolingError
      ? {
          errorType: error.errorType,
          severity: error.severity,
          isRetryable: error.isRetryable()
        }
      : undefined

    addToolError(
      iterationContext.memoryManager,
      action,
      errorMessage,
      iterationContext.iteration,
      iterationContext.executionContext.sessionTurnNumber ?? null,
      metadata
    )

    const memoryEndTime = Date.now()
    const memoryDuration = memoryEndTime - memoryStartTime

    iterationContext.logger.action(
      'memory-tool-error',
      `Stored error for ${action.name}: ${errorMessage}, history size: ${iterationContext.memoryManager.getHistoryLength()}`,
      iterationContext.iteration,
      memoryStartTime,
      memoryEndTime,
      memoryDuration
    )
  }
}
