package orchestrator

import (
	"context"
	"fmt"
	"git.yeij.top/AskaEth/Cyrene/pkg/logger"
	"strings"
	"sync"
	"time"

	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/cache"
	ctxbuild "git.yeij.top/AskaEth/Cyrene/ai-core/internal/context"
	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/llm"
	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/memory"
	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/model"
	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/persona"
	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/subsession"

	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/bus"
	"git.yeij.top/AskaEth/Cyrene/ai-core/internal/scheduler"

	plgManager "git.yeij.top/AskaEth/Cyrene-Plugins/manager"
)

// Orchestrator 对话编排器 v2.0
// 负责：意图分析 → 子会话分派 → 结果汇总 → 综合生成回复
type Orchestrator struct {
	personaLoader   *persona.Loader
	contextBuilder  *ctxbuild.Builder
	llmAdapter      *llm.Adapter
	subManager      *subsession.Manager
	intentAnalyzer  *IntentAnalyzer
	synthesizer     *Synthesizer
	memoryRetriever *memory.Retriever
	memoryExtractor *memory.Extractor
	responseCache   *cache.ResponseCache
	eventBus        bus.Bus
	enrichmentStore *SessionEnrichmentStore
	msgScheduler    *scheduler.MessageScheduler
	emotionTracker  *persona.EmotionTracker
	toolRegistry    *plgManager.ToolRegistry
	traceFn         func(hop, sessionID, userID, label, status, detail string, durationMs int64) // trace 回调
	visionProvider  llm.LLMProvider // 视觉模型 (图片预处理)
	ocrProvider     llm.LLMProvider // OCR 模型 (文字提取，与视觉模型并行调用)
	videoProvider   llm.LLMProvider // 视频模型 (短视频理解)
	asrProvider     llm.ASRProvider  // ASR 语音识别 (语音消息转录)

	// 群聊并发：主会话繁忙时创建协会议话
	sessionProcMu   sync.Mutex
	sessionProc     map[string]bool // sessionID → currently processing
	activeCoSessions map[string]int // sessionID → active co-session count
	maxCoSessions   int             // max concurrent co-sessions per main session (default 3)
}

// SetResponseCache sets the response cache (optional, for Phase 0.2).
func (o *Orchestrator) SetResponseCache(c *cache.ResponseCache) {
	o.responseCache = c
}

// SetBus sets the event bus (optional, for Phase 1).
func (o *Orchestrator) SetBus(b bus.Bus) {
	o.eventBus = b
}

// SetEnrichmentStore sets the enrichment store (optional, for Phase 1 Step 2).
func (o *Orchestrator) SetEnrichmentStore(s *SessionEnrichmentStore) {
	o.enrichmentStore = s
}

// SetMessageScheduler sets the message scheduler (optional, for Phase 1 Step 3).
func (o *Orchestrator) SetMessageScheduler(s *scheduler.MessageScheduler) {
	o.msgScheduler = s
}

// SetEmotionTracker sets the emotion tracker (optional, for Phase 2).
func (o *Orchestrator) SetEmotionTracker(t *persona.EmotionTracker) {
	o.emotionTracker = t
}

// SetToolRegistry sets the tool registry for tool-calling support in the main chat flow.
func (o *Orchestrator) SetToolRegistry(tr *plgManager.ToolRegistry) {
	o.toolRegistry = tr
	o.synthesizer.toolRegistry = tr
}

// SetToolResultPusher sets the callback for proactive tool result delivery.
func (o *Orchestrator) SetToolResultPusher(pusher func(sessionID, userID, toolName, result string, params SynthesizeParams)) {
	o.synthesizer.SetResultPusher(pusher)
}

// SetTraceFunc sets the trace callback for pipeline event recording.
func (o *Orchestrator) SetTraceFunc(fn func(hop, sessionID, userID, label, status, detail string, durationMs int64)) {
	o.traceFn = fn
	o.synthesizer.SetTraceFunc(fn)
}

// SetVisionProvider sets the vision model provider for image preprocessing.
func (o *Orchestrator) SetVisionProvider(vp llm.LLMProvider) {
	o.visionProvider = vp
}

// SetOCRProvider sets the OCR model provider for text extraction.
func (o *Orchestrator) SetOCRProvider(op llm.LLMProvider) {
	o.ocrProvider = op
}

// SetVideoProvider sets the video model provider for short video understanding.
func (o *Orchestrator) SetVideoProvider(vp llm.LLMProvider) {
	o.videoProvider = vp
}

