//! Main execution loop logic

use crate::agent::{AgentExecution, AgentState, AgentStep, ExecutionError, ExecutionOutcome};
use crate::error::{SageError, SageResult, UnifiedError};
use crate::session::{UnifiedToolCall, WireTokenUsage};

use super::UnifiedExecutor;
use super::event_manager::ExecutionEvent;

impl UnifiedExecutor {
    /// Run the main execution loop
    ///
    /// Returns the execution outcome and the final message list (for conversation history).
    pub(super) async fn run_execution_loop(
        &mut self,
        mut execution: AgentExecution,
        mut messages: Vec<crate::llm::messages::LlmMessage>,
        tool_schemas: Vec<crate::tools::types::ToolSchema>,
        task_scope: crate::interrupt::TaskScope,
        provider_name: String,
        max_steps: Option<u32>,
    ) -> SageResult<(ExecutionOutcome, Vec<crate::llm::messages::LlmMessage>)> {
        // Repetition detection: track recent outputs to detect loops
        const MAX_RECENT_OUTPUTS: usize = 3;
        const REPETITION_THRESHOLD: usize = 2; // Force completion after N similar outputs
        let mut recent_outputs: std::collections::VecDeque<String> =
            std::collections::VecDeque::with_capacity(MAX_RECENT_OUTPUTS);

        // Set max steps in event manager
        self.event_manager.set_max_steps(max_steps);

        let outcome = 'execution_loop: {
            let mut step_number = 0u32;
            loop {
                step_number += 1;
                self.event_manager.set_step(step_number);

                // Check max_steps limit (None = unlimited)
                if let Some(max) = max_steps.filter(|&max| step_number > max) {
                    tracing::warn!("Reached maximum steps: {}", max);
                    execution.complete(false, Some("Reached maximum steps".to_string()));
                    break 'execution_loop ExecutionOutcome::MaxStepsReached { execution };
                }

                // Check for interrupt before each step
                if task_scope.is_cancelled() {
                    self.event_manager.stop_animation().await;
                    tracing::info!("Task interrupted by user");
                    execution.complete(false, Some("Interrupted by user".to_string()));
                    break 'execution_loop ExecutionOutcome::Interrupted { execution };
                }

                // Execute one step
                match self
                    .execute_step(step_number, &messages, &tool_schemas, &task_scope)
                    .await
                {
                    Ok((mut step, new_messages)) => {
                        // Repetition detection: check if output is repeated
                        // Only check non-empty content - empty strings are normal when AI only calls tools
                        if let Some(ref response) = step.llm_response {
                            let content_trimmed = response.content.trim();

                            // Skip repetition detection for empty or very short content
                            // (AI often returns empty content when only calling tools)
                            if content_trimmed.len() >= 10 {
                                let output_key = if content_trimmed.len() > 200 {
                                    // Use first 200 chars as fingerprint for long outputs
                                    content_trimmed.chars().take(200).collect::<String>()
                                } else {
                                    content_trimmed.to_string()
                                };

                                // Count how many recent outputs are similar
                                let repetition_count =
                                    recent_outputs.iter().filter(|o| *o == &output_key).count();

                                if repetition_count >= REPETITION_THRESHOLD {
                                    tracing::warn!(
                                        repetition_count = repetition_count,
                                        "Detected repeated output, forcing completion to prevent infinite loop"
                                    );
                                    step.state = AgentState::Completed;
                                }

                                // Track this output
                                if recent_outputs.len() >= MAX_RECENT_OUTPUTS {
                                    recent_outputs.pop_front();
                                }
                                recent_outputs.push_back(output_key);
                            }
                        }

                        let is_completed = step.state == AgentState::Completed;

                        // Session recording is handled by llm_interaction.rs and tool_execution.rs
                        // which record each request/response/tool_call/tool_result individually

                        // Record assistant message in JSONL session
                        self.record_step_in_session(&step).await?;

                        execution.add_step(step);

                        // Update messages for next iteration
                        messages = new_messages;

                        if is_completed {
                            execution
                                .complete(true, Some("Task completed successfully".to_string()));
                            break 'execution_loop ExecutionOutcome::Success(execution);
                        }
                    }
                    Err(e) => {
                        self.event_manager.stop_animation().await;

                        // Check if this is a user cancellation
                        if matches!(e, SageError::Cancelled) {
                            execution.complete(false, Some("Cancelled by user".to_string()));
                            break 'execution_loop ExecutionOutcome::UserCancelled {
                                execution,
                                pending_question: None,
                            };
                        }

                        let error_message = e.to_string();
                        let error_step = AgentStep::new(step_number, AgentState::Error)
                            .with_error(error_message.clone());

                        // Determine error type using structured error codes
                        let error_type = match e.error_code() {
                            "SAGE_LLM" | "SAGE_HTTP" => "api_error",
                            "SAGE_TIMEOUT" => "timeout_error",
                            "SAGE_TOOL" => "tool_error",
                            "SAGE_CONFIG" => "config_error",
                            "SAGE_IO" | "SAGE_STORAGE" => "io_error",
                            "SAGE_JSON" => "parse_error",
                            "SAGE_CANCELLED" => "cancelled_error",
                            _ => "execution_error",
                        };

                        // Record error in trajectory (for replay)
                        if let Some(recorder) = self.session_manager.session_recorder() {
                            if let Err(e) = recorder
                                .lock()
                                .await
                                .record_error(error_type, &error_message)
                                .await
                            {
                                tracing::warn!(
                                    error = %e,
                                    "Failed to record error in trajectory (non-fatal)"
                                );
                            }
                        }

                        // Record error in JSONL session (for user visibility)
                        if let Err(e) = self.record_error_message(error_type, &error_message).await
                        {
                            tracing::warn!(
                                error = %e,
                                "Failed to record error message in session (non-fatal)"
                            );
                        }

                        execution.add_step(error_step);
                        execution.complete(false, Some(format!("Task failed: {}", e)));

                        // Emit error event through injected UI context.
                        self.event_manager
                            .emit(ExecutionEvent::ErrorOccurred {
                                error_type: error_type.to_string(),
                                message: error_message.clone(),
                            })
                            .await;

                        let exec_error =
                            ExecutionError::from_sage_error(&e, Some(provider_name.clone()));
                        break 'execution_loop ExecutionOutcome::Failed {
                            execution,
                            error: exec_error,
                        };
                    }
                }
            }

            // This is unreachable since the loop only exits via break statements
            // The compiler requires this branch for exhaustiveness
            #[allow(unreachable_code)]
            {
                unreachable!("Execution loop should exit via break statements")
            }
        };

        Ok((outcome, messages))
    }

    /// Record step in JSONL session
    async fn record_step_in_session(&mut self, step: &AgentStep) -> SageResult<()> {
        if self.session_manager.current_session_id().is_none() {
            return Ok(());
        }

        let Some(llm_response) = step.llm_response.as_ref() else {
            return Ok(());
        };

        // Convert tool calls if any exist
        let tool_calls = if !llm_response.tool_calls.is_empty() {
            Some(
                llm_response
                    .tool_calls
                    .iter()
                    .map(|c| UnifiedToolCall {
                        id: c.id.clone(),
                        name: c.name.clone(),
                        arguments: serde_json::to_value(&c.arguments)
                            .unwrap_or(serde_json::Value::Object(Default::default())),
                    })
                    .collect(),
            )
        } else {
            None
        };

        // Convert usage if available
        let usage = llm_response.usage.as_ref().map(WireTokenUsage::from);

        // Record assistant message and get the message UUID
        if let Ok(Some(msg)) = self
            .record_assistant_message(&llm_response.content, tool_calls, usage)
            .await
        {
            // Record file snapshot if files were tracked
            if let Err(e) = self.record_file_snapshot(&msg.uuid).await {
                tracing::warn!(
                    error = %e,
                    message_uuid = %msg.uuid,
                    "Failed to record file snapshot (non-fatal)"
                );
            }
        }

        Ok(())
    }
}
