//! Claude Code message parsing — extracts text and tool-use info from assistant
//! and user messages.

#![allow(unused_imports, dead_code)]
use crate::llm::types::{FunctionCall, Message, Role, ToolCall};

pub(super) fn parse_assistant_message(
    content: &[serde_json::Value],
    msg_tx: &std::sync::mpsc::Sender<Message>,
) {
    let mut text = String::new();
    let mut tool_calls: Vec<ToolCall> = Vec::new();

    for block in content {
        match block.get("type").and_then(|t| t.as_str()) {
            Some("text") => {
                if let Some(t) = block.get("text").and_then(|t| t.as_str()) {
                    text.push_str(t);
                }
            }
            Some("tool_use") => {
                let id = block
                    .get("id")
                    .and_then(|v| v.as_str())
                    .map(|s| s.to_string());
                let name = block
                    .get("name")
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string();
                let input = block
                    .get("input")
                    .cloned()
                    .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
                let arguments = serde_json::to_string(&input).unwrap_or_default();
                tool_calls.push(ToolCall {
                    id,
                    function: FunctionCall { name, arguments },
                    kind: "function".to_string(),
                });
            }
            _ => {}
        }
    }

    let msg = Message {
        role: Role::Assistant,
        content: text,
        tool_calls: if tool_calls.is_empty() {
            None
        } else {
            Some(tool_calls)
        },
        tool_call_id: None,
    };
    let _ = msg_tx.send(msg);
}

/// Parse a `user` message containing tool_result blocks.
///
/// Claude Code injects tool results into the conversation as `user` role
pub(super) fn parse_tool_results(
    content: &[serde_json::Value],
    msg_tx: &std::sync::mpsc::Sender<Message>,
) {
    for block in content {
        if block.get("type").and_then(|t| t.as_str()) != Some("tool_result") {
            continue;
        }

        let tool_use_id = block
            .get("tool_use_id")
            .and_then(|v| v.as_str())
            .map(|s| s.to_string());

        // `content` in a tool_result can be a plain string or an array of content blocks
        let content_str = match block.get("content") {
            Some(serde_json::Value::String(s)) => s.clone(),
            Some(serde_json::Value::Array(arr)) => {
                // Extract text from content block array
                arr.iter()
                    .filter_map(|b| {
                        if b.get("type").and_then(|t| t.as_str()) == Some("text") {
                            b.get("text")
                                .and_then(|t| t.as_str())
                                .map(|s| s.to_string())
                        } else {
                            None
                        }
                    })
                    .collect::<Vec<_>>()
                    .join("\n")
            }
            Some(other) => serde_json::to_string(other).unwrap_or_default(),
            None => String::new(),
        };

        let _ = msg_tx.send(Message {
            role: Role::Tool,
            content: content_str,
            tool_calls: None,
            tool_call_id: tool_use_id,
        });
    }
}