// SetASRProvider sets the ASR provider for voice message transcription.
func (o *Orchestrator) SetASRProvider(ap llm.ASRProvider) {
	o.asrProvider = ap
}

// getBus returns the bus or a nop fallback.
func (o *Orchestrator) getBus() bus.Bus {
	if o.eventBus == nil {
		return &bus.NopBus{}
	}
	return o.eventBus
}

// NewOrchestrator 创建编排器。
// chatAdapter 用于对话生成 (PurposeChat)，intentAdapter 用于意图分析 (PurposeIntentAnalysis)。
func NewOrchestrator(
	personaLoader *persona.Loader,
	contextBuilder *ctxbuild.Builder,
	chatAdapter *llm.Adapter,
	intentAdapter *llm.Adapter,
	subManager *subsession.Manager,
	memoryRetriever *memory.Retriever,
	memoryExtractor *memory.Extractor,
) *Orchestrator {
	return &Orchestrator{
		personaLoader:     personaLoader,
		contextBuilder:    contextBuilder,
		llmAdapter:        chatAdapter,
		subManager:        subManager,
		intentAnalyzer:    NewIntentAnalyzer(intentAdapter),
		synthesizer:       NewSynthesizer(chatAdapter, nil),
		memoryRetriever:   memoryRetriever,
		memoryExtractor:   memoryExtractor,
		sessionProc:       make(map[string]bool),
		activeCoSessions:  make(map[string]int),
		maxCoSessions:     3,
	}
}

// ProcessParams 处理参数
type ProcessParams struct {
	UserID      string
	SessionID   string
	Message     string
	Images      []string // 图片 base64 data URL (多模态)
	VideoURLs   []string // 视频 URL (多模态), ≤20s short videos
	VoiceURLs   []string // 语音 URL (ASR 转录)
	Mode        string   // text / voice_msg / voice_assistant
	Nickname    string
	ChannelType string // direct / group
	ChannelID   string // platform channel ID (group ID or private OBv11 number)
	BotUID      string // bot's own platform UID (e.g., OBv11 account)
	AdapterName string // adapter config name for routing
}

// ProcessResult 处理结果
type ProcessResult struct {
	FullContent string              // 完整回复文本
	Mode        string              // 回复模式
	Segments    []model.Segment     // 断句片段
	Intent      *model.IntentResult // 意图分析结果
}

