package openai

import (
	"fmt"
	"strings"
)

func scanXMLTextToolCallBlocks(content string) ([]xmlTextToolCallBlock, bool) {
	if !strings.Contains(strings.ToLower(content), textToolCallOpeningHint) {
		return nil, false
	}
	foundAny := false
	var blocks []xmlTextToolCallBlock
	for pos := 0; pos < len(content); {
		openStart, openEnd, ok := findXMLOpenTag(content, pos, len(content), "tool_call")
		if !ok {
			break
		}
		foundAny = true
		closeStart, closeEnd, ok := findXMLCloseTag(content, openEnd, len(content), "tool_call")
		if !ok {
			blocks = append(blocks, xmlTextToolCallBlock{
				start: openStart,
				end:   len(content),
				err:   fmt.Errorf("malformed tool call: unclosed tool_call block"),
			})
			break
		}
		block := parseXMLTextToolCallBlock(content, openStart, openEnd, closeStart, closeEnd)
		blocks = append(blocks, block)
		pos = closeEnd
	}
	return blocks, foundAny
}

func parseXMLTextToolCallBlock(content string, blockStart, bodyStart, bodyEnd, blockEnd int) xmlTextToolCallBlock {
	_, fnOpenEnd, name, ok := findXMLNamedOpenTag(content, bodyStart, bodyEnd, "function")
	if !ok {
		return xmlTextToolCallBlock{
			start: blockStart,
			end:   blockEnd,
			err:   fmt.Errorf("malformed tool call: missing function block or definition inside tool_call"),
		}
	}
	fnCloseStart, _, ok := findXMLCloseTag(content, fnOpenEnd, bodyEnd, "function")
	if !ok {
		return xmlTextToolCallBlock{
			start: blockStart,
			end:   blockEnd,
			err:   fmt.Errorf("malformed tool call: missing function block or definition inside tool_call"),
		}
	}
	params := map[string]string{}
	for pos := fnOpenEnd; pos < fnCloseStart; {
		paramStart, paramOpenEnd, key, ok := findXMLNamedOpenTag(content, pos, fnCloseStart, "parameter")
		if !ok {
			break
		}
		paramCloseStart, paramCloseEnd, ok := findXMLCloseTag(content, paramOpenEnd, fnCloseStart, "parameter")
		if !ok {
			break
		}
		key = strings.TrimSpace(key)
		if key != "" {
			params[key] = content[paramOpenEnd:paramCloseStart]
		}
		pos = paramCloseEnd
		if pos <= paramStart {
			break
		}
	}
	return xmlTextToolCallBlock{
		start:  blockStart,
		end:    blockEnd,
		name:   strings.TrimSpace(name),
		params: params,
		err:    nil,
	}
}

func findXMLOpenTag(content string, start, limit int, tag string) (int, int, bool) {
	for pos := start; pos < limit; {
		idx := strings.IndexByte(content[pos:limit], '<')
		if idx < 0 {
			return 0, 0, false
		}
		tagStart := pos + idx
		next := skipASCIISpaces(content, tagStart+1)
		if next < limit && content[next] == '/' {
			pos = tagStart + 1
			continue
		}
		if !matchASCIIFoldAt(content, next, tag) {
			pos = tagStart + 1
			continue
		}
		afterTag := next + len(tag)
		if afterTag < limit && isTextToolIdentifierChar(content[afterTag], false) {
			pos = tagStart + 1
			continue
		}
		closeIdx := strings.IndexByte(content[afterTag:limit], '>')
		if closeIdx < 0 {
			return 0, 0, false
		}
		return tagStart, afterTag + closeIdx + 1, true
	}
	return 0, 0, false
}

func findXMLNamedOpenTag(content string, start, limit int, tag string) (int, int, string, bool) {
	for pos := start; pos < limit; {
		idx := strings.IndexByte(content[pos:limit], '<')
		if idx < 0 {
			return 0, 0, "", false
		}
		tagStart := pos + idx
		next := skipASCIISpaces(content, tagStart+1)
		if next < limit && content[next] == '/' {
			pos = tagStart + 1
			continue
		}
		if !matchASCIIFoldAt(content, next, tag) {
			pos = tagStart + 1
			continue
		}
		cursor := skipASCIISpaces(content, next+len(tag))
		if cursor >= limit || content[cursor] != '=' {
			pos = tagStart + 1
			continue
		}
		cursor = skipASCIISpaces(content, cursor+1)
		nameStart := cursor
		for cursor < limit && isTextToolIdentifierChar(content[cursor], cursor == nameStart) {
			cursor++
		}
		if cursor == nameStart {
			pos = tagStart + 1
			continue
		}
		name := content[nameStart:cursor]
		cursor = skipASCIISpaces(content, cursor)
		if cursor >= limit || content[cursor] != '>' {
			pos = tagStart + 1
			continue
		}
		return tagStart, cursor + 1, name, true
	}
	return 0, 0, "", false
}

func findXMLCloseTag(content string, start, limit int, tag string) (int, int, bool) {
	for pos := start; pos < limit; {
		idx := strings.Index(content[pos:limit], "</")
		if idx < 0 {
			return 0, 0, false
		}
		tagStart := pos + idx
		cursor := skipASCIISpaces(content, tagStart+2)
		if !matchASCIIFoldAt(content, cursor, tag) {
			pos = tagStart + 2
			continue
		}
		cursor = skipASCIISpaces(content, cursor+len(tag))
		if cursor < limit && content[cursor] == '=' {
			cursor = skipASCIISpaces(content, cursor+1)
			nameStart := cursor
			for cursor < limit && isTextToolIdentifierChar(content[cursor], cursor == nameStart) {
				cursor++
			}
			if cursor == nameStart {
				pos = tagStart + 2
				continue
			}
			cursor = skipASCIISpaces(content, cursor)
		}
		if cursor >= limit || content[cursor] != '>' {
			pos = tagStart + 2
			continue
		}
		return tagStart, cursor + 1, true
	}
	return 0, 0, false
}

func matchASCIIFoldAt(s string, pos int, want string) bool {
	if pos < 0 || pos+len(want) > len(s) {
		return false
	}
	for i := 0; i < len(want); i++ {
		a := s[pos+i]
		b := want[i]
		if a >= 'A' && a <= 'Z' {
			a += 'a' - 'A'
		}
		if b >= 'A' && b <= 'Z' {
			b += 'a' - 'A'
		}
		if a != b {
			return false
		}
	}
	return true
}

func findMustacheToolCallEnd(content string, argsStart int) (int, int, bool) {
	depth := 0
	var quote byte
	escaped := false
	for i := argsStart; i < len(content); i++ {
		c := content[i]
		if quote != 0 {
			if escaped {
				escaped = false
				continue
			}
			if c == '\\' {
				escaped = true
				continue
			}
			if c == quote {
				quote = 0
			}
			continue
		}
		switch c {
		case '\'', '"':
			quote = c
		case '[', '{', '(':
			depth++
		case ']', '}':
			if depth > 0 {
				depth--
			}
		case ')':
			if depth > 0 {
				depth--
				continue
			}
			closeStart := skipASCIISpaces(content, i+1)
			if closeStart+1 < len(content) && content[closeStart] == '}' && content[closeStart+1] == '}' {
				return i, closeStart + 2, true
			}
		}
	}
	return 0, 0, false
}
