package orchestrator

import (
	"regexp"
	"strings"

	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/model"
)

// codeBlockPattern matches fenced code blocks: ```lang\n...\n```
var codeBlockPattern = regexp.MustCompile("`{3}([^\n]*)\n([\\s\\S]*?)`{3}")

// actionTagPattern matches <action>...</action> XML tags (supports multiline content).
var actionTagPattern = regexp.MustCompile(`(?s)<action>(.*?)</action>`)

// markdownPatterns detects common Markdown syntax for auto-classification.
var markdownPatterns = []*regexp.Regexp{
	regexp.MustCompile(`^#{1,6}\s`),                // headings
	regexp.MustCompile(`\*\*[^*]+\*\*`),             // bold
	regexp.MustCompile(`(?:^|[^*])\*([^*]+)\*(?:[^*]|$)`), // italic (*text*)
	regexp.MustCompile(`\[([^\]]+)\]\(([^\)]+)\)`),   // links [text](url)
	regexp.MustCompile(`^[\-\*]\s`),                 // unordered list
	regexp.MustCompile(`^\d+\.\s`),                  // ordered list
	regexp.MustCompile(`^>\s`),                      // blockquote
	regexp.MustCompile(`^\|.*\|.*\|`),               // table
	regexp.MustCompile("`[^`]+`"),                   // inline code
}

// hasMarkdownSyntax reports whether text contains Markdown formatting.
func hasMarkdownSyntax(text string) bool {
	for _, p := range markdownPatterns {
		if p.MatchString(text) {
			return true
		}
	}
	return false
}

// autoDetectType returns the best message type for a text segment.
func autoDetectType(text string) model.ReviewMessageType {
	if hasMarkdownSyntax(text) {
		return model.ReviewMessageMarkdown
	}
	if isActionLike(text) {
		return model.ReviewMessageAction
	}
	return model.ReviewMessageChat
}

// isActionLike checks whether text looks like an action/expression description
// (e.g. "忍不住轻声笑出来", "俏皮地眨眨眼") rather than dialogue. Used as a
// fallback when the model doesn't use <action> tags or brackets.
func isActionLike(text string) bool {
	runes := []rune(strings.TrimSpace(text))
	if len(runes) == 0 || len(runes) > 50 {
		return false
	}
	// Dialogue markers disqualify action
	if strings.ContainsAny(text, "？?！!") {
		return false
	}
	// Common dialogue starters
	dialoguePrefixes := []string{"你", "您", "我", "他", "她", "这", "那", "怎么", "什么", "为什"}
	for _, p := range dialoguePrefixes {
		if strings.HasPrefix(string(runes), p) {
			return false
		}
	}
	// Soft dialogue indicators: text that looks like addressing someone
	softDialogue := []string{"吗", "吧", "哦", "呢", "啦", "呀", "喔"}
	dialogueScore := 0
	for _, s := range softDialogue {
		if strings.HasSuffix(string(runes), s) {
			dialogueScore++
		}
	}
	if dialogueScore >= 1 && strings.Contains(text, "你") {
		return false
	}
	// Action-indicating patterns
	actionPatterns := []string{
		"笑出来", "眨眨眼", "歪头", "点头", "摇头", "挥手", "伸手",
		"松口气", "叹口气", "叹气", "拍拍", "摸摸", "抱抱",
		"轻轻", "俏皮", "微微", "默默", "悄悄", "偷偷",
		"忍不住", "不由得", "不禁",
		"站起来", "坐下", "走", "跑", "跳", "躺",
		"眼睛", "目光", "嘴角", "眉头", "脸上",
	}
	for _, p := range actionPatterns {
		if strings.Contains(text, p) {
			return true
		}
	}
	return false
}