// ProcessInput 处理用户输入 — 新的主入口
// 返回流式事件通道
// v2.1: 支持非阻塞子会话分派 + 简单问候快速通道 + 审查子会话
func (o *Orchestrator) ProcessInput(
	ctx context.Context,
	params ProcessParams,
) (<-chan model.StreamEvent, error) {

	eventCh := make(chan model.StreamEvent, 200)

	if params.Mode == "" {
		params.Mode = "text"
	}

	go func() {
		defer close(eventCh)
		defer func() {
			if r := recover(); r != nil {
				logger.Printf("[orchestrator] 编排器主循环 panic 恢复: %v", r)
			}
		}()

		// 0. 群聊并发：检测主会话是否繁忙，决定是主会话还是协会议话
		isCoSession := false
		o.sessionProcMu.Lock()
		if o.sessionProc[params.SessionID] {
			// 等待主会话释放（最多等 3s，避免永久死锁）
			waitStart := time.Now()
			for o.activeCoSessions[params.SessionID] >= o.maxCoSessions && time.Since(waitStart) < 3*time.Second {
				o.sessionProcMu.Unlock()
				select {
				case <-ctx.Done():
					o.sessionProcMu.Lock()
					o.sessionProcMu.Unlock()
					logger.Printf("[orchestrator] 等待会话释放时 context 取消")
					return
				case <-time.After(500 * time.Millisecond):
				}
				o.sessionProcMu.Lock()
			}
			if o.activeCoSessions[params.SessionID] >= o.maxCoSessions {
				o.sessionProcMu.Unlock()
				logger.Printf("[orchestrator] 协会议话已达上限且等待超时，拒绝请求")
				return
			}
			o.activeCoSessions[params.SessionID]++
			isCoSession = true
			o.sessionProcMu.Unlock()
			logger.Printf("[orchestrator] 主会话繁忙，创建协会议话 (session=%s, active=%d)", params.SessionID, o.activeCoSessions[params.SessionID])
		} else {
			o.sessionProc[params.SessionID] = true
			o.sessionProcMu.Unlock()
			defer func() {
				o.sessionProcMu.Lock()
				delete(o.sessionProc, params.SessionID)
				o.sessionProcMu.Unlock()
			}()
		}

		if isCoSession {
			defer func() {
				o.sessionProcMu.Lock()
				o.activeCoSessions[params.SessionID]--
				if o.activeCoSessions[params.SessionID] <= 0 {
					delete(o.activeCoSessions, params.SessionID)
				}
				o.sessionProcMu.Unlock()
			}()
		}

		// 0.5 发布合成开始事件
		o.getBus().Publish(bus.BusEvent{
			Type:      bus.EventSynthesisStarted,
			SessionID: params.SessionID,
			UserID:    params.UserID,
		})

		// 0.5 图片预处理: 使用视觉模型分析图片，将描述注入消息
	if len(params.Images) > 0 && o.visionProvider != nil {
		startTime := time.Now()
		augmented := o.PreprocessImages(ctx, params.Message, params.Images)
		if augmented != params.Message {
			params.Message = augmented
			logger.Printf("[orchestrator] 图片预处理耗时: %v, 原消息=%d字, 增强后=%d字",
				time.Since(startTime), len([]rune(params.Message))-len([]rune(augmented))+len([]rune(params.Message)), len([]rune(augmented)))
		}
		// 预处理后清空原始图片，避免后续传给不支持多模态的 Chat 模型
		params.Images = nil

		// 0.6 视频预处理: 使用视频模型分析短视频 (≤20s)，将描述注入消息
		if len(params.VideoURLs) > 0 && o.videoProvider != nil {
			startTime := time.Now()
			augmented := o.preprocessVideos(ctx, params.Message, params.VideoURLs)
			if augmented != params.Message {
				params.Message = augmented
				logger.Printf("[orchestrator] 视频预处理耗时: %v", time.Since(startTime))
			}
			params.VideoURLs = nil
		} else if len(params.VideoURLs) > 0 {
			logger.Printf("[orchestrator] 视频模型未配置，丢弃 %d 个视频", len(params.VideoURLs))
			params.VideoURLs = nil
		}

		// 0.7 语音预处理: 使用 ASR 模型转录语音消息，将文本注入消息
		if len(params.VoiceURLs) > 0 && o.asrProvider != nil && o.asrProvider.IsAvailable() {
			startTime := time.Now()
			augmented := o.preprocessVoice(ctx, params.Message, params.VoiceURLs)
			if augmented != params.Message {
				params.Message = augmented
				logger.Printf("[orchestrator] 语音预处理耗时: %v", time.Since(startTime))
			}
			params.VoiceURLs = nil
		} else if len(params.VoiceURLs) > 0 {
			logger.Printf("[orchestrator] ASR模型未配置，丢弃 %d 个语音", len(params.VoiceURLs))
			params.VoiceURLs = nil
		}
	} else if len(params.Images) > 0 {
		// 未配置 Vision 模型时，告知用户该模型不支持图片，并清空图片避免报错
		if params.Message == "" {
			params.Message = "(用户发送了一张图片，但当前未配置视觉模型，无法识别图片内容)"
		}
		logger.Printf("[orchestrator] 视觉模型未配置，丢弃 %d 张图片", len(params.Images))
		params.Images = nil
	}

	// 1. 意图分析
		startTime := time.Now()
		historyHint := o.buildHistoryHint(params.SessionID)
		intent, err := o.intentAnalyzer.Analyze(ctx, params.Message, historyHint)
		if err != nil || intent == nil {
			logger.Printf("[orchestrator] 意图分析失败: %v，使用默认值", err)
			intent = &model.IntentResult{
				Primary:     "chat",
				NeedsMemory: true,
				Sentiment:   "neutral",
				Urgency:     "low",
			}
		}
		logger.Printf("[orchestrator] 意图分析耗时: %v, primary=%s", time.Since(startTime), intent.Primary)
		if o.traceFn != nil {
			o.traceFn("intent", params.SessionID, params.UserID, "🎯 "+intent.Primary, "success", intent.Primary, time.Since(startTime).Milliseconds())
		}

			// 1.6 记录情感状态
			if o.emotionTracker != nil {
				o.emotionTracker.RecordSentiment(intent.Sentiment)
			}

		// 1.5 检查响应缓存
		if o.responseCache != nil {
			if cached, ok := o.responseCache.Get(params.Message); ok {
				logger.Printf("[orchestrator] 缓存命中，跳过 LLM 调用")
				fullContent := cached
				eventCh <- model.StreamEvent{
					Type:  model.StreamDelta,
					Delta: fullContent,
				}
				if reviewMessages := parseReviewMessages(fullContent); len(reviewMessages) > 0 {
					reviewMessages = o.scheduleWithDelays(reviewMessages)
					eventCh <- model.StreamEvent{
						Type:           model.StreamReview,
						ReviewMessages: reviewMessages,
					}
				}
				segmenter := llm.NewSegmenter()
				var segments []model.Segment
				for _, ch := range fullContent {
					newSegs := segmenter.Feed(string(ch))
					for _, s := range newSegs {
						segments = append(segments, model.Segment{Index: s.Index, Text: s.Text})
					}
				}
				if remaining := segmenter.Flush(); remaining != nil {
					segments = append(segments, model.Segment{Index: remaining.Index, Text: remaining.Text})
				}
				if len(segments) > 0 {
					eventCh <- model.StreamEvent{Type: model.StreamSegments, Segments: segments}
				}
				eventCh <- model.StreamEvent{Type: model.StreamDone}
				o.cacheAssistantMessage(params, fullContent)
				logger.Printf("[orchestrator] 缓存响应完成: len=%d", len([]rune(fullContent)))
				return
			}
		}

		// 2. 加载人格配置
		personaConfig, err := o.personaLoader.Get("cyrene")
		if err != nil {
			eventCh <- model.StreamEvent{
				Type:  model.StreamError,
				Error: fmt.Errorf("加载人格配置失败: %w", err),
			}
			return
		}

		// 确定用户名
		userName := params.Nickname
		if userName == "" {
			userName = params.UserID
		}

		// 注入 userID 到 context 供 MemoryProvider 使用
		subCtx := context.WithValue(ctx, "userID", params.UserID)

		// 3. 分派子会话（并行执行，非阻塞：先启动合成再等待子会话结果）
		createParams := subsession.CreateContextParams{
			UserID:        params.UserID,
			SessionID:     params.SessionID,
			UserMessage:   params.Message,
			PersonaConfig: personaConfig,
			Intent:        intent,
			Nickname:      userName,
		}

		// 只有明确的关键词问候才跳过子会话分派，日常闲聊也需要检索记忆
			// 因为 LLM 容易将日常闲聊误判为 needs_memory=false，导致回复缺乏上下文
			var resultCh <-chan model.SubSessionResult
			skipSubSessions := intent.Primary == "greeting" && !intent.NeedsMemory
			if skipSubSessions {
				logger.Printf("[orchestrator] 快速通道: 简单问候(primary=%s)，跳过子会话分派", intent.Primary)
				emptyCh := make(chan model.SubSessionResult)
				close(emptyCh)
				resultCh = emptyCh
		} else {
			resultCh = o.subManager.Dispatch(subCtx, intent, params.Message, createParams)
		}

		// 3.5 确保全局工具结果存储已初始化
		InitGlobalPendingToolStore()

		// 4. 加载上一轮异步完成的子会话富化结果
		var prevEnrichment *EnrichmentData
		if o.enrichmentStore != nil {
			prevEnrichment = o.enrichmentStore.Pop(params.SessionID)
			// Also merge any pending tool results from the global store
			if globalStore := GetGlobalPendingToolStore(); globalStore != nil {
				if toolData := globalStore.Pop(params.SessionID); toolData != nil && len(toolData.PendingToolResults) > 0 {
					if prevEnrichment == nil {
						prevEnrichment = &EnrichmentData{}
					}
					prevEnrichment.PendingToolResults = append(prevEnrichment.PendingToolResults, toolData.PendingToolResults...)
					logger.Printf("[orchestrator] 合并后台工具结果 %d 条", len(toolData.PendingToolResults))
				}
			}
		} else {
			// Still check global store even if enrichmentStore is not set
			if globalStore := GetGlobalPendingToolStore(); globalStore != nil {
				if toolData := globalStore.Pop(params.SessionID); toolData != nil && len(toolData.PendingToolResults) > 0 {
					prevEnrichment = toolData
					logger.Printf("[orchestrator] 加载后台工具结果 %d 条", len(toolData.PendingToolResults))
				}
			}
		}
			if prevEnrichment != nil {
			logger.Printf("[orchestrator] 加载上一轮富化结果: memory=%t thought=%t iot=%t knowledge=%t tools=%d",
				prevEnrichment.MemorySummary != "",
				prevEnrichment.ThoughtOutline != "",
				prevEnrichment.IoTSummary != "",
				prevEnrichment.KnowledgeInfo != "",
				len(prevEnrichment.PendingToolResults))
		}

		// 5. 先构建基础综合参数（不含子会话结果），开始合成
		history := o.contextBuilder.GetHistory(params.SessionID, 20)
		mood, expr := "", ""
			if o.emotionTracker != nil {
				mood, expr, _ = o.emotionTracker.GetCurrentMood()
			}
			systemPrompt := personaConfig.BuildSystemPromptWithMood(userName, 1, mood, expr)

		// 构建初始综合参数（注入上一轮富化结果）
		synthParams := SynthesizeParams{
			UserID:        params.UserID,
			SessionID:     params.SessionID,
			UserMessage:   params.Message,
			Images:        params.Images,
			Nickname:      userName,
			PersonaPrompt: systemPrompt,
			DialogHistory: history,
			Mode:          params.Mode,
			ChannelType:   params.ChannelType,
			ChannelID:    params.ChannelID,
			AdapterName:  params.AdapterName,
		}
		if prevEnrichment != nil {
			synthParams.MemorySummary = prevEnrichment.MemorySummary
			synthParams.ThoughtOutline = prevEnrichment.ThoughtOutline
			synthParams.IoTSummary = prevEnrichment.IoTSummary
			synthParams.KnowledgeInfo = prevEnrichment.KnowledgeInfo
			synthParams.PendingToolResults = prevEnrichment.PendingToolResults
		}

		// 异步收集子会话结果，存入 enrichmentStore 供下一轮使用
		go func() {
			var enriched EnrichmentData

			for result := range resultCh {
				if result.Error != "" {
					logger.Printf("[orchestrator] 子会话 %s 出错: %s", result.Type, result.Error)
					continue
				}

				switch result.Type {
				case model.SubSessionMemory:
					enriched.MemorySummary = result.Summary
					if result.Details != "" {
						enriched.MemorySummary += "\n" + result.Details
					}
					logger.Printf("[orchestrator] 记忆子会话完成: %s", result.Summary)
				case model.SubSessionGeneral:
					enriched.ThoughtOutline = result.Summary
					if result.Details != "" {
						enriched.ThoughtOutline += "\n" + result.Details
					}
					logger.Printf("[orchestrator] 通用对话子会话完成: %s", result.Summary)
				case model.SubSessionIoT:
					enriched.IoTSummary = result.Summary
				case model.SubSessionKnowledge:
					enriched.KnowledgeInfo = result.Summary
					logger.Printf("[orchestrator] IoT 子会话完成: %s", result.Summary)
				}
			}

			if o.enrichmentStore != nil {
				o.enrichmentStore.Store(params.SessionID, &enriched)
				logger.Printf("[orchestrator] 子会话全部完成，富化结果已存入下一轮")
			}
		}()

		// 5. 调用 Synthesizer 流式生成最终回复
		chunkCh, err := o.synthesizer.Synthesize(ctx, synthParams, eventCh)
		if err != nil {
			logger.Printf("[orchestrator] 综合器启动失败: %v", err)
			eventCh <- model.StreamEvent{
				Type:  model.StreamError,
				Error: fmt.Errorf("生成回复失败: %w", err),
			}
			return
		}

		// 6. 流式输出 delta
		var fullContent string
		segmenter := llm.NewSegmenter()
		var segments []model.Segment

		for chunk := range chunkCh {
			if chunk.Error != nil {
				logger.Printf("[orchestrator] 流式错误: %v", chunk.Error)
				eventCh <- model.StreamEvent{
					Type:  model.StreamError,
					Error: chunk.Error,
				}
				return
			}

			if chunk.Done {
				if remaining := segmenter.Flush(); remaining != nil {
					segments = append(segments, model.Segment{
						Index: remaining.Index,
						Text:  remaining.Text,
					})
				}
				break
			}

			if chunk.Content != "" {
				fullContent += chunk.Content

				// 实时断句
				newSegs := segmenter.Feed(chunk.Content)
				for _, s := range newSegs {
					segments = append(segments, model.Segment{
						Index: s.Index,
						Text:  s.Text,
					})
				}

				eventCh <- model.StreamEvent{
					Type:  model.StreamDelta,
					Delta: chunk.Content,
				}
			}
		}

		// 7. 审查完整回复文本，生成带类型的消息列表
		if fullContent != "" {
			reviewMessages := parseReviewMessages(fullContent)
			if len(reviewMessages) > 0 {
				// 通过 MessageScheduler 计算每条消息的发送延迟
				reviewMessages = o.scheduleWithDelays(reviewMessages)
				eventCh <- model.StreamEvent{
					Type:           model.StreamReview,
					ReviewMessages: reviewMessages,
				}
				logger.Printf("[orchestrator] 审查完成: %d 条带类型消息", len(reviewMessages))
			}
				o.getBus().Publish(bus.BusEvent{
					Type:      bus.EventReviewReady,
					SessionID: params.SessionID,
					UserID:    params.UserID,
					Payload:   bus.ReviewPayload{Messages: reviewMessages},
				})
		}

		// 8. 发送断句信息
		if len(segments) > 0 {
			eventCh <- model.StreamEvent{
				Type:     model.StreamSegments,
				Segments: segments,
			}
		}

		// 9. 完成
		eventCh <- model.StreamEvent{
			Type: model.StreamDone,
		}

		o.getBus().Publish(bus.BusEvent{
			Type:      bus.EventSynthesisDone,
			SessionID: params.SessionID,
			UserID:    params.UserID,
		})

		// 10. 后处理：缓存回复
		if fullContent != "" {
			o.cacheAssistantMessage(params, fullContent)
			if o.responseCache != nil {
				o.responseCache.Set(params.Message, fullContent)
			}
		}

		// 11. 异步提取记忆
		if o.memoryExtractor != nil && fullContent != "" {
			go o.memoryExtractor.ExtractAndStore(
				context.Background(),
				params.UserID,
				params.SessionID,
				params.Message,
				fullContent,
			)
		}

		logger.Printf("[orchestrator] 处理完成: intent=%s, content_len=%d, time=%v",
			intent.Primary, len([]rune(fullContent)), time.Since(startTime))
		if o.traceFn != nil {
			totalMs := time.Since(startTime).Milliseconds()
			o.traceFn("response", params.SessionID, params.UserID, "💬 回复", "success", fmt.Sprintf("len=%d", len([]rune(fullContent))), totalMs)
		}
	}()

	return eventCh, nil
}

