/**
 * Action Phase Processor
 * Orchestrates execution of LLM-planned actions
 */

import type { IterationContext } from '../core/types'
import type { LLMIterationResponse } from '../reasoning/types'
import type { AgentAction, ToolCallAction } from './types'
import { executeToolCall } from './executor'
import { executeNavigateKnowledge } from './navigate-knowledge-executor'

/**
 * Validate action sequence for correctness
 * Ensures no invalid patterns that would cause undefined behavior
 *
 * Rules:
 * - No multiple 'complete' actions
 * - 'complete' cannot mix with 'navigate-knowledge' (loading knowledge without
 *   reasoning about it is always wasteful - the LLM should iterate instead)
 * - 'complete' CAN mix with 'tool-call' (side-effect tools like navigate_user,
 *   update_filters are fire-and-forget - tool calls execute first via
 *   Promise.allSettled before completion is signaled)
 * - 'message' actions ARE allowed before 'complete' (user-facing communication)
 *
 * @param actions - Action array to validate
 * @throws Error if validation fails
 */
function validateActionSequence(actions: AgentAction[]): void {
  // Count completion actions
  const completeActions = actions.filter((a) => a.type === 'complete')

  // Rule 1: No duplicate completions
  if (completeActions.length > 1) {
    throw new Error('Multiple complete actions not allowed in single iteration')
  }

  // Rule 2: Completion cannot mix with navigate-knowledge
  // (tool-call + complete is allowed - side-effect tools execute before completion)
  if (completeActions.length === 1) {
    const hasNavigateKnowledge = actions.some((a) => a.type === 'navigate-knowledge')
    if (hasNavigateKnowledge) {
      throw new Error('Complete action cannot mix with navigate-knowledge actions')
    }
  }
}

/**
 * Collapse multiple session-visible messages into one assistant message.
 *
 * Session turns should produce at most one visible assistant bubble per
 * iteration. Non-session executions keep the historical behavior because their
 * messages are not rendered as conversational bubbles.
 */
function normalizeSessionMessages(actions: AgentAction[], sessionCapable: boolean): AgentAction[] {
  if (!sessionCapable) {
    return actions
  }

  const messages = actions.filter((action) => action.type === 'message')
  if (messages.length <= 1) {
    return actions
  }

  const collapsedText = messages.map((message) => message.text).join('\n\n')
  const collapsedMessage: AgentAction = { type: 'message', text: collapsedText }
  let emittedCollapsedMessage = false

  return actions.flatMap((action): AgentAction[] => {
    if (action.type !== 'message') {
      return [action]
    }

    if (emittedCollapsedMessage) {
      return []
    }

    emittedCollapsedMessage = true
    return [collapsedMessage]
  })
}

/**
 * Process all actions from LLM response
 * Executes tool calls in parallel, handles completion, and messages sequentially
 *
 * Tool calls are parallelized using Promise.allSettled() for performance:
 * - Independent tool calls execute concurrently (up to 5x faster)
 * - Partial failures don't abort successful tools
 * - Results added to memory as they complete
 *
 * Other actions (navigate-knowledge, message, complete) remain sequential
 * because they are order-dependent.
 *
 * @param iterationContext - Agent execution context
 * @param response - LLM response with actions to execute
 * @returns Object with shouldComplete flag (no finalAnswer - generated in completion phase)
 */
export async function processActions(
  iterationContext: IterationContext,
  response: LLMIterationResponse
): Promise<{ shouldComplete: boolean }> {
  // Validate action sequence before processing
  validateActionSequence(response.nextActions)
  const normalizedActions = normalizeSessionMessages(response.nextActions, !!iterationContext.config.sessionCapable)

  let shouldComplete = false

  // Group actions by parallelizability
  const toolCalls: ToolCallAction[] = []
  const otherActions: AgentAction[] = []

  for (const action of normalizedActions) {
    if (action.type === 'tool-call') {
      toolCalls.push(action)
    } else {
      otherActions.push(action)
    }
  }

  // Execute tool calls in parallel (if any)
  // Uses Promise.allSettled() for partial success handling:
  // - If 4/5 tools succeed, we get those results
  // - Each tool handles errors independently and adds to memory
  // - No result aggregation needed (each tool call adds to memory directly)
  if (toolCalls.length > 0) {
    await Promise.allSettled(toolCalls.map((action) => executeToolCall(iterationContext, action)))
  }

  // Execute other actions sequentially (order matters)
  for (const action of otherActions) {
    switch (action.type) {
      case 'navigate-knowledge':
        await executeNavigateKnowledge(iterationContext, action)
        break

      case 'complete':
        shouldComplete = true
        break

      case 'message': {
        // Emit assistant message event (immediate persistence + streaming)
        await iterationContext.executionContext.onMessageEvent?.({
          type: 'assistant_message',
          text: action.text
        })
        break
      }
    }
  }

  // A user-facing message with no tool calls and no explicit completion ends a
  // conversational turn. Without this, a tool-less sessionCapable agent loops and
  // emits multiple message bubbles for a single user turn when the LLM omits `complete`.
  if (
    !shouldComplete &&
    iterationContext.config.sessionCapable &&
    toolCalls.length === 0 &&
    normalizedActions.some((a) => a.type === 'message') &&
    !normalizedActions.some((a) => a.type === 'navigate-knowledge')
  ) {
    shouldComplete = true
  }

  return { shouldComplete }
}
