use super::handle_local::coerce_integer_like_tool_args;

/// Build an assistant message with tool_calls for the conversation history.
/// Format varies by API style (OpenAI-compatible vs Anthropic).
pub(crate) fn build_assistant_tool_message(
    text: &str,
    tool_calls: &[serde_json::Value],
    provider: &str,
    model: &str,
) -> serde_json::Value {
    let is_anthropic = super::super::provider::provider_uses_anthropic_messages(provider, model);
    let is_gemini = super::super::provider::provider_uses_gemini_messages(provider, model);
    let is_ollama = super::super::provider::provider_uses_ollama_messages(provider, model);
    if is_anthropic {
        // Anthropic format: content blocks with text and tool_use
        let mut content = Vec::new();
        if !text.is_empty() {
            content.push(serde_json::json!({"type": "text", "text": text}));
        }
        for tc in tool_calls {
            content.push(serde_json::json!({
                "type": "tool_use",
                "id": tc["id"],
                "name": tc["name"],
                "input": tc["arguments"],
            }));
        }
        serde_json::json!({"role": "assistant", "content": content})
    } else if is_gemini {
        let mut parts = Vec::new();
        if !text.is_empty() {
            parts.push(serde_json::json!({"text": text}));
        }
        for tc in tool_calls {
            let mut function_call = serde_json::json!({
                "name": tc["name"],
                "args": tc["arguments"],
            });
            if let Some(id) = tc.get("id").and_then(|value| value.as_str()) {
                if !id.is_empty() {
                    function_call["id"] = serde_json::json!(id);
                }
            }
            let mut part = serde_json::json!({ "functionCall": function_call });
            if let Some(signature) = tc
                .get("thought_signature")
                .or_else(|| tc.get("thoughtSignature"))
                .and_then(|value| value.as_str())
            {
                if !signature.is_empty() {
                    part["thoughtSignature"] = serde_json::json!(signature);
                }
            }
            parts.push(part);
        }
        serde_json::json!({"role": "assistant", "content": parts})
    } else if is_ollama {
        // Ollama's chat API uses the OpenAI-style `function` envelope but
        // its request schema expects `function.arguments` to be a string.
        let calls: Vec<serde_json::Value> = tool_calls
            .iter()
            .enumerate()
            .map(|(idx, tc)| {
                serde_json::json!({
                    "id": tc["id"],
                    "type": "function",
                    "function": {
                        "index": idx,
                        "name": tc["name"],
                        "arguments": serde_json::to_string(&tc["arguments"]).unwrap_or_default(),
                    }
                })
            })
            .collect();
        let mut msg = serde_json::json!({
            "role": "assistant",
            "tool_calls": calls,
        });
        if !text.is_empty() {
            msg["content"] = serde_json::json!(text);
        }
        msg
    } else {
        // OpenAI-compatible format: assistant message with tool_calls array
        let calls: Vec<serde_json::Value> = tool_calls
            .iter()
            .map(|tc| {
                serde_json::json!({
                    "id": tc["id"],
                    "type": "function",
                    "function": {
                        "name": tc["name"],
                        "arguments": serde_json::to_string(&tc["arguments"]).unwrap_or_default(),
                    }
                })
            })
            .collect();
        serde_json::json!({
            "role": "assistant",
            "content": if text.is_empty() { serde_json::Value::String(String::new()) } else { serde_json::json!(text) },
            "tool_calls": calls,
        })
    }
}

/// Build a durable assistant message for transcript/run-record storage.
/// Prefer canonical structured blocks when available so hosts can restore
/// richer assistant state without reparsing visible text.
pub(crate) fn build_assistant_response_message(
    text: &str,
    blocks: &[serde_json::Value],
    tool_calls: &[serde_json::Value],
    reasoning: Option<&str>,
    provider: &str,
    model: &str,
) -> serde_json::Value {
    let mut message = if !tool_calls.is_empty() {
        if super::super::provider::provider_uses_gemini_messages(provider, model)
            && !blocks.is_empty()
        {
            let content =
                crate::llm::content::gemini_parts(&serde_json::Value::Array(blocks.to_vec()));
            if content
                .iter()
                .any(|part| part.get("functionCall").is_some())
            {
                serde_json::json!({"role": "assistant", "content": content})
            } else {
                build_assistant_tool_message(text, tool_calls, provider, model)
            }
        } else {
            build_assistant_tool_message(text, tool_calls, provider, model)
        }
    } else if !blocks.is_empty() {
        serde_json::json!({
            "role": "assistant",
            "content": blocks,
        })
    } else {
        serde_json::json!({
            "role": "assistant",
            "content": text,
        })
    };
    if let Some(reasoning) = reasoning.filter(|value| !value.is_empty()) {
        message["reasoning"] = serde_json::json!(reasoning);
    }
    message
}

/// Normalize tool call arguments before dispatch.
///
/// The VM walks the active policy's
/// `tool_annotations[name].arg_schema.arg_aliases` table and rewrites any
/// aliases present in the arguments object to their canonical keys. This
/// is purely driven by pipeline declarations — the VM has no hardcoded
/// tool-name branches. If a tool isn't annotated, no aliases are rewritten.
pub(crate) fn normalize_tool_args(name: &str, args: &serde_json::Value) -> serde_json::Value {
    let mut obj = match args.as_object() {
        Some(o) => o.clone(),
        None => return args.clone(),
    };

    if let Some(annotations) = crate::orchestration::current_tool_annotations(name) {
        for (alias, canonical) in &annotations.arg_schema.arg_aliases {
            if obj.contains_key(canonical) {
                continue;
            }
            if let Some(value) = obj.remove(alias) {
                obj.insert(canonical.clone(), value);
            }
        }
    }

    let mut normalized = serde_json::Value::Object(obj);
    coerce_integer_like_tool_args(&mut normalized);
    normalized
}