// ExtractMemoriesOnly 仅提取记忆，不生成回复。
// 用于 platform_silent 模式：观察群聊消息并提取值得记住的信息到对应命名空间。
func (o *Orchestrator) ExtractMemoriesOnly(ctx context.Context, userID, sessionID, message string) {
	if o.memoryExtractor == nil {
		return
	}
	o.memoryExtractor.ExtractObservations(ctx, userID, sessionID, message)
}

// scheduleWithDelays 通过 MessageScheduler 为审查消息分配发送延迟
func (o *Orchestrator) scheduleWithDelays(messages []model.ReviewMessage) []model.ReviewMessage {
	if o.msgScheduler == nil || len(messages) <= 1 {
		return messages
	}

	scheduled := make([]scheduler.ScheduledMessage, len(messages))
	for i, m := range messages {
		displayType := scheduler.DisplayChat
		if m.Type == model.ReviewMessageAction {
			displayType = scheduler.DisplayAction
		}
		scheduled[i] = scheduler.ScheduledMessage{
			Type:    displayType,
			Content: m.Content,
		}
	}

	scheduled = o.msgScheduler.Schedule(scheduled)

	for i := range messages {
		messages[i].DelayMs = int(scheduled[i].Delay.Milliseconds())
	}

	return messages
}

