package parsers

import (
	"testing"

	"github.com/google/go-cmp/cmp"

	"github.com/ollama/ollama/api"
)

func TestLFM2Parser(t *testing.T) {
	tests := []struct {
		name             string
		input            string
		expectedContent  string
		expectedThinking string
		expectedCalls    []api.ToolCall
		hasThinking      bool
	}{
		{
			name:            "simple_content",
			input:           "Hello, how are you?",
			expectedContent: "Hello, how are you?",
			hasThinking:     false,
		},
		{
			name:             "thinking_content",
			input:            "<think>I need to think about this...</think>The answer is 42.",
			expectedThinking: "I need to think about this...",
			expectedContent:  "The answer is 42.",
			hasThinking:      true,
		},
		{
			name:            "direct_answer_with_thinking_enabled",
			input:           "The answer is 42.",
			expectedContent: "The answer is 42.",
			hasThinking:     true,
		},
		{
			name:            "direct_answer_with_tool_call",
			input:           "I'll check the weather.<|tool_call_start|>[get_weather(location=\"Paris\")]<|tool_call_end|>",
			expectedContent: "I'll check the weather.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "get_weather",
						Arguments: testArgs(map[string]any{
							"location": "Paris",
						}),
					},
				},
			},
			hasThinking: true,
		},
		{
			name:             "thinking_with_newlines",
			input:            "<think>Let me think:\n- Point 1\n- Point 2</think>\n\nHere's my answer.",
			expectedThinking: "Let me think:\n- Point 1\n- Point 2",
			expectedContent:  "Here's my answer.",
			hasThinking:      true,
		},
		{
			name:            "tool_call_simple",
			input:           "I'll check the weather.<|tool_call_start|>[get_weather(location=\"Paris\")]<|tool_call_end|>",
			expectedContent: "I'll check the weather.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "get_weather",
						Arguments: testArgs(map[string]any{
							"location": "Paris",
						}),
					},
				},
			},
			hasThinking: false,
		},
		{
			name:            "multiple_tool_calls",
			input:           "Getting weather for both cities.<|tool_call_start|>[get_weather(location=\"Paris\")]<|tool_call_end|><|tool_call_start|>[get_weather(location=\"London\")]<|tool_call_end|>",
			expectedContent: "Getting weather for both cities.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Index: 0,
						Name:  "get_weather",
						Arguments: testArgs(map[string]any{
							"location": "Paris",
						}),
					},
				},
				{
					Function: api.ToolCallFunction{
						Index: 1,
						Name:  "get_weather",
						Arguments: testArgs(map[string]any{
							"location": "London",
						}),
					},
				},
			},
			hasThinking: false,
		},
		{
			name:            "complex_tool_arguments",
			input:           "Processing data.<|tool_call_start|>[process_data(items=['item1','item2'], config={'enabled': True, 'threshold': 0.95})]<|tool_call_end|>",
			expectedContent: "Processing data.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "process_data",
						Arguments: testArgs(map[string]any{
							"items":  []interface{}{"item1", "item2"},
							"config": map[string]interface{}{"enabled": true, "threshold": 0.95},
						}),
					},
				},
			},
			hasThinking: false,
		},
		{
			name:             "thinking_with_tool_call",
			input:            "<think>Let me check the weather...</think>I'll get that for you.<|tool_call_start|>[get_weather(location=\"Paris\")]<|tool_call_end|>",
			expectedThinking: "Let me check the weather...",
			expectedContent:  "I'll get that for you.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "get_weather",
						Arguments: testArgs(map[string]any{
							"location": "Paris",
						}),
					},
				},
			},
			hasThinking: true,
		},
		{
			name:            "empty_content",
			input:           "",
			expectedContent: "",
			hasThinking:     false,
		},
		{
			name:             "only_thinking",
			input:            "<think>Just thinking content</think>",
			expectedThinking: "Just thinking content",
			expectedContent:  "",
			hasThinking:      true,
		},
		{
			name:            "unicode_content",
			input:           "مرحبا بالعالم! 你好世界! 🌍",
			expectedContent: "مرحبا بالعالم! 你好世界! 🌍",
			hasThinking:     false,
		},
		{
			name:            "newlines_and_whitespace",
			input:           "Line 1\n\nLine 3\t\tTabbed content",
			expectedContent: "Line 1\n\nLine 3\t\tTabbed content",
			hasThinking:     false,
		},
		{
			name:             "thinking_with_unicode",
			input:            "<think>我在思考这个问题...</think>答案是42。",
			expectedThinking: "我在思考这个问题...",
			expectedContent:  "答案是42。",
			hasThinking:      true,
		},
		{
			name:            "tool_call_with_text_args",
			input:           "Searching for information.<|tool_call_start|>[search(query='beijing weather', language='zh')]<|tool_call_end|>",
			expectedContent: "Searching for information.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "search",
						Arguments: testArgs(map[string]any{
							"query":    "beijing weather",
							"language": "zh",
						}),
					},
				},
			},
			hasThinking: false,
		},
		{
			name:             "thinking_with_special_chars",
			input:            "<think>Let me calculate: 2+2=4 & 3*3=9...</think>The results are correct!",
			expectedThinking: "Let me calculate: 2+2=4 & 3*3=9...",
			expectedContent:  "The results are correct!",
			hasThinking:      true,
		},
		{
			name:            "empty_tool_call_args",
			input:           "Pinging server.<|tool_call_start|>[ping()]<|tool_call_end|>",
			expectedContent: "Pinging server.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name:      "ping",
						Arguments: api.NewToolCallFunctionArguments(),
					},
				},
			},
			hasThinking: false,
		},
		// Python-style tool call tests (from Liquid AI docs)
		{
			name:            "python_style_tool_call",
			input:           "Let me check that.<|tool_call_start|>[get_candidate_status(candidate_id=\"12345\")]<|tool_call_end|>",
			expectedContent: "Let me check that.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "get_candidate_status",
						Arguments: testArgs(map[string]any{
							"candidate_id": "12345",
						}),
					},
				},
			},
			hasThinking: false,
		},
		{
			name:            "python_style_multiple_calls",
			input:           "Running commands.<|tool_call_start|>[bash(command='ls'),bash(command='pwd')]<|tool_call_end|>",
			expectedContent: "Running commands.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Index: 0,
						Name:  "bash",
						Arguments: testArgs(map[string]any{
							"command": "ls",
						}),
					},
				},
				{
					Function: api.ToolCallFunction{
						Index: 1,
						Name:  "bash",
						Arguments: testArgs(map[string]any{
							"command": "pwd",
						}),
					},
				},
			},
			hasThinking: false,
		},
		{
			name:             "thinking_then_python_tool_call",
			input:            "<think>I should check the status...</think>Let me look that up.<|tool_call_start|>[get_status(id=\"123\")]<|tool_call_end|>",
			expectedThinking: "I should check the status...",
			expectedContent:  "Let me look that up.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "get_status",
						Arguments: testArgs(map[string]any{
							"id": "123",
						}),
					},
				},
			},
			hasThinking: true,
		},
		{
			name:            "python_style_no_args",
			input:           "Pinging.<|tool_call_start|>[ping()]<|tool_call_end|>",
			expectedContent: "Pinging.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name:      "ping",
						Arguments: api.NewToolCallFunctionArguments(),
					},
				},
			},
			hasThinking: false,
		},
		{
			name:            "python_style_mixed_types",
			input:           "Processing.<|tool_call_start|>[process(name=\"test\", count=42, enabled=true)]<|tool_call_end|>",
			expectedContent: "Processing.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "process",
						Arguments: testArgs(map[string]any{
							"name":    "test",
							"count":   int64(42),
							"enabled": true,
						}),
					},
				},
			},
			hasThinking: false,
		},
		{
			name:            "tool_call_only_no_content",
			input:           "<|tool_call_start|>[check()]<|tool_call_end|>",
			expectedContent: "",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name:      "check",
						Arguments: api.NewToolCallFunctionArguments(),
					},
				},
			},
			hasThinking: false,
		},
		{
			name:             "thinking_directly_to_tool_call",
			input:            "<think>Let me run this command...</think><|tool_call_start|>[bash(command='ls')]<|tool_call_end|>",
			expectedThinking: "Let me run this command...",
			expectedContent:  "",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": "ls",
						}),
					},
				},
			},
			hasThinking: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &LFM2Parser{hasThinkingSupport: tt.hasThinking}
			parser.Init([]api.Tool{}, nil, &api.ThinkValue{Value: tt.hasThinking})

			content, thinking, calls, err := parser.Add(tt.input, true)
			if err != nil {
				t.Fatalf("Add() error = %v", err)
			}

			if diff := cmp.Diff(tt.expectedContent, content); diff != "" {
				t.Errorf("Content mismatch (-want +got):\n%s", diff)
			}

			if diff := cmp.Diff(tt.expectedThinking, thinking); diff != "" {
				t.Errorf("Thinking mismatch (-want +got):\n%s", diff)
			}

			if diff := cmp.Diff(tt.expectedCalls, calls, argsComparer); diff != "" {
				t.Errorf("Tool calls mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

func TestLFM2Parser_Streaming(t *testing.T) {
	tests := []struct {
		name             string
		chunks           []string
		expectedContent  string
		expectedThinking string
		expectedCalls    []api.ToolCall
		hasThinking      bool
	}{
		{
			name:            "streaming_simple_content",
			chunks:          []string{"Hello, ", "how are ", "you?"},
			expectedContent: "Hello, how are you?",
			hasThinking:     false,
		},
		{
			name:             "streaming_thinking",
			chunks:           []string{"<think>", "I need to ", "think about this", "...</think>", "The answer is 42."},
			expectedThinking: "I need to think about this...",
			expectedContent:  "The answer is 42.",
			hasThinking:      true,
		},
		{
			name:            "streaming_direct_answer",
			chunks:          []string{"The answer ", "is ", "42."},
			expectedContent: "The answer is 42.",
			hasThinking:     true,
		},
		{
			name:            "streaming_tool_call",
			chunks:          []string{"I'll check weather.", "<|tool_call_start|>", "[get_weather(", "location=\"Paris\")]", "<|tool_call_end|>"},
			expectedContent: "I'll check weather.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "get_weather",
						Arguments: testArgs(map[string]any{
							"location": "Paris",
						}),
					},
				},
			},
			hasThinking: false,
		},
		{
			name:             "streaming_thinking_with_partial_tag",
			chunks:           []string{"<think>", "Thinking about this", "...</", "think>", "Done thinking."},
			expectedThinking: "Thinking about this...",
			expectedContent:  "Done thinking.",
			hasThinking:      true,
		},
		{
			name:            "streaming_unicode_content",
			chunks:          []string{"مرحبا ", "بالعالم! ", "你好", "世界!"},
			expectedContent: "مرحبا بالعالم! 你好世界!",
			hasThinking:     false,
		},
		{
			name:            "streaming_tool_call_with_split_python",
			chunks:          []string{"Processing.", "<|tool_call_start|>", "[calc(", "x=42, ", "y=24)]", "<|tool_call_end|>"},
			expectedContent: "Processing.",
			expectedCalls: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "calc",
						Arguments: testArgs(map[string]any{
							"x": int64(42),
							"y": int64(24),
						}),
					},
				},
			},
			hasThinking: false,
		},
		{
			// Opening <think> tag split across chunks must still be recognized.
			name:             "streaming_thinking_split_open_tag",
			chunks:           []string{"<th", "ink>", "reasoning", "</think>", "answer"},
			expectedThinking: "reasoning",
			expectedContent:  "answer",
			hasThinking:      true,
		},
		{
			// A direct answer starting with '<' is ambiguous until enough arrives
			// to rule out an opening <think> tag.
			name:            "streaming_direct_answer_starting_with_angle",
			chunks:          []string{"<", "html> is a tag"},
			expectedContent: "<html> is a tag",
			hasThinking:     true,
		},
		{
			// Test that leading whitespace after <think> is trimmed even when in separate chunks
			name:             "streaming_thinking_whitespace_after_tag",
			chunks:           []string{"<think>", "\n\n  ", "Actual thinking content", "</think>", "Response"},
			expectedThinking: "Actual thinking content",
			expectedContent:  "Response",
			hasThinking:      true,
		},
		{
			// Test whitespace between </think> and content in streaming
			name:             "streaming_whitespace_after_close_tag",
			chunks:           []string{"<think>Thinking</think>", "\n\n\n", "Response content"},
			expectedThinking: "Thinking",
			expectedContent:  "Response content",
			hasThinking:      true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &LFM2Parser{hasThinkingSupport: tt.hasThinking}
			parser.Init([]api.Tool{}, nil, &api.ThinkValue{Value: tt.hasThinking})

			var allContent, allThinking string
			var allCalls []api.ToolCall

			for i, chunk := range tt.chunks {
				done := i == len(tt.chunks)-1
				content, thinking, calls, err := parser.Add(chunk, done)
				if err != nil {
					t.Fatalf("Add() error = %v", err)
				}

				allContent += content
				allThinking += thinking
				allCalls = append(allCalls, calls...)
			}

			if diff := cmp.Diff(tt.expectedContent, allContent); diff != "" {
				t.Errorf("Content mismatch (-want +got):\n%s", diff)
			}

			if diff := cmp.Diff(tt.expectedThinking, allThinking); diff != "" {
				t.Errorf("Thinking mismatch (-want +got):\n%s", diff)
			}

			if diff := cmp.Diff(tt.expectedCalls, allCalls, argsComparer); diff != "" {
				t.Errorf("Tool calls mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

func TestLFM2Parser_HasThinkingSupport(t *testing.T) {
	tests := []struct {
		name            string
		hasThinking     bool
		expectedSupport bool
	}{
		{
			name:            "thinking_enabled",
			hasThinking:     true,
			expectedSupport: true,
		},
		{
			name:            "thinking_disabled",
			hasThinking:     false,
			expectedSupport: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &LFM2Parser{hasThinkingSupport: tt.hasThinking}
			if got := parser.HasThinkingSupport(); got != tt.expectedSupport {
				t.Errorf("HasThinkingSupport() = %v, want %v", got, tt.expectedSupport)
			}
		})
	}
}

func TestLFM2Parser_HasToolSupport(t *testing.T) {
	parser := &LFM2Parser{}
	if !parser.HasToolSupport() {
		t.Error("HasToolSupport() should return true")
	}
}

func TestLFM2Parser_Init(t *testing.T) {
	parser := &LFM2Parser{hasThinkingSupport: true}
	tools := []api.Tool{
		{
			Type: "function",
			Function: api.ToolFunction{
				Name: "test_tool",
			},
		},
	}

	returnedTools := parser.Init(tools, nil, &api.ThinkValue{Value: true})

	if diff := cmp.Diff(tools, returnedTools, toolsComparer); diff != "" {
		t.Errorf("Init() returned tools mismatch (-want +got):\n%s", diff)
	}

	// Test initial state looks for a leading <think> tag when thinking is enabled
	if parser.state != LFM2LookingForThinking {
		t.Errorf("Expected initial state to be LFM2LookingForThinking, got %v", parser.state)
	}
}

func TestLFM2Parser_parseToolCallContent(t *testing.T) {
	tests := []struct {
		name        string
		content     string
		expected    api.ToolCall
		expectError bool
	}{
		{
			name:    "python_style_single_call",
			content: `get_weather(location="Paris")`,
			expected: api.ToolCall{
				Function: api.ToolCallFunction{
					Name: "get_weather",
					Arguments: testArgs(map[string]any{
						"location": "Paris",
					}),
				},
			},
		},
		{
			name:    "python_style_with_brackets",
			content: `[get_weather(location="Paris")]`,
			expected: api.ToolCall{
				Function: api.ToolCallFunction{
					Name: "get_weather",
					Arguments: testArgs(map[string]any{
						"location": "Paris",
					}),
				},
			},
		},
		{
			name:    "python_style_complex_arguments",
			content: `process(items=['a', 'b'], config={'enabled': True})`,
			expected: api.ToolCall{
				Function: api.ToolCallFunction{
					Name: "process",
					Arguments: testArgs(map[string]any{
						"items":  []any{"a", "b"},
						"config": map[string]any{"enabled": true},
					}),
				},
			},
		},
		{
			name:    "python_style_empty_arguments",
			content: `ping()`,
			expected: api.ToolCall{
				Function: api.ToolCallFunction{
					Name:      "ping",
					Arguments: api.NewToolCallFunctionArguments(),
				},
			},
		},
		{
			name:        "missing_parenthesis",
			content:     `get_weather location="Paris")`,
			expectError: true,
		},
		{
			name:        "invalid_argument_format",
			content:     `bash(command)`,
			expectError: true,
		},
	}

	parser := &LFM2Parser{}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := parser.parseToolCallContent(tt.content)

			if tt.expectError {
				if err == nil {
					t.Error("Expected error but got none")
				}
				return
			}

			if err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}

			if diff := cmp.Diff(tt.expected, result, argsComparer); diff != "" {
				t.Errorf("parseToolCallContent() mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

func TestLFM2Parser_parseToolCallsContent(t *testing.T) {
	tests := []struct {
		name        string
		content     string
		expected    []api.ToolCall
		expectError bool
	}{
		{
			name:    "multiple_python_style_calls",
			content: `[bash(command='curl google.com'),bash(command='curl example.com')]`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": "curl google.com",
						}),
					},
				},
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": "curl example.com",
						}),
					},
				},
			},
		},
		{
			name:    "python_style_complex_literals",
			content: `[AskUserQuestion(question="What's up?", headers=['Hello!', 'How can I help you?'], options=['Debugging help', 'Code writing assistance'], multiSelect=False, metadata={'priority': 1, 'active': True})]`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "AskUserQuestion",
						Arguments: testArgs(map[string]any{
							"question":    "What's up?",
							"headers":     []any{"Hello!", "How can I help you?"},
							"options":     []any{"Debugging help", "Code writing assistance"},
							"multiSelect": false,
							"metadata":    map[string]any{"priority": float64(1), "active": true},
						}),
					},
				},
			},
		},
		{
			name:    "single_python_style_call",
			content: `bash(command='ls -la')`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": "ls -la",
						}),
					},
				},
			},
		},
		{
			name:    "single_bracketed_call",
			content: `[bash(command='pwd')]`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": "pwd",
						}),
					},
				},
			},
		},
		{
			name:    "single_call_with_orphan_end_tag",
			content: `[bash(command='ls')]<|tool_call_end|>`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": "ls",
						}),
					},
				},
			},
		},
		{
			name:    "single_call_with_wrapper_tags",
			content: `<|tool_call_start|>[bash(command='pwd')]<|tool_call_end|>`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": "pwd",
						}),
					},
				},
			},
		},
		{
			name:    "multiple_different_functions",
			content: `[get_weather(location='Paris'),search(query='news')]`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "get_weather",
						Arguments: testArgs(map[string]any{
							"location": "Paris",
						}),
					},
				},
				{
					Function: api.ToolCallFunction{
						Name: "search",
						Arguments: testArgs(map[string]any{
							"query": "news",
						}),
					},
				},
			},
		},
		{
			name:    "nested_parentheses_in_arg",
			content: `bash(command='echo "(hello)"')`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": `echo "(hello)"`,
						}),
					},
				},
			},
		},
		{
			name:    "comma_inside_quotes",
			content: `bash(command='echo "hello, world"')`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": `echo "hello, world"`,
						}),
					},
				},
			},
		},
		{
			name:    "equals_inside_quotes",
			content: `bash(command='export FOO=bar')`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": `export FOO=bar`,
						}),
					},
				},
			},
		},
		{
			name:    "double_quotes_with_single_inside",
			content: `bash(command="echo 'hello'")`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": `echo 'hello'`,
						}),
					},
				},
			},
		},
		{
			name:    "multiple_args",
			content: `bash(command='ls', flag='-la', count=42)`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": "ls",
							"flag":    "-la",
							"count":   int64(42),
						}),
					},
				},
			},
		},
		{
			name:    "no_args",
			content: `ping()`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name:      "ping",
						Arguments: api.NewToolCallFunctionArguments(),
					},
				},
			},
		},
		{
			name:    "three_calls",
			content: `[a(x='1'),b(y='2'),c(z='3')]`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name:      "a",
						Arguments: testArgs(map[string]any{"x": "1"}),
					},
				},
				{
					Function: api.ToolCallFunction{
						Name:      "b",
						Arguments: testArgs(map[string]any{"y": "2"}),
					},
				},
				{
					Function: api.ToolCallFunction{
						Name:      "c",
						Arguments: testArgs(map[string]any{"z": "3"}),
					},
				},
			},
		},
		{
			// Note: backslash escapes are preserved as-is, not processed
			name:    "escaped_quote_in_value",
			content: `bash(command='echo \'hello\'')`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "bash",
						Arguments: testArgs(map[string]any{
							"command": `echo \'hello\'`,
						}),
					},
				},
			},
		},
		// Tests based on Liquid AI documentation examples
		{
			name:    "docs_example_candidate_status",
			content: `[get_candidate_status(candidate_id="12345")]`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "get_candidate_status",
						Arguments: testArgs(map[string]any{
							"candidate_id": "12345",
						}),
					},
				},
			},
		},
		{
			name:    "boolean_true_arg",
			content: `configure(enabled=true)`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "configure",
						Arguments: testArgs(map[string]any{
							"enabled": true,
						}),
					},
				},
			},
		},
		{
			name:    "boolean_false_arg",
			content: `configure(enabled=false)`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "configure",
						Arguments: testArgs(map[string]any{
							"enabled": false,
						}),
					},
				},
			},
		},
		{
			name:    "float_arg",
			content: `set_threshold(value=0.95)`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "set_threshold",
						Arguments: testArgs(map[string]any{
							"value": 0.95,
						}),
					},
				},
			},
		},
		{
			name:    "negative_number_arg",
			content: `adjust(offset=-10)`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "adjust",
						Arguments: testArgs(map[string]any{
							"offset": int64(-10),
						}),
					},
				},
			},
		},
		{
			name:    "mixed_arg_types",
			content: `process(name="test", count=42, ratio=3.14, active=true)`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "process",
						Arguments: testArgs(map[string]any{
							"name":   "test",
							"count":  int64(42),
							"ratio":  3.14,
							"active": true,
						}),
					},
				},
			},
		},
		{
			name:    "newline_in_string_arg",
			content: `write_file(content="line1\nline2\nline3")`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "write_file",
						Arguments: testArgs(map[string]any{
							"content": "line1\\nline2\\nline3",
						}),
					},
				},
			},
		},
		{
			name:    "empty_string_arg",
			content: `search(query="")`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "search",
						Arguments: testArgs(map[string]any{
							"query": "",
						}),
					},
				},
			},
		},
		{
			name:    "underscore_function_name",
			content: `get_user_profile(user_id="abc123")`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "get_user_profile",
						Arguments: testArgs(map[string]any{
							"user_id": "abc123",
						}),
					},
				},
			},
		},
		{
			name:    "whitespace_around_args",
			content: `func( arg1 = "value1" , arg2 = 42 )`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "func",
						Arguments: testArgs(map[string]any{
							"arg1": "value1",
							"arg2": int64(42),
						}),
					},
				},
			},
		},
		{
			name:    "json_in_string_arg",
			content: `send_data(payload='{"key": "value", "num": 123}')`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "send_data",
						Arguments: testArgs(map[string]any{
							"payload": `{"key": "value", "num": 123}`,
						}),
					},
				},
			},
		},
		{
			name:    "url_in_arg",
			content: `fetch(url="https://example.com/api?foo=bar&baz=qux")`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "fetch",
						Arguments: testArgs(map[string]any{
							"url": "https://example.com/api?foo=bar&baz=qux",
						}),
					},
				},
			},
		},
		{
			name:    "path_with_spaces",
			content: `read_file(path="/home/user/My Documents/file.txt")`,
			expected: []api.ToolCall{
				{
					Function: api.ToolCallFunction{
						Name: "read_file",
						Arguments: testArgs(map[string]any{
							"path": "/home/user/My Documents/file.txt",
						}),
					},
				},
			},
		},
	}

	parser := &LFM2Parser{}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := parser.parseToolCallsContent(tt.content)

			if tt.expectError {
				if err == nil {
					t.Error("Expected error but got none")
				}
				return
			}

			if err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}

			if diff := cmp.Diff(tt.expected, result, argsComparer); diff != "" {
				t.Errorf("parseToolCallsContent() mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

func TestLFM2Parser_EdgeCases(t *testing.T) {
	tests := []struct {
		name             string
		input            string
		expectedContent  string
		expectedThinking string
		hasThinking      bool
	}{
		{
			name:             "multiple_think_close_tags",
			input:            "<think>First thought</think>Second thought</think>Final content",
			expectedThinking: "First thought",
			expectedContent:  "Second thought</think>Final content",
			hasThinking:      true,
		},
		{
			name:             "empty_thinking_block",
			input:            "<think></think>Just content",
			expectedThinking: "",
			expectedContent:  "Just content",
			hasThinking:      true,
		},
		{
			name:            "direct_answer_with_leading_whitespace",
			input:           "  \n  Hello there",
			expectedContent: "Hello there",
			hasThinking:     true,
		},
		{
			name:            "thinking_disabled_with_think_tags",
			input:           "Some content</think>More content",
			expectedContent: "Some content</think>More content",
			hasThinking:     false,
		},
		{
			name:            "whitespace_only_content",
			input:           "   \n\t   ",
			expectedContent: "   \n\t   ",
			hasThinking:     false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &LFM2Parser{hasThinkingSupport: tt.hasThinking}
			parser.Init([]api.Tool{}, nil, &api.ThinkValue{Value: tt.hasThinking})

			content, thinking, _, err := parser.Add(tt.input, true)
			if err != nil {
				t.Fatalf("Add() error = %v", err)
			}

			if diff := cmp.Diff(tt.expectedContent, content); diff != "" {
				t.Errorf("Content mismatch (-want +got):\n%s", diff)
			}

			if diff := cmp.Diff(tt.expectedThinking, thinking); diff != "" {
				t.Errorf("Thinking mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

func TestLFM2Parser_BareToolCallFallback(t *testing.T) {
	parser := &LFM2Parser{}
	tools := []api.Tool{
		{
			Type: "function",
			Function: api.ToolFunction{
				Name: "get_weather",
			},
		},
	}
	parser.Init(tools, nil, &api.ThinkValue{Value: false})

	content, thinking, calls, err := parser.Add(`[get_weather(location="Paris")]`, true)
	if err != nil {
		t.Fatalf("Add() error = %v", err)
	}

	if content != "" {
		t.Fatalf("expected empty content, got %q", content)
	}
	if thinking != "" {
		t.Fatalf("expected empty thinking, got %q", thinking)
	}
	if len(calls) != 1 {
		t.Fatalf("expected 1 tool call, got %d", len(calls))
	}
	if calls[0].Function.Name != "get_weather" {
		t.Fatalf("expected tool name get_weather, got %q", calls[0].Function.Name)
	}
}

func TestLFM2Parser_BareUnknownToolCallDoesNotParse(t *testing.T) {
	parser := &LFM2Parser{}
	tools := []api.Tool{
		{
			Type: "function",
			Function: api.ToolFunction{
				Name: "get_weather",
			},
		},
	}
	parser.Init(tools, nil, &api.ThinkValue{Value: false})

	input := `[unknown_tool(location="Paris")]`
	content, _, calls, err := parser.Add(input, true)
	if err != nil {
		t.Fatalf("Add() error = %v", err)
	}

	if content != input {
		t.Fatalf("expected content to be preserved, got %q", content)
	}
	if len(calls) != 0 {
		t.Fatalf("expected no tool calls, got %d", len(calls))
	}
}

func TestLFM2Parser_ImagePlaceholdersPreserved(t *testing.T) {
	tests := []struct {
		name  string
		input string
	}{
		{
			name:  "indexed_img_placeholder",
			input: "[img-0]describe this image",
		},
		{
			name:  "template_image_placeholder",
			input: "<image>describe this image",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &LFM2Parser{}
			tools := []api.Tool{
				{
					Type: "function",
					Function: api.ToolFunction{
						Name: "bash",
					},
				},
			}
			parser.Init(tools, nil, &api.ThinkValue{Value: false})

			content, thinking, calls, err := parser.Add(tt.input, true)
			if err != nil {
				t.Fatalf("Add() error = %v", err)
			}

			if content != tt.input {
				t.Fatalf("expected content %q, got %q", tt.input, content)
			}
			if thinking != "" {
				t.Fatalf("expected empty thinking, got %q", thinking)
			}
			if len(calls) != 0 {
				t.Fatalf("expected no tool calls, got %d", len(calls))
			}
		})
	}
}
