//! ToolUseLoop 配置与请求构建辅助。
//!
//! - `ToolUseConfig` — 纯参数，Clone + Send + Sync
//! - `ToolUseDeps` — 策略服务，Arc 包裹
//! - `build_request_*` — 请求构建辅助函数

use lellm_core::{ChatRequest, LlmError, Message, ToolDefinition};
use lellm_provider::ResolvedModel;

use super::context::ContextBudget;
use super::fallback::FallbackStrategy;
use super::request_opts::RequestOptions;
use super::retry::RetryPolicy;
use std::sync::Arc;

// ─── 配置（纯参数）──────────────────────────────────────────────

/// ToolUseLoop 纯参数配置。
///
/// - `Clone` + `Send` + `Sync` — 可安全跨线程共享
/// - 仅包含数据字段，不含行为逻辑
/// - 未来可扩展为 `Serialize` / `Deserialize`
#[derive(Debug, Clone)]
pub struct ToolUseConfig {
    /// 系统提示（运行时注入，不修改 messages）
    pub system_prompt: Option<String>,
    /// 最大迭代轮次（默认 10）
    pub max_iterations: usize,
    /// 每次 LLM 请求的最大输出 token 数（默认 4k）
    ///
    /// 控制单次 Provider 调用的响应长度上限，防止模型输出过长。
    /// 工具调用轮次通常只需几百 token，但模型 thinking 会消耗额外空间，
    /// 4k 在"够用"和"不浪费"之间取得平衡。
    /// 若需要长文本生成，可通过 Builder 调大。
    /// 会自动注入到 `ChatRequest.max_tokens`。
    pub max_output_tokens: u32,
    /// 整个 Agent Run 的最大输出 token 总数（可选，默认无限制）。
    ///
    /// 即使每轮的 `max_output_tokens` 设置合理，多轮工具调用仍可能导致
    /// 总输出巨大（如 10 轮 × 4k = 40k）。此字段提供聚合层面的保险丝，
    /// 防止因工具循环或 Provider 忽略 max_tokens 而导致的成本失控。
    ///
    /// 统计范围：Assistant Text（不含 Thinking，不含 Tool Call 结构开销）。
    /// 在流式模式下边接收边检查，达到阈值立即停止。
    pub max_total_output_tokens: Option<u32>,
    /// 整个 Agent Run 的最大推理 token 总数（可选，默认无限制）。
    ///
    /// 与 `max_total_output_tokens` 分离：thinking 是模型内部推理，不计入输出预算。
    /// 双层设计：
    /// - 单轮：`RequestOptions.max_reasoning_tokens` → 透传给 Provider
    /// - 总计：`max_total_reasoning_tokens` → Agent 层累计检查
    pub max_total_reasoning_tokens: Option<u32>,
    /// 上下文预算管理（默认开启）
    ///
    /// **v0.1**: 默认 `ContextBudget::default()`（max_tokens = 128,000）
    /// **v0.2**: 从 `ResolvedModel.context_window` 自动推导（window * 0.8）
    ///
    /// 若要关闭限制，设置 `max_tokens = usize::MAX`。
    pub context_budget: ContextBudget,
    /// 每轮 LLM 调用的生成参数覆盖。
    ///
    /// 独立字段定义，不与 `ChatRequest` 耦合。
    /// `apply()` 方法将非默认值（temperature、top_p、reasoning 等）
    /// 覆盖到 Agent 层构建的基础 `ChatRequest` 上。
    ///
    /// `model`、`messages`、`tools` 由 Agent 层注入，不会被覆盖。
    pub request_options: RequestOptions,
    /// 是否向消费者流式输出推理过程（ThinkingDelta 事件）。
    ///
    /// `false`（默认）= 模型可推理，但不向消费者发射 ThinkingDelta 事件
    /// `true` = 将推理内容以 ThinkingDelta 事件流式输出
    ///
    /// **重要：** 此字段控制框架行为（Event 管道），不属于协议参数。
    /// 不应出现在 `ChatRequest` 中（Codec 不应看到此字段）。
    pub stream_thinking: bool,
    /// 工具重试策略
    pub retry_policy: RetryPolicy,
}