// splitReviewLongMessage 将长消息按句子边界拆分为多条短消息
func splitReviewLongMessage(msgType model.ReviewMessageType, text string) []model.ReviewMessage {
	const maxLen = 80 // 最大字符数（按 rune 计数）

	runes := []rune(text)
	if len(runes) <= maxLen {
		return []model.ReviewMessage{{Type: msgType, Content: text}}
	}
	// ... split by sentence boundaries for long messages
	return splitLongText(msgType, runes, maxLen)
}

// splitChatByLines 将聊天文本按双换行（段落分隔）拆分为多条消息，每条再检查是否需要按长度拆分
func splitChatByLines(msgType model.ReviewMessageType, text string) []model.ReviewMessage {
	lines := strings.Split(text, "\n\n")
	var msgs []model.ReviewMessage
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}
		msgs = append(msgs, splitReviewLongMessage(msgType, line)...)
	}
	return msgs
}

// splitLongText 将文本按句子边界分割
func splitLongText(msgType model.ReviewMessageType, runes []rune, maxLen int) []model.ReviewMessage {

	var messages []model.ReviewMessage
	start := 0

	for start < len(runes) {
		end := start + maxLen
		if end > len(runes) {
			end = len(runes)
		}

		// 尝试在句子边界处分割
		if end < len(runes) {
			lastBreak := -1
			// 先找句号、感叹号、问号
			for i := end - 1; i >= start+maxLen/2; i-- {
				ch := runes[i]
				if ch == '。' || ch == '！' || ch == '？' || ch == '.' || ch == '!' || ch == '?' || ch == '；' || ch == ';' || ch == '\n' {
					lastBreak = i
					break
				}
			}
			// 再找逗号
			if lastBreak < 0 {
				for i := end - 1; i >= start+maxLen/2; i-- {
					ch := runes[i]
					if ch == '，' || ch == ',' || ch == ' ' || ch == '　' {
						lastBreak = i
						break
					}
				}
			}

			if lastBreak > 0 {
				end = lastBreak + 1
			}
		}

		chunk := strings.TrimSpace(string(runes[start:end]))
		if chunk != "" {
			messages = append(messages, model.ReviewMessage{
				Type:    msgType,
				Content: chunk,
			})
		}
		start = end
	}

	if len(messages) == 0 {
		messages = append(messages, model.ReviewMessage{
			Type:    msgType,
			Content: string(runes),
		})
	}

	return messages
}