/// Extract text from a stream event and send to the token channel for live display.
///
/// Stream events provide incremental text deltas for real-time rendering.
#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    fn collect_messages(f: impl Fn(&std::sync::mpsc::Sender<Message>)) -> Vec<Message> {
        let (tx, rx) = std::sync::mpsc::channel();
        f(&tx);
        drop(tx);
        rx.try_iter().collect()
    }

    #[test]
    fn parse_assistant_message_text_only() {
        let content = vec![json!({"type": "text", "text": "Hello, world!"})];
        let msgs = collect_messages(|tx| parse_assistant_message(&content, tx));
        assert_eq!(msgs.len(), 1);
        assert_eq!(msgs[0].role, Role::Assistant);
        assert_eq!(msgs[0].content, "Hello, world!");
        assert!(msgs[0].tool_calls.is_none());
    }

    #[test]
    fn parse_assistant_message_with_tool_use() {
        let content = vec![
            json!({"type": "text", "text": "I'll read that file."}),
            json!({
                "type": "tool_use",
                "id": "toolu_abc123",
                "name": "Read",
                "input": {"file_path": "/src/main.rs"}
            }),
        ];
        let msgs = collect_messages(|tx| parse_assistant_message(&content, tx));
        assert_eq!(msgs.len(), 1);
        let msg = &msgs[0];
        assert_eq!(msg.role, Role::Assistant);
        assert_eq!(msg.content, "I'll read that file.");
        let tool_calls = msg.tool_calls.as_ref().expect("should have tool calls");
        assert_eq!(tool_calls.len(), 1);
        assert_eq!(tool_calls[0].id.as_deref(), Some("toolu_abc123"));
        assert_eq!(tool_calls[0].function.name, "Read");
        let args: serde_json::Value =
            serde_json::from_str(&tool_calls[0].function.arguments).unwrap();
        assert_eq!(args["file_path"], "/src/main.rs");
    }

    #[test]
    fn parse_assistant_message_multiple_tool_uses() {
        let content = vec![
            json!({"type": "tool_use", "id": "id1", "name": "Glob", "input": {"pattern": "*.rs"}}),
            json!({"type": "tool_use", "id": "id2", "name": "Bash", "input": {"command": "cargo test"}}),
        ];
        let msgs = collect_messages(|tx| parse_assistant_message(&content, tx));
        assert_eq!(msgs.len(), 1);
        let tool_calls = msgs[0].tool_calls.as_ref().unwrap();
        assert_eq!(tool_calls.len(), 2);
        assert_eq!(tool_calls[0].function.name, "Glob");
        assert_eq!(tool_calls[1].function.name, "Bash");
    }

    #[test]
    fn parse_tool_results_string_content() {
        let content = vec![json!({
            "type": "tool_result",
            "tool_use_id": "toolu_abc123",
            "content": "fn main() { println!(\"hello\"); }"
        })];
        let msgs = collect_messages(|tx| parse_tool_results(&content, tx));
        assert_eq!(msgs.len(), 1);
        assert_eq!(msgs[0].role, Role::Tool);
        assert_eq!(msgs[0].content, "fn main() { println!(\"hello\"); }");
        assert_eq!(msgs[0].tool_call_id.as_deref(), Some("toolu_abc123"));
    }

    #[test]
    fn parse_tool_results_array_content() {
        let content = vec![json!({
            "type": "tool_result",
            "tool_use_id": "toolu_xyz",
            "content": [
                {"type": "text", "text": "Line 1"},
                {"type": "text", "text": "Line 2"}
            ]
        })];
        let msgs = collect_messages(|tx| parse_tool_results(&content, tx));
        assert_eq!(msgs.len(), 1);
        assert_eq!(msgs[0].content, "Line 1\nLine 2");
        assert_eq!(msgs[0].tool_call_id.as_deref(), Some("toolu_xyz"));
    }

    #[test]
    fn parse_tool_results_multiple_results() {
        let content = vec![
            json!({"type": "tool_result", "tool_use_id": "id1", "content": "result1"}),
            json!({"type": "tool_result", "tool_use_id": "id2", "content": "result2"}),
        ];
        let msgs = collect_messages(|tx| parse_tool_results(&content, tx));
        assert_eq!(msgs.len(), 2);
        assert_eq!(msgs[0].tool_call_id.as_deref(), Some("id1"));
        assert_eq!(msgs[1].tool_call_id.as_deref(), Some("id2"));
    }

    #[test]
    fn parse_tool_results_ignores_non_tool_result_blocks() {
        let content = vec![
            json!({"type": "text", "text": "not a tool result"}),
            json!({"type": "tool_result", "tool_use_id": "id1", "content": "actual result"}),
        ];
        let msgs = collect_messages(|tx| parse_tool_results(&content, tx));
        assert_eq!(msgs.len(), 1);
        assert_eq!(msgs[0].tool_call_id.as_deref(), Some("id1"));
    }

    #[test]
    fn parse_assistant_message_empty_text_with_tool_use() {
        // When a message has only tool_use (no text), content should be empty string
        let content = vec![json!({
            "type": "tool_use",
            "id": "toolu_1",
            "name": "Write",
            "input": {"file_path": "foo.rs", "content": "fn foo() {}"}
        })];
        let msgs = collect_messages(|tx| parse_assistant_message(&content, tx));
        assert_eq!(msgs.len(), 1);
        assert_eq!(msgs[0].content, "");
        assert!(msgs[0].tool_calls.is_some());
    }

    #[test]
    fn parse_tool_results_no_tool_use_id() {
        let content = vec![json!({"type": "tool_result", "content": "output"})];
        let msgs = collect_messages(|tx| parse_tool_results(&content, tx));
        assert_eq!(msgs.len(), 1);
        assert!(msgs[0].tool_call_id.is_none());
        assert_eq!(msgs[0].content, "output");
    }

    #[test]
    fn parse_tool_results_null_content() {
        let content = vec![json!({"type": "tool_result", "tool_use_id": "id1"})];
        let msgs = collect_messages(|tx| parse_tool_results(&content, tx));
        assert_eq!(msgs.len(), 1);
        assert_eq!(msgs[0].content, "");
    }

    #[test]
    fn parse_tool_results_other_json_content() {
        let content = vec![json!({
            "type": "tool_result",
            "tool_use_id": "id1",
            "content": {"nested": "object"}
        })];
        let msgs = collect_messages(|tx| parse_tool_results(&content, tx));
        assert_eq!(msgs.len(), 1);
        // Falls through to serde_json::to_string
        assert!(!msgs[0].content.is_empty());
    }

    #[test]
    fn parse_assistant_message_empty_content_array() {
        let content: Vec<serde_json::Value> = vec![];
        let msgs = collect_messages(|tx| parse_assistant_message(&content, tx));
        assert_eq!(msgs.len(), 1);
        assert_eq!(msgs[0].content, "");
        assert!(msgs[0].tool_calls.is_none());
    }

    #[test]
    fn parse_assistant_message_unknown_block_type() {
        let content = vec![
            json!({"type": "image", "source": {"type": "base64"}}),
            json!({"type": "text", "text": "done"}),
        ];
        let msgs = collect_messages(|tx| parse_assistant_message(&content, tx));
        assert_eq!(msgs.len(), 1);
        assert_eq!(msgs[0].content, "done");
    }

    #[test]
    fn parse_assistant_message_tool_use_without_id() {
        let content = vec![json!({
            "type": "tool_use",
            "name": "Bash",
            "input": {"command": "ls"}
        })];
        let msgs = collect_messages(|tx| parse_assistant_message(&content, tx));
        assert_eq!(msgs.len(), 1);
        let calls = msgs[0].tool_calls.as_ref().unwrap();
        assert_eq!(calls.len(), 1);
        assert!(calls[0].id.is_none());
        assert_eq!(calls[0].function.name, "Bash");
    }

    #[test]
    fn parse_assistant_message_tool_use_without_input_defaults_to_empty_object() {
        let content = vec![json!({"type": "tool_use", "id": "tid", "name": "Read"})];
        let msgs = collect_messages(|tx| parse_assistant_message(&content, tx));
        let calls = msgs[0].tool_calls.as_ref().unwrap();
        let args: serde_json::Value = serde_json::from_str(&calls[0].function.arguments).unwrap();
        assert!(args.is_object());
        assert!(args.as_object().unwrap().is_empty());
    }

    type Channels = (
        tokio::sync::mpsc::UnboundedSender<String>,
        tokio::sync::mpsc::UnboundedReceiver<String>,
        tokio::sync::mpsc::UnboundedSender<String>,
        tokio::sync::mpsc::UnboundedReceiver<String>,
        tokio::sync::mpsc::UnboundedSender<String>,
        tokio::sync::mpsc::UnboundedReceiver<String>,
        std::sync::mpsc::Sender<Message>,
        std::sync::mpsc::Receiver<Message>,
    );

    fn make_channels() -> Channels {
        let (tok_tx, tok_rx) = tokio::sync::mpsc::unbounded_channel();
        let (thi_tx, thi_rx) = tokio::sync::mpsc::unbounded_channel();
        let (act_tx, act_rx) = tokio::sync::mpsc::unbounded_channel();
        let (msg_tx, msg_rx) = std::sync::mpsc::channel();
        (
            tok_tx, tok_rx, thi_tx, thi_rx, act_tx, act_rx, msg_tx, msg_rx,
        )
    }
}