impl Default for ToolUseConfig {
    fn default() -> Self {
        Self {
            system_prompt: None,
            max_iterations: 10,
            max_output_tokens: 4_000,
            max_total_output_tokens: None,
            max_total_reasoning_tokens: None,
            context_budget: ContextBudget::default(),
            request_options: RequestOptions::default(),
            stream_thinking: false,
            retry_policy: RetryPolicy::default(),
        }
    }
}

// ─── 依赖（策略服务）────────────────────────────────────────────

/// ToolUseLoop 策略依赖。
///
/// 包含有行为逻辑的服务对象（Arc 包裹），与纯参数 Config 分离。
#[derive(Clone)]
pub struct ToolUseDeps {
    /// Provider 降级策略
    pub fallback: Arc<dyn FallbackStrategy>,
}

impl Default for ToolUseDeps {
    fn default() -> Self {
        Self {
            fallback: Arc::new(super::fallback::DefaultFallback::default()),
        }
    }
}

// ─── 辅助函数 ───────────────────────────────────────────────────

/// 检查消息列表中是否已存在 System 消息。
pub(super) fn has_system_message(messages: &[Message]) -> bool {
    messages.iter().any(|m| matches!(m, Message::System { .. }))
}

/// 构建有效的请求消息列表（用于 spawned task，无法使用 &self）
pub(super) fn build_request_messages_inner(
    config: &ToolUseConfig,
    messages: &[Message],
) -> Result<Vec<Message>, LlmError> {
    if let Some(ref sp) = config.system_prompt {
        if has_system_message(messages) {
            return Err(LlmError::DuplicateSystemPrompt);
        }
        let mut result = vec![Message::System {
            content: lellm_core::text_block(sp.clone()),
        }];
        result.extend(messages.iter().cloned());
        Ok(result)
    } else {
        Ok(messages.to_vec())
    }
}

/// 构建 ChatRequest（用于 spawned task）
///
/// 先构建基础请求（Agent 层注入 model/messages/tools/max_tokens），
/// 再应用 RequestOptions 非默认值覆盖。
///
/// `definitions` — 预解析的工具定义列表（从 ResolvedRound 获取）。
pub(super) fn build_request_inner(
    model: &ResolvedModel,
    messages: &[Message],
    max_output_tokens: u32,
    request_options: &RequestOptions,
    definitions: &[ToolDefinition],
) -> ChatRequest {
    let tools = if definitions.is_empty() {
        None
    } else {
        Some(definitions.to_vec())
    };

    let mut req = ChatRequest {
        model: model.model.clone(),
        messages: messages.to_vec(),
        tools,
        max_tokens: Some(max_output_tokens),
        temperature: None,
        top_p: None,
        seed: None,
        tool_choice: None,
        stop_sequences: None,
        prefill: None,
        reasoning: None,
        max_reasoning_tokens: None,
        extra: None,
    };

    // 应用 RequestOptions 非默认值覆盖
    request_options.apply(&mut req);

    req
}

/// 构建首轮 ChatRequest，支持强制指定工具（仅第一轮生效）。
///
/// 当 `RequestOptions` 设置了 `tool_choice` 时，仅在第一轮注入；
/// 后续轮次由 LLM 自主决定是否调用工具。
///
/// `definitions` — 预解析的工具定义列表（从 ResolvedRound 获取）。
pub(super) fn build_request_inner_with_round(
    model: &ResolvedModel,
    messages: &[Message],
    max_output_tokens: u32,
    request_options: &RequestOptions,
    iteration: usize,
    definitions: &[ToolDefinition],
) -> ChatRequest {
    let mut req = build_request_inner(
        model,
        messages,
        max_output_tokens,
        request_options,
        definitions,
    );

    // 如果 RequestOptions 设置了 tool_choice 且不是第一轮，清除它
    // 让 LLM 在工具调用后自主选择
    if iteration > 0 && request_options.tool_choice.is_some() {
        req.tool_choice = None;
    }

    req
}

/// 构建空的 ChatResponse（边界情况兜底）
pub(super) fn empty_response() -> lellm_core::ChatResponse {
    lellm_core::ChatResponse::new(
        lellm_core::text_block(String::new()),
        lellm_core::TokenUsage::default(),
        serde_json::Value::Null,
    )
}