// ProcessInputSync 同步处理用户输入（兼容旧接口）
func (o *Orchestrator) ProcessInputSync(
	ctx context.Context,
	params ProcessParams,
) (*ProcessResult, error) {

	eventCh, err := o.ProcessInput(ctx, params)
	if err != nil {
		return nil, err
	}

	result := &ProcessResult{
		Mode: params.Mode,
	}

	for event := range eventCh {
		switch event.Type {
		case model.StreamError:
			return nil, event.Error
		case model.StreamDelta:
			result.FullContent += event.Delta
		case model.StreamSegments:
			result.Segments = event.Segments
		case model.StreamDone:
			// 完成
		}
	}

	return result, nil
}

// GetHistory 获取会话历史（暴露给外部使用）
func (o *Orchestrator) GetHistory(sessionID string, limit int) []model.LLMMessage {
	if o.contextBuilder == nil {
		return nil
	}
	return o.contextBuilder.GetHistory(sessionID, limit)
}

// buildHistoryHint returns a short context string from recent conversation history.
// Used by the intent analyzer to disambiguate follow-up questions from IoT queries.
func (o *Orchestrator) buildHistoryHint(sessionID string) string {
	if o.contextBuilder == nil {
		return ""
	}
	history := o.contextBuilder.GetHistory(sessionID, 3)
	if len(history) == 0 {
		return ""
	}
	var parts []string
	for _, m := range history {
		roleLabel := "用户"
		if m.Role == model.RoleAssistant {
			roleLabel = "昔涟"
		}
		content := []rune(m.Content)
		if len(content) > 60 {
			content = content[:60]
		}
		parts = append(parts, fmt.Sprintf("%s: %s", roleLabel, string(content)))
	}
	return strings.Join(parts, "\n")
}