// parseReviewMessages splits the assistant's full response into typed messages.
//
// Phases:
//  1. Extract fenced code blocks (```) → code type with language metadata.
//  2. For text between code blocks, run the bracket-action parser:
//     （…） / (…) → action type.
//  3. Remaining text is auto-detected as markdown or chat.
//  4. Markdown and code messages are never sentence-split (keeps formatting intact).
func parseReviewMessages(text string) []model.ReviewMessage {
	if text == "" {
		return nil
	}

	var messages []model.ReviewMessage

	// Phase 1: extract code blocks
	codeMatches := codeBlockPattern.FindAllStringSubmatchIndex(text, -1)
	type codeBlock struct {
		start, end int
		language   string
		content    string
	}
	var blocks []codeBlock
	for _, m := range codeMatches {
		blocks = append(blocks, codeBlock{
			start:    m[0],
			end:      m[1],
			language: strings.TrimSpace(text[m[2]:m[3]]),
			content:  strings.TrimSpace(text[m[4]:m[5]]),
		})
	}

	// Phase 2: XML action tags + bracket-based fallback
	var processBracketText func(t string) // pre-declare for mutual reference

	processText := func(t string) {
		// Step 1: extract <action> XML tags
		actionMatches := actionTagPattern.FindAllStringSubmatchIndex(t, -1)
		type xmlAction struct {
			start, end int
			content    string
		}
		var xmlActions []xmlAction
		for _, m := range actionMatches {
			xmlActions = append(xmlActions, xmlAction{
				start:   m[0],
				end:     m[1],
				content: strings.TrimSpace(t[m[2]:m[3]]),
			})
		}

		pos := 0
		for _, xa := range xmlActions {
			if xa.start > pos {
				processBracketText(t[pos:xa.start])
			}
			if xa.content != "" {
				messages = append(messages, model.ReviewMessage{
					Type:    model.ReviewMessageAction,
					Content: xa.content,
				})
			}
			pos = xa.end
		}
		if pos < len(t) {
			processBracketText(t[pos:])
		}
	}

	// processBracketText is the bracket-based action parser (backward compat).
	// Detects (action) and （action） patterns in text that wasn't already handled by XML tags.
	processBracketText = func(t string) {
		remaining := t
		for len(remaining) > 0 {
			actionStart := -1
			actionEnd := -1
			actionContent := ""

			runes := []rune(remaining)
			for ri, r := range runes {
				if r == '(' || r == '（' {
					actionStart = len(string(runes[:ri]))
					closeRune := ')'
					if r == '（' {
						closeRune = '）'
					}
					for rj := ri + 1; rj < len(runes); rj++ {
						if runes[rj] == closeRune {
							actionEnd = len(string(runes[:rj+1]))
							actionContent = string(runes[ri+1 : rj])
							break
						}
					}
					break
				}
			}

			if actionStart >= 0 {
				if actionStart > 0 {
					prefix := strings.TrimSpace(remaining[:actionStart])
					if prefix != "" {
						messages = append(messages, classifyText(autoDetectType(prefix), prefix)...)
					}
				}
				content := strings.TrimSpace(actionContent)
				if content != "" {
					messages = append(messages, model.ReviewMessage{
						Type:    model.ReviewMessageAction,
						Content: content,
					})
				}
				remaining = remaining[actionEnd:]
			} else {
				remaining = strings.TrimSpace(remaining)
				if remaining != "" {
					messages = append(messages, classifyText(autoDetectType(remaining), remaining)...)
				}
				break
			}
		}
	}

	// Phase 3: interleave code blocks and parsed text
	pos := 0
	for _, cb := range blocks {
		if cb.start > pos {
			processText(text[pos:cb.start])
		}
		messages = append(messages, model.ReviewMessage{
			Type:    model.ReviewMessageCode,
			Content: cb.content,
			Metadata: map[string]any{"language": cb.language},
		})
		pos = cb.end
	}
	if pos < len(text) {
		processText(text[pos:])
	}

	if len(messages) == 0 && text != "" {
		messages = append(messages, model.ReviewMessage{
			Type:    model.ReviewMessageChat,
			Content: strings.TrimSpace(text),
		})
	}

	return messages
}

// classifyText splits text by paragraph boundaries.
// markdown and code types are never sentence-split — they stay as complete blocks.
func classifyText(msgType model.ReviewMessageType, text string) []model.ReviewMessage {
	switch msgType {
	case model.ReviewMessageMarkdown, model.ReviewMessageCode:
		return []model.ReviewMessage{{Type: msgType, Content: text}}
	default:
		return splitChatByLines(msgType, text)
	}
}
