package parsers

import (
	"encoding/json"
	"fmt"
	"strings"

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

type ministralParserState int

const (
	ministralCollectingContent = iota
	ministralCollectingThinkingContent
	ministralCollectingToolName
	ministralCollectingToolArgs
)

type MinistralParser struct {
	state              ministralParserState
	buffer             strings.Builder
	tools              []api.Tool
	hasThinkingSupport bool
	currentTool        *api.Tool
}

func (p *MinistralParser) HasToolSupport() bool {
	return true
}

func (p *MinistralParser) HasThinkingSupport() bool {
	return p.hasThinkingSupport
}

func (p *MinistralParser) setInitialState(lastMessage *api.Message) {
	prefill := lastMessage != nil && lastMessage.Role == "assistant"
	if !p.HasThinkingSupport() {
		p.state = ministralCollectingContent
		return
	}

	if prefill && lastMessage.Content != "" {
		p.state = ministralCollectingContent
		return
	}

	p.state = ministralCollectingThinkingContent
}

func (p *MinistralParser) Init(tools []api.Tool, lastMessage *api.Message, thinkValue *api.ThinkValue) []api.Tool {
	p.tools = tools
	p.setInitialState(lastMessage)
	return tools
}

func toolByName(tools []api.Tool, n string) (*api.Tool, error) {
	for i := range tools {
		if tools[i].Function.Name == n {
			return &tools[i], nil
		}
	}
	return nil, fmt.Errorf("tool '%s' not found", n)
}

func (p *MinistralParser) Add(s string, done bool) (content string, thinking string, calls []api.ToolCall, err error) {
	p.buffer.WriteString(s)

	switch p.state {
	case ministralCollectingContent:
		if strings.Contains(p.buffer.String(), "[TOOL_CALLS]") {
			before, _ := splitAtTag(&p.buffer, "[TOOL_CALLS]", false)
			if before != "" {
				return before, "", calls, nil
			}
			p.state = ministralCollectingToolName
		} else if strings.Contains(p.buffer.String(), "[THINK]") {
			p.state = ministralCollectingThinkingContent
			return "", "", calls, nil
		} else {
			p.buffer.Reset()
			return s, "", calls, nil
		}
	case ministralCollectingThinkingContent:
		if strings.Contains(p.buffer.String(), "[/THINK]") {
			thinkingContent, after := splitAtTag(&p.buffer, "[/THINK]", true)
			p.state = ministralCollectingContent
			if after != "" {
				p.buffer.Reset()
				return after, thinkingContent, calls, nil
			}
			return "", thinkingContent, calls, nil
		} else {
			p.buffer.Reset()
			return "", s, calls, nil
		}
	case ministralCollectingToolName:
		if strings.Contains(p.buffer.String(), "[ARGS]") {
			name, _ := splitAtTag(&p.buffer, "[ARGS]", false)

			t, err := toolByName(p.tools, name)
			if err != nil {
				return "", "", calls, err
			}
			p.currentTool = t
			p.state = ministralCollectingToolArgs
			return "", "", calls, nil
		}
		return "", "", calls, nil
	case ministralCollectingToolArgs:
		if strings.Contains(p.buffer.String(), "}") {
			before, _ := splitAtTag(&p.buffer, "}", false)
			before += "}"

			var data map[string]any
			if err := json.Unmarshal([]byte(before), &data); err != nil {
				// todo - throw a better error
				return "", "", calls, err
			}

			p.state = ministralCollectingContent

			call := api.ToolCall{
				Function: api.ToolCallFunction{
					Name:      p.currentTool.Function.Name,
					Arguments: api.ToolCallFunctionArguments(data),
				},
			}
			calls = append(calls, call)
			return "", "", calls, nil
		}
		return "", "", calls, nil
	}

	return p.buffer.String(), thinking, calls, nil
}