// CacheMessage 缓存消息
func (o *Orchestrator) CacheMessage(sessionID string, role model.Role, content string) {
	if o.contextBuilder != nil {
		o.contextBuilder.CacheMessage(sessionID, role, content)
	}
}

// cacheAssistantMessage caches the assistant response.
func (o *Orchestrator) cacheAssistantMessage(params ProcessParams, fullContent string) {
	if o.contextBuilder == nil {
		return
	}
	o.contextBuilder.CacheMessage(params.SessionID, model.RoleAssistant, fullContent)
}

// PreprocessImages uses vision and OCR models to analyze images and augments the user message.
// When both vision and OCR providers are available (and are different models), they are called
// in parallel and both results are passed to the chat model for autonomous judgment.
// For standalone images (no text): generates a comprehensive description as the message.
// For text+images: appends image descriptions as contextual annotations.
func (o *Orchestrator) PreprocessImages(ctx context.Context, message string, images []string) string {
	visionPromptBase := "请详细描述这张图片的内容，包括场景、物体、人物、文字（如有）、颜色、氛围等所有视觉信息。"
	ocrPromptBase := `请逐字逐句完整提取图片中的所有文字内容，保持原有格式和排版。如果图片中没有文字，请回复"无文字"。`

	if message != "" {
		visionPromptBase = fmt.Sprintf("用户的问题是：「%s」\n\n请根据用户的问题，分析这张图片中相关的视觉信息，帮助回答用户的问题。如果图片中有文字，请完整提取。", message)
		ocrPromptBase = fmt.Sprintf(`用户的问题是：「%s」

请逐字逐句完整提取图片中的所有文字内容，保持原有格式和排版。如果图片中没有文字，请回复"无文字"。`, message)
	}

	// Determine if OCR is a distinct model (avoid double-calling the same model)
	useDual := o.ocrProvider != nil && o.visionProvider != nil &&
		o.ocrProvider.ModelName() != o.visionProvider.ModelName()

	var descriptions []string
	for i, img := range images {
		var visionDesc, ocrDesc string
		var wg sync.WaitGroup

		if o.visionProvider != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				resp, err := o.visionProvider.Chat(ctx, []model.LLMMessage{
					{Role: model.RoleUser, Content: visionPromptBase, Images: []string{img}},
				})
				if err != nil {
					logger.Printf("[orchestrator] 图片 %d 视觉分析失败: %v", i, err)
					return
				}
				visionDesc = resp.Content
			}()
		}

		if useDual {
			wg.Add(1)
			go func() {
				defer wg.Done()
				resp, err := o.ocrProvider.Chat(ctx, []model.LLMMessage{
					{Role: model.RoleUser, Content: ocrPromptBase, Images: []string{img}},
				})
				if err != nil {
					logger.Printf("[orchestrator] 图片 %d OCR提取失败: %v", i, err)
					return
				}
				ocrDesc = resp.Content
			}()
		}

		wg.Wait()

		var combined string
		switch {
		case visionDesc != "" && ocrDesc != "":
			combined = fmt.Sprintf("这张图片的内容：%s（图中包含的文字：%s）", visionDesc, ocrDesc)
		case visionDesc != "":
			combined = visionDesc
		case ocrDesc != "":
			combined = ocrDesc
		}

		if combined != "" {
			descriptions = append(descriptions, combined)
		}
	}

	if len(descriptions) == 0 {
		return message
	}

	if message == "" {
		return strings.Join(descriptions, "\n\n")
	}

	augmented := message
	for i, desc := range descriptions {
		label := "图片分析结果"
		if len(descriptions) > 1 {
			label = fmt.Sprintf("图片%d分析结果", i+1)
		}
		augmented += fmt.Sprintf("\n\n[%s]: %s", label, desc)
	}
	return augmented
}

// preprocessVideos uses the video model to analyze short videos and augments the message.
func (o *Orchestrator) preprocessVideos(ctx context.Context, message string, videoURLs []string) string {
	if o.videoProvider == nil {
		return message
	}

	var descriptions []string
	for i, url := range videoURLs {
		resp, err := o.videoProvider.Chat(ctx, []model.LLMMessage{
			{Role: model.RoleUser, Content: "请用简短的中文描述这个视频的内容，包括场景、人物、动作等。控制在100字以内。", VideoURLs: []string{url}},
		})
		if err != nil {
			logger.Printf("[orchestrator] 视频 %d 分析失败: %v", i, err)
			continue
		}
		if resp.Content != "" {
			descriptions = append(descriptions, resp.Content)
		}
	}

	if len(descriptions) == 0 {
		return message
	}

	if message == "" {
		return strings.Join(descriptions, "\n\n")
	}

	augmented := message
	for i, desc := range descriptions {
		augmented += fmt.Sprintf("\n\n[视频%d的分析]: %s", i+1, desc)
	}
	return augmented
}

// preprocessVoice transcribes voice messages using the ASR provider and augments the message.
func (o *Orchestrator) preprocessVoice(ctx context.Context, message string, voiceURLs []string) string {
	if o.asrProvider == nil || !o.asrProvider.IsAvailable() {
		return message
	}

	var transcriptions []string
	for i, url := range voiceURLs {
		text, err := o.asrProvider.Transcribe(ctx, url, "zh")
		if err != nil {
			logger.Printf("[orchestrator] 语音 %d 转录失败: %v", i, err)
			continue
		}
		if text != "" {
			transcriptions = append(transcriptions, text)
		}
	}

	if len(transcriptions) == 0 {
		return message
	}

	if message == "" {
		return strings.Join(transcriptions, "\n\n")
	}

	augmented := message
	for i, t := range transcriptions {
		augmented += fmt.Sprintf("\n\n[语音%d的转写]: %s", i+1, t)
	}
	return augmented
}

// Ensure time, memory are used
var _ = time.Now
var _ = memory.NewRetriever
