package app

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"slices"
	"strings"
	"time"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	"github.com/voocel/agentcore"
	"github.com/voocel/agentcore/llm"
	"github.com/voocel/ainovel-cli/domain"
	"github.com/voocel/ainovel-cli/state"
	"github.com/voocel/ainovel-cli/tools"
	"github.com/voocel/litellm"
)

// emitFn 是可选的 UIEvent 发射回调，用于向 TUI 转发结构化事件。
// CLI 模式下为 nil，Runtime 模式下指向 events channel。
type emitFn func(UIEvent)

// deltaFn 是可选的流式 token 回调，用于向 TUI 转发 LLM 生成的文字。
type deltaFn func(delta string)

// clearFn 是可选的流式缓冲清空回调，在新一轮 LLM 输出开始时触发。
type clearFn func()

// Run 启动小说创作流程（CLI 模式，阻塞直到完成）。
func Run(cfg Config, refs tools.References, prompts Prompts, styles map[string]string) error {
	cfg.FillDefaults()
	if err := cfg.Validate(); err != nil {
		return err
	}
	log.Printf("[boot] provider=%s model=%s base_url=%s output=%s", cfg.Provider, cfg.ModelName, cfg.BaseURL, cfg.OutputDir)

	// 1. 初始化状态
	store := state.NewStore(cfg.OutputDir)
	if err := store.Init(); err != nil {
		return fmt.Errorf("init store: %w", err)
	}

	// 1.5 日志写入文件（CLI 模式同时输出到 stderr 和日志文件）
	if cleanup := setupFileLogger(store.Dir()); cleanup != nil {
		defer cleanup()
	}

	// 2. 创建模型
	model, err := createModel(cfg)
	if err != nil {
		return fmt.Errorf("create model: %w", err)
	}

	// 3. 组装 Coordinator
	coordinator, askUser := BuildCoordinator(cfg, store, model, refs, prompts, styles)
	askUser.SetHandler(cliAskUserHandler)

	// 4. 确定性控制面：事件监听 + FollowUp 注入
	registerSubscription(coordinator, store, cfg.Provider, nil, nil, nil)

	// 5. 初始化运行元信息（保留已有 SteerHistory）
	if err := store.InitRunMeta(cfg.Style, cfg.Provider, cfg.ModelName); err != nil {
		log.Printf("[warn] 初始化运行元信息失败: %v", err)
	}

	// 6. Steer 协程：stdin 读取用户干预
	go func() {
		scanner := bufio.NewScanner(os.Stdin)
		for scanner.Scan() {
			text := strings.TrimSpace(scanner.Text())
			if text == "" {
				continue
			}
			submitSteer(store, coordinator, text)
		}
	}()

	// 7. 恢复或启动
	progress, _ := store.LoadProgress()
	runMeta, _ := store.LoadRunMeta()
	recovery := determineRecovery(progress, runMeta)

	if recovery.IsNew {
		if err := store.InitProgress(cfg.NovelName, 0); err != nil {
			return fmt.Errorf("init progress: %w", err)
		}
		log.Printf("新建模式：%s", cfg.NovelName)
		promptText := fmt.Sprintf(
			"请创作一部小说。若需求中有明确章节数，请严格遵守；若无明确章节数且题材适合长篇连载，请优先规划为分层长篇结构。要求如下：\n\n%s",
			cfg.Prompt,
		)
		if err := coordinator.Prompt(promptText); err != nil {
			return fmt.Errorf("prompt: %w", err)
		}
	} else {
		log.Printf("%s", recovery.Label)
		if err := coordinator.Prompt(recovery.PromptText); err != nil {
			return fmt.Errorf("prompt: %w", err)
		}
	}

	// 8. 等待完成
	coordinator.WaitForIdle()
	finalizeSteerIfIdle(store)

	// 9. 输出结果
	finalProgress, _ := store.LoadProgress()
	if finalProgress != nil {
		log.Printf("创作完成：%d 章，共 %d 字，输出目录：%s",
			len(finalProgress.CompletedChapters), finalProgress.TotalWordCount, store.Dir())
	}
	return nil
}

// registerSubscription 注册 coordinator 事件订阅，包含确定性控制和可选的 UIEvent/Delta 转发。
func registerSubscription(coordinator *agentcore.Agent, store *state.Store, provider string, emit emitFn, onDelta deltaFn, onClear clearFn) {
	var lastProgressSummary string
	agentExt := newFieldExtractor("agent")   // Coordinator → subagent 目标 agent 名称
	taskExt := newFieldExtractor("task")     // Coordinator → subagent 调度指令
	subFilter := newStreamFilter("content")  // SubAgent：文本透传 + JSON 提取 content

	coordinator.Subscribe(func(ev agentcore.Event) {
		switch ev.Type {
		case agentcore.EventToolExecStart:
			log.Printf("[tool:start] %s", ev.Tool)
			if emit != nil {
				emit(UIEvent{Time: time.Now(), Category: "TOOL", Summary: ev.Tool + ".start", Level: "info"})
			}

		case agentcore.EventToolExecUpdate:
			// 区分流式 delta 和进度摘要
			if delta, ok := parseStreamDelta(ev); ok {
				if onDelta != nil {
					if text := subFilter.Feed(delta); text != "" {
						onDelta(text)
					}
				}
				return
			}
			summary := parseProgressSummary(ev)
			if summary == "" {
				return
			}
			if summary == lastProgressSummary {
				return
			}
			lastProgressSummary = summary
			log.Printf("[progress] %s", summary)
			if emit != nil {
				emit(UIEvent{Time: time.Now(), Category: "TOOL", Summary: summary, Level: "info"})
			}

		case agentcore.EventMessageStart:
			// 新一轮 LLM 输出开始，重置提取器 + 清空流式缓冲
			agentExt.Reset()
			taskExt.Reset()
			subFilter.Reset()
			if onClear != nil {
				onClear()
			}

		case agentcore.EventMessageUpdate:
			// Coordinator 的流式 token：先提取 agent 名称做标题，再提取 task 内容
			if ev.Delta != "" && onDelta != nil {
				if name := agentExt.Feed(ev.Delta); name != "" {
					onDelta("\n▸ " + agentLabel(name) + "\n")
				}
				if text := taskExt.Feed(ev.Delta); text != "" {
					onDelta(text)
				}
			}

		case agentcore.EventToolExecEnd:
			lastProgressSummary = ""
			if ev.IsError {
				detail := extractToolErrorText(ev.Result)
				if detail != "" {
					log.Printf("[tool:error] %s → %s", ev.Tool, detail)
				} else {
					log.Printf("[tool:error] %s", ev.Tool)
				}
				if emit != nil {
					summary := ev.Tool + " 执行失败"
					if detail != "" {
						summary += "： " + truncateLog(detail, 80)
					}
					emit(UIEvent{Time: time.Now(), Category: "ERROR", Summary: summary, Level: "error"})
				}
				return
			}

			// subagent 结果：提取 usage 和 error，单独记录
			if ev.Tool == "subagent" {
				logSubAgentResult(ev.Result, emit)
				handleFoundationCheck(coordinator, store, emit)
				committed := handleSubAgentDone(coordinator, store, emit)
				if !committed {
					handleUncommittedDraft(coordinator, store, emit)
				}
				handleEditorDone(coordinator, store, emit)
				break
			}

			// novel_context：提取加载摘要替代原始 JSON
			if ev.Tool == "novel_context" {
				if summary := extractLoadingSummary(ev.Result); summary != "" {
					log.Printf("[tool:done] novel_context → %s", summary)
					if emit != nil {
						emit(UIEvent{Time: time.Now(), Category: "CONTEXT", Summary: summary, Level: "info"})
					}
				} else {
					log.Printf("[tool:done] novel_context → %s", truncateLog(string(ev.Result), 200))
				}
				if emit != nil {
					emit(UIEvent{Time: time.Now(), Category: "TOOL", Summary: "novel_context.done", Level: "info"})
				}
				break
			}

			// 其他工具：保持原样
			log.Printf("[tool:done] %s → %s", ev.Tool, truncateLog(string(ev.Result), 200))
			if emit != nil {
				emit(UIEvent{Time: time.Now(), Category: "TOOL", Summary: ev.Tool + ".done", Level: "info"})
			}

		case agentcore.EventMessageEnd:
			if ev.Message != nil && ev.Message.GetRole() == agentcore.RoleAssistant {
				text := truncateLog(ev.Message.TextContent(), 300)
				log.Printf("[assistant] %s", text)
				if emit != nil {
					emit(UIEvent{Time: time.Now(), Category: "AGENT", Summary: truncateLog(ev.Message.TextContent(), 80), Level: "info"})
				}
			}

		case agentcore.EventError:
			log.Printf("[error][provider=%s] %v", provider, ev.Err)
			if emit != nil {
				emit(UIEvent{Time: time.Now(), Category: "ERROR", Summary: fmt.Sprintf("[%s] %v", provider, ev.Err), Level: "error"})
			}
		}
	})
}

func planningTierGuidance(runMeta *domain.RunMeta) string {
	if runMeta == nil {
		return ""
	}
	switch runMeta.PlanningTier {
	case domain.PlanningTierShort:
		return "当前规划级别：short。如需调整设定或重做大纲，优先调用 architect_short。"
	case domain.PlanningTierMid:
		return "当前规划级别：mid。如需调整设定或重做大纲，优先调用 architect_mid。"
	case domain.PlanningTierLong:
		return "当前规划级别：long。如需调整设定或重做大纲，优先调用 architect_long，并保持分层大纲的一致性。"
	default:
		return ""
	}
}

// submitSteer 提交用户干预（CLI 和 Runtime 共用）。
func submitSteer(store *state.Store, coordinator *agentcore.Agent, text string) {
	log.Printf("[steer] 用户干预: %s", text)
	if err := store.AppendSteerEntry(domain.SteerEntry{
		Input:     text,
		Timestamp: time.Now().Format(time.RFC3339),
	}); err != nil {
		log.Printf("[warn] 追加干预记录失败: %v", err)
	}
	if err := store.SetPendingSteer(text); err != nil {
		log.Printf("[warn] 设置待处理干预失败: %v", err)
	}
	if err := store.SetFlow(domain.FlowSteering); err != nil {
		log.Printf("[warn] 设置流程状态失败: %v", err)
	}
	runMeta, err := store.LoadRunMeta()
	if err != nil {
		log.Printf("[warn] 读取运行元信息失败: %v", err)
	}
	guidance := planningTierGuidance(runMeta)
	message := fmt.Sprintf("[用户干预] %s\n请评估影响范围，决定是否需要修改设定或重写已有章节。", text)
	if guidance != "" {
		message += "\n" + guidance
	}
	coordinator.Steer(agentcore.UserMsg(fmt.Sprintf(
		"%s", message)))
}

// recoveryResult 恢复链的判断结果。
type recoveryResult struct {
	PromptText string // 恢复时的 Prompt 文本
	Label      string // 恢复类型描述（供日志和 TUI 显示）
	IsNew      bool   // true 表示新建模式
}

// determineRecovery 根据 Progress 和 RunMeta 判断恢复类型和 Prompt 文本。
// 章节总数完全来自 Progress.TotalChapters（由大纲自动设定），不再由外部传入。
func determineRecovery(progress *domain.Progress, runMeta *domain.RunMeta) recoveryResult {
	if progress == nil || progress.Phase == domain.PhaseInit {
		return recoveryResult{IsNew: true}
	}
	guidance := planningTierGuidance(runMeta)
	withGuidance := func(prompt string) string {
		if guidance == "" {
			return prompt
		}
		return prompt + "\n" + guidance
	}

	// 规划阶段恢复：premise 已保存但 outline/characters 尚未完成
	if progress.Phase == domain.PhasePremise {
		return recoveryResult{
			PromptText: withGuidance("前提设定已保存，但大纲/角色设定尚未完成。请调用 novel_context 查看已有设定，然后继续完成 outline、characters、world_rules 的规划，完成后开始逐章写作。"),
			Label:      "规划恢复：继续生成大纲/角色设定",
		}
	}

	// 规划阶段恢复：outline 已保存但写作尚未开始
	if progress.Phase == domain.PhaseOutline {
		return recoveryResult{
			PromptText: withGuidance(fmt.Sprintf(
				"大纲规划已完成，尚未开始写作。请从第 1 章开始逐章写作，总共需要写 %d 章。",
				progress.TotalChapters)),
			Label: fmt.Sprintf("写作恢复：大纲就绪，从第1章开始（共 %d 章）", progress.TotalChapters),
		}
	}

	if progress.InProgressChapter > 0 {
		ch := progress.InProgressChapter
		return recoveryResult{
			PromptText: withGuidance(fmt.Sprintf(
				"第 %d 章正在进行中，已有部分草稿。请调用 writer 继续完成该章（可用 read_chapter 读取已有草稿）。总共需要写 %d 章。",
				ch, progress.TotalChapters)),
			Label: fmt.Sprintf("恢复：第 %d 章进行中", ch),
		}
	}

	if len(progress.PendingRewrites) > 0 {
		verb := "重写"
		if progress.Flow == domain.FlowPolishing {
			verb = "打磨"
		}
		return recoveryResult{
			PromptText: withGuidance(fmt.Sprintf(
				"有 %d 章待%s（受影响章节：%v）。原因：%s。请逐章调用 writer %s后继续正常写作。总共需要写 %d 章。",
				len(progress.PendingRewrites), verb, progress.PendingRewrites, progress.RewriteReason, verb, progress.TotalChapters)),
			Label: fmt.Sprintf("%s恢复：%d 章待处理 %v", verb, len(progress.PendingRewrites), progress.PendingRewrites),
		}
	}

	if progress.Flow == domain.FlowReviewing {
		return recoveryResult{
			PromptText: withGuidance(fmt.Sprintf(
				"上次审阅中断，请重新调用 editor 对已完成章节进行全局审阅。已完成 %d 章，共 %d 字。总共需要写 %d 章。",
				len(progress.CompletedChapters), progress.TotalWordCount, progress.TotalChapters)),
			Label: "审阅恢复：上次审阅中断",
		}
	}

	if progress.IsResumable() && runMeta != nil && runMeta.PendingSteer != "" {
		next := progress.NextChapter()
		return recoveryResult{
			PromptText: withGuidance(fmt.Sprintf(
				"从第 %d 章继续写作。之前已完成 %d 章，共 %d 字。总共需要写 %d 章。\n\n[用户干预-恢复] %s\n请评估影响范围，决定是否需要修改设定或重写已有章节。",
				next, len(progress.CompletedChapters), progress.TotalWordCount, progress.TotalChapters, runMeta.PendingSteer)),
			Label: "Steer 恢复：上次干预未完成，重新注入",
		}
	}

	if progress.IsResumable() {
		next := progress.NextChapter()
		return recoveryResult{
			PromptText: withGuidance(fmt.Sprintf(
				"从第 %d 章继续写作。之前已完成 %d 章，共 %d 字。总共需要写 %d 章。",
				next, len(progress.CompletedChapters), progress.TotalWordCount, progress.TotalChapters)),
			Label: fmt.Sprintf("恢复模式：从第 %d 章继续（已完成 %d 章，共 %d 字）",
				next, len(progress.CompletedChapters), progress.TotalWordCount),
		}
	}

	return recoveryResult{IsNew: true}
}

// handleFoundationCheck 在 SubAgent 完成后检查基础设定是否完备。
// 如果 phase 仍在 premise（有 premise 但无 outline），注入确定性提醒。
func handleFoundationCheck(coordinator *agentcore.Agent, store *state.Store, emit emitFn) {
	progress, _ := store.LoadProgress()
	if progress == nil {
		return
	}
	// 只在规划阶段检查（premise 已保存但 outline 未保存）
	if progress.Phase != domain.PhasePremise {
		return
	}
	var missing []string
	if o, _ := store.LoadOutline(); len(o) == 0 {
		missing = append(missing, "outline")
	}
	if c, _ := store.LoadCharacters(); len(c) == 0 {
		missing = append(missing, "characters")
	}
	if r, _ := store.LoadWorldRules(); len(r) == 0 {
		missing = append(missing, "world_rules")
	}
	if len(missing) == 0 {
		return
	}
	log.Printf("[host] 基础设定不完整，缺失: %v", missing)
	if emit != nil {
		emit(UIEvent{Time: time.Now(), Category: "SYSTEM",
			Summary: fmt.Sprintf("基础设定不完整，缺失: %v", missing), Level: "warn"})
	}
	runMeta, _ := store.LoadRunMeta()
	guidance := planningTierGuidance(runMeta)
	msg := fmt.Sprintf(
		"[系统] 基础设定不完整，以下项目尚未保存：%v。请重新调用对应规划师补全这些设定。在基础设定全部完备前，不要调用 writer。",
		missing)
	if guidance != "" {
		msg += "\n" + guidance
	}
	coordinator.FollowUp(agentcore.UserMsg(msg))
}

// handleSubAgentDone 在每次 SubAgent 调用完成后读取文件系统信号，注入确定性任务。
// 返回 true 表示检测到 commit 信号（Writer 正常完成）。
func handleSubAgentDone(coordinator *agentcore.Agent, store *state.Store, emit emitFn) bool {
	result, err := store.LoadLastCommit()
	if err != nil || result == nil {
		return false
	}
	if err := store.ClearLastCommit(); err != nil {
		log.Printf("[host] 清除 commit 信号失败: %v", err)
	}

	log.Printf("[host] 章节提交信号：第 %d 章，%d 字",
		result.Chapter, result.WordCount)
	if emit != nil {
		emit(UIEvent{
			Time:     time.Now(),
			Category: "SYSTEM",
			Summary:  fmt.Sprintf("第 %d 章已提交：%d 字", result.Chapter, result.WordCount),
			Level:    "success",
		})
	}

	// outline_feedback 处理：Writer 反馈大纲偏离
	if result.Feedback != nil && result.Feedback.Deviation != "" {
		log.Printf("[host] outline_feedback: %s", result.Feedback.Deviation)
		if emit != nil {
			emit(UIEvent{Time: time.Now(), Category: "SYSTEM",
				Summary: "Writer 反馈大纲偏离: " + truncateLog(result.Feedback.Deviation, 60), Level: "info"})
		}
		coordinator.FollowUp(agentcore.UserMsg(fmt.Sprintf(
			"[系统] Writer 在第 %d 章写作中发现大纲偏离。偏离：%s。建议：%s。请评估是否需要调整后续大纲。",
			result.Chapter, result.Feedback.Deviation, result.Feedback.Suggestion)))
	}

	// 确定性判断 0：正在重写/打磨流程中
	progress, _ := store.LoadProgress()
	if progress != nil && (progress.Flow == domain.FlowRewriting || progress.Flow == domain.FlowPolishing) {
		if !slices.Contains(progress.PendingRewrites, result.Chapter) {
			log.Printf("[host] 警告：重写期间提交了非队列章节 %d，拒绝并提醒", result.Chapter)
			coordinator.FollowUp(agentcore.UserMsg(fmt.Sprintf(
				"[系统] 当前处于重写流程，但提交了非队列章节（第 %d 章）。请先完成待重写章节 %v 后再继续新章节。",
				result.Chapter, progress.PendingRewrites)))
			return true
		}
		if err := store.CompleteRewrite(result.Chapter); err != nil {
			log.Printf("[host] 完成重写标记失败: %v", err)
		}
		clearHandledSteer(store)
		updated, _ := store.LoadProgress()
		if updated != nil && len(updated.PendingRewrites) == 0 {
			log.Printf("[host] 所有重写/打磨已完成，恢复正常写作")
			saveCheckpoint(store, fmt.Sprintf("ch%02d-commit", result.Chapter))
			saveCheckpoint(store, "rewrite-done")
			if emit != nil {
				emit(UIEvent{Time: time.Now(), Category: "SYSTEM", Summary: "所有重写/打磨已完成", Level: "success"})
			}
		} else if updated != nil {
			log.Printf("[host] 还有 %d 章待处理：%v", len(updated.PendingRewrites), updated.PendingRewrites)
			saveCheckpoint(store, fmt.Sprintf("ch%02d-commit", result.Chapter))
		}
		return true
	}

	// 确定性判断 1.5：长篇弧/卷边界处理
	if progress != nil && progress.Layered && result.ArcEnd {
		// 判断是否全书最后一弧
		isBookEnd := progress.TotalChapters > 0 && result.NextChapter > progress.TotalChapters

		if result.VolumeEnd {
			log.Printf("[host] 第 %d 卷第 %d 弧结束（卷结束），注入弧级+卷级评审指令", result.Volume, result.Arc)
			if err := store.SetFlow(domain.FlowReviewing); err != nil {
				log.Printf("[host] 设置审阅流程失败: %v", err)
			}
			if emit != nil {
				emit(UIEvent{Time: time.Now(), Category: "SYSTEM",
					Summary: fmt.Sprintf("第 %d 卷第 %d 弧结束（卷结束），触发评审", result.Volume, result.Arc), Level: "warn"})
			}

			tail := "完成后继续写下一卷。"
			if isBookEnd {
				tail = "完成后总结全书并结束。不要再调用 writer。"
			}
			coordinator.FollowUp(agentcore.UserMsg(fmt.Sprintf(
				"[系统] 第 %d 卷第 %d 弧结束（卷结束）。请依次：\n"+
					"1. 调用 editor 进行弧级评审（scope=arc，最新章节为第 %d 章）\n"+
					"2. 调用 editor 生成弧摘要和角色快照（save_arc_summary，volume=%d，arc=%d）\n"+
					"3. 调用 editor 生成卷摘要（save_volume_summary，volume=%d）\n"+
					"%s",
				result.Volume, result.Arc, result.Chapter, result.Volume, result.Arc, result.Volume, tail)))
		} else {
			log.Printf("[host] 第 %d 卷第 %d 弧结束，注入弧级评审指令", result.Volume, result.Arc)
			if err := store.SetFlow(domain.FlowReviewing); err != nil {
				log.Printf("[host] 设置审阅流程失败: %v", err)
			}
			if emit != nil {
				emit(UIEvent{Time: time.Now(), Category: "SYSTEM",
					Summary: fmt.Sprintf("第 %d 卷第 %d 弧结束，触发弧级评审", result.Volume, result.Arc), Level: "warn"})
			}
			coordinator.FollowUp(agentcore.UserMsg(fmt.Sprintf(
				"[系统] 第 %d 卷第 %d 弧结束。请依次：\n"+
					"1. 调用 editor 进行弧级评审（scope=arc，最新章节为第 %d 章）\n"+
					"2. 调用 editor 生成弧摘要和角色快照（save_arc_summary，volume=%d，arc=%d）\n"+
					"完成后继续写下一弧的章节。",
				result.Volume, result.Arc, result.Chapter, result.Volume, result.Arc)))
		}

		if isBookEnd {
			log.Printf("[host] 全书最后一弧，评审完成后将结束")
			if err := store.MarkComplete(); err != nil {
				log.Printf("[host] 标记完成失败: %v", err)
			}
			if emit != nil {
				emit(UIEvent{Time: time.Now(), Category: "SYSTEM",
					Summary: fmt.Sprintf("全部 %d 章已完成，等待最终评审", progress.TotalChapters), Level: "success"})
			}
		}
		clearHandledSteer(store)
		saveCheckpoint(store, fmt.Sprintf("ch%02d-commit", result.Chapter))
		return true
	}

	// 确定性判断 1：全书完成（TotalChapters 由大纲自动设定）
	totalChapters := 0
	if progress != nil {
		totalChapters = progress.TotalChapters
	}
	if totalChapters > 0 && result.NextChapter > totalChapters {
		log.Printf("[host] 所有 %d 章已完成，注入完成指令", totalChapters)
		if err := store.MarkComplete(); err != nil {
			log.Printf("[host] 标记完成失败: %v", err)
		}
		clearHandledSteer(store)
		saveCheckpoint(store, fmt.Sprintf("ch%02d-commit", result.Chapter))
		if emit != nil {
			emit(UIEvent{Time: time.Now(), Category: "SYSTEM", Summary: fmt.Sprintf("全部 %d 章已完成", totalChapters), Level: "success"})
		}
		coordinator.FollowUp(agentcore.UserMsg(fmt.Sprintf(
			"[系统] 全部 %d 章已写完。请总结全书并结束。不要再调用 writer。",
			totalChapters)))
		return true
	}

	// 确定性判断 2：需要全局审阅
	if result.ReviewRequired {
		log.Printf("[host] review_required=true（%s），注入审阅指令", result.ReviewReason)
		if err := store.SetFlow(domain.FlowReviewing); err != nil {
			log.Printf("[host] 设置审阅流程失败: %v", err)
		}
		if emit != nil {
			emit(UIEvent{Time: time.Now(), Category: "SYSTEM", Summary: "review_required=true " + result.ReviewReason, Level: "warn"})
		}
		coordinator.FollowUp(agentcore.UserMsg(fmt.Sprintf(
			"[系统] review_required=true，%s。请调用 editor 对已完成章节进行全局审阅，然后根据审阅结果决定继续写第 %d 章还是修正已有章节。",
			result.ReviewReason, result.NextChapter)))
	}
	clearHandledSteer(store)
	saveCheckpoint(store, fmt.Sprintf("ch%02d-commit", result.Chapter))
	return true
}

// handleUncommittedDraft 在 Writer 结束但没有 commit 时检测是否存在未提交的草稿。
// 如果存在，提醒 Coordinator 重新调用 writer 完成提交。
func handleUncommittedDraft(coordinator *agentcore.Agent, store *state.Store, emit emitFn) {
	progress, _ := store.LoadProgress()
	if progress == nil || progress.Phase == domain.PhaseComplete {
		return
	}
	// 确定下一个应该写的章节
	next := 1
	if progress.InProgressChapter > 0 {
		next = progress.InProgressChapter
	} else if len(progress.CompletedChapters) > 0 {
		next = progress.NextChapter()
	}
	// 检查该章节是否有草稿但未提交
	draft, _ := store.LoadDraft(next)
	if draft == "" {
		return
	}
	// 有草稿但没有 commit 信号
	log.Printf("[host] Writer 结束但第 %d 章草稿未提交", next)
	if emit != nil {
		emit(UIEvent{Time: time.Now(), Category: "SYSTEM",
			Summary: fmt.Sprintf("第 %d 章有草稿但未提交", next), Level: "warn"})
	}
	coordinator.FollowUp(agentcore.UserMsg(fmt.Sprintf(
		"[系统] Writer 结束但第 %d 章草稿未提交。请重新调用 writer 完成该章的自审和提交（commit_chapter）。",
		next)))
}

// handleEditorDone 在 Editor SubAgent 完成后读取审阅信号。
func handleEditorDone(coordinator *agentcore.Agent, store *state.Store, emit emitFn) {
	review, err := store.LoadLastReviewSignal()
	if err != nil {
		log.Printf("[host] 加载审阅信号失败: %v", err)
		return
	}
	if review == nil {
		return
	}
	if err := store.ClearLastReview(); err != nil {
		log.Printf("[host] 清除审阅信号失败: %v", err)
	}

	criticalN := review.CriticalCount()
	log.Printf("[host] 审阅信号：verdict=%s，%d 个问题（critical=%d，error=%d）",
		review.Verdict, len(review.Issues), criticalN, review.ErrorCount())

	// 宿主兜底：如果 LLM 给了 accept 但存在 critical 问题，强制升级为 rewrite
	if review.Verdict == "accept" && criticalN > 0 {
		log.Printf("[host] 检测到 %d 个 critical 问题但 verdict=accept，强制升级为 rewrite", criticalN)
		review.Verdict = "rewrite"
	}

	chaptersInfo := ""
	if len(review.AffectedChapters) > 0 {
		chaptersInfo = fmt.Sprintf("受影响章节：%v。", review.AffectedChapters)
	}

	switch review.Verdict {
	case "rewrite":
		if err := store.SetPendingRewrites(review.AffectedChapters, review.Summary); err != nil {
			log.Printf("[host] 设置重写队列失败: %v", err)
		}
		if err := store.SetFlow(domain.FlowRewriting); err != nil {
			log.Printf("[host] 设置流程状态失败: %v", err)
		}
		if emit != nil {
			emit(UIEvent{Time: time.Now(), Category: "REVIEW",
				Summary: fmt.Sprintf("verdict=rewrite affected=%v", review.AffectedChapters), Level: "warn"})
		}
		coordinator.FollowUp(agentcore.UserMsg(fmt.Sprintf(
			"[系统] Editor 审阅结论：rewrite。%s%s请逐章调用 writer 重写受影响章节，全部完成后继续正常写作。",
			review.Summary, chaptersInfo)))
	case "polish":
		if err := store.SetPendingRewrites(review.AffectedChapters, review.Summary); err != nil {
			log.Printf("[host] 设置打磨队列失败: %v", err)
		}
		if err := store.SetFlow(domain.FlowPolishing); err != nil {
			log.Printf("[host] 设置流程状态失败: %v", err)
		}
		if emit != nil {
			emit(UIEvent{Time: time.Now(), Category: "REVIEW",
				Summary: fmt.Sprintf("verdict=polish affected=%v", review.AffectedChapters), Level: "warn"})
		}
		coordinator.FollowUp(agentcore.UserMsg(fmt.Sprintf(
			"[系统] Editor 审阅结论：polish。%s%s请逐章调用 writer 打磨受影响章节，全部完成后继续正常写作。",
			review.Summary, chaptersInfo)))
	default:
		if err := store.SetFlow(domain.FlowWriting); err != nil {
			log.Printf("[host] 清除审阅状态失败: %v", err)
		}
		if emit != nil {
			emit(UIEvent{Time: time.Now(), Category: "REVIEW", Summary: "verdict=accept 审阅通过", Level: "success"})
		}
	}
	clearHandledSteer(store)
	saveCheckpoint(store, fmt.Sprintf("review-ch%02d-%s", review.Chapter, review.Verdict))
	if emit != nil {
		emit(UIEvent{Time: time.Now(), Category: "CHECK",
			Summary: fmt.Sprintf("saved review-ch%02d-%s", review.Chapter, review.Verdict), Level: "info"})
	}
}

func saveCheckpoint(store *state.Store, label string) {
	if err := store.SaveCheckpoint(label); err != nil {
		log.Printf("[host] 保存检查点失败: %v", err)
	}
}

// parseStreamDelta 从 EventToolExecUpdate 中提取流式 delta 文本。
// 如果事件是 SubAgent 转发的 token delta（含 "delta" 字段），返回文本和 true。
func parseStreamDelta(ev agentcore.Event) (string, bool) {
	if len(ev.Result) == 0 {
		return "", false
	}
	var data struct {
		Delta string `json:"delta"`
	}
	if err := json.Unmarshal(ev.Result, &data); err != nil {
		return "", false
	}
	if data.Delta != "" {
		return data.Delta, true
	}
	return "", false
}

// parseProgressSummary 从 EventToolExecUpdate 中提取可读摘要。
func parseProgressSummary(ev agentcore.Event) string {
	if len(ev.Result) == 0 {
		return "progress"
	}
	var data struct {
		Agent   string `json:"agent"`
		Tool    string `json:"tool"`
		Turn    int    `json:"turn"`
		Error   bool   `json:"error"`
		Message string `json:"message"`
		Thinking string `json:"thinking"`
	}
	if err := json.Unmarshal(ev.Result, &data); err != nil {
		return truncateLog(string(ev.Result), 60)
	}
	// subagent 的 thinking 更新属于高频内部推理，不适合刷到事件流面板。
	if data.Thinking != "" && data.Tool == "" {
		return ""
	}
	if data.Tool != "" {
		if data.Error {
			if data.Message != "" {
				return fmt.Sprintf("%s → %s (error: %s)", data.Agent, data.Tool, truncateLog(data.Message, 120))
			}
			return fmt.Sprintf("%s → %s (error)", data.Agent, data.Tool)
		}
		return fmt.Sprintf("%s → %s", data.Agent, data.Tool)
	}
	if data.Turn > 0 {
		return fmt.Sprintf("%s turn %d", data.Agent, data.Turn)
	}
	return truncateLog(string(ev.Result), 60)
}

// extractLoadingSummary 从 novel_context 的返回 JSON 中提取 _loading_summary 字段。
func extractLoadingSummary(result json.RawMessage) string {
	if len(result) == 0 {
		return ""
	}
	var data struct {
		Summary string `json:"_loading_summary"`
	}
	if err := json.Unmarshal(result, &data); err != nil {
		return ""
	}
	return data.Summary
}

// logSubAgentResult 从 subagent 结果中提取 usage 和 error，分别记录结构化日志。
func logSubAgentResult(result json.RawMessage, emit emitFn) {
	if len(result) == 0 {
		log.Printf("[tool:done] subagent → (empty)")
		return
	}
	var data struct {
		Output string `json:"output"`
		Error  string `json:"error"`
		Usage  struct {
			Input      int     `json:"input"`
			Output     int     `json:"output"`
			CacheRead  int     `json:"cache_read"`
			CacheWrite int     `json:"cache_write"`
			Cost       float64 `json:"cost"`
			Turns      int     `json:"turns"`
			Tools      int     `json:"tools"`
		} `json:"usage"`
	}
	if err := json.Unmarshal(result, &data); err != nil {
		log.Printf("[tool:done] subagent → %s", truncateLog(string(result), 200))
		return
	}

	// 记录 usage
	u := data.Usage
	log.Printf("[usage] input=%d output=%d cache_read=%d turns=%d tools=%d",
		u.Input, u.Output, u.CacheRead, u.Turns, u.Tools)

	if data.Error != "" {
		log.Printf("[subagent:error] %s", data.Error)
		if emit != nil {
			emit(UIEvent{Time: time.Now(), Category: "ERROR",
				Summary: "subagent: " + truncateLog(data.Error, 80), Level: "error"})
		}
		return
	}

	log.Printf("[tool:done] subagent → %s", truncateLog(data.Output, 200))
	if emit != nil {
		emit(UIEvent{Time: time.Now(), Category: "TOOL", Summary: "subagent.done", Level: "info"})
	}
}

func extractToolErrorText(result json.RawMessage) string {
	if len(result) == 0 {
		return ""
	}

	var plain string
	if err := json.Unmarshal(result, &plain); err == nil {
		return plain
	}

	var obj struct {
		Error   string `json:"error"`
		Message string `json:"message"`
		Detail  string `json:"detail"`
	}
	if err := json.Unmarshal(result, &obj); err == nil {
		switch {
		case obj.Error != "":
			return obj.Error
		case obj.Message != "":
			return obj.Message
		case obj.Detail != "":
			return obj.Detail
		}
	}

	return truncateLog(string(result), 160)
}

// agentLabel 将内部 agent 名称映射为用户友好的标签。
func agentLabel(name string) string {
	switch name {
	case "architect_short", "architect_mid", "architect_long":
		return "Architect 规划中"
	case "writer":
		return "Writer 创作中"
	case "editor":
		return "Editor 审阅中"
	default:
		return name
	}
}

func truncateLog(s string, maxRunes int) string {
	runes := []rune(s)
	if len(runes) <= maxRunes {
		return s
	}
	return string(runes[:maxRunes]) + "..."
}

func clearHandledSteer(store *state.Store) {
	if err := store.ClearPendingSteer(); err != nil {
		log.Printf("[host] 清除待处理干预失败: %v", err)
	}
	progress, _ := store.LoadProgress()
	if progress != nil && progress.Flow == domain.FlowSteering {
		if err := store.SetFlow(domain.FlowWriting); err != nil {
			log.Printf("[host] 重置流程状态失败: %v", err)
		}
	}
}

func finalizeSteerIfIdle(store *state.Store) {
	runMeta, _ := store.LoadRunMeta()
	progress, _ := store.LoadProgress()
	if runMeta == nil || runMeta.PendingSteer == "" || progress == nil {
		return
	}
	if progress.Flow != domain.FlowSteering {
		return
	}
	clearHandledSteer(store)
}

// setupFileLogger 设置 CLI 模式日志，同时输出到 stderr 和日志文件。
func setupFileLogger(outputDir string) func() {
	logPath := filepath.Join(outputDir, "meta", "cli.log")
	if err := os.MkdirAll(filepath.Dir(logPath), 0o755); err != nil {
		return nil
	}
	f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
	if err != nil {
		return nil
	}
	log.SetOutput(io.MultiWriter(os.Stderr, f))
	return func() {
		log.SetOutput(os.Stderr)
		_ = f.Close()
	}
}

// createModel 根据 provider 创建对应的 LLM 模型。
func createModel(cfg Config) (agentcore.ChatModel, error) {
	var baseURL []string
	if cfg.BaseURL != "" {
		baseURL = append(baseURL, cfg.BaseURL)
	}
	switch cfg.Provider {
	case "anthropic":
		return llm.NewAnthropicModel(cfg.ModelName, cfg.APIKey, baseURL...)
	case "gemini":
		return llm.NewGeminiModel(cfg.ModelName, cfg.APIKey, baseURL...)
	case "openrouter":
		return newOpenRouterModel(cfg.ModelName, cfg.APIKey, baseURL...)
	default: // openai 及其他 OpenAI 兼容服务
		return llm.NewOpenAIModel(cfg.ModelName, cfg.APIKey, baseURL...)
	}
}

func newOpenRouterModel(model, apiKey string, baseURL ...string) (agentcore.ChatModel, error) {
	cfg := litellm.ProviderConfig{APIKey: apiKey}
	if len(baseURL) > 0 {
		cfg.BaseURL = baseURL[0]
	}
	client, err := litellm.NewWithProvider("openrouter", cfg)
	if err != nil {
		return nil, fmt.Errorf("openrouter: %w", err)
	}
	return llm.NewLiteLLMAdapter(model, client), nil
}

// cliAskUserHandler 是 CLI 模式下的交互式选择器，上下键选择，回车确认。
func cliAskUserHandler(_ context.Context, questions []tools.Question) (*tools.AskUserResponse, error) {
	resp := &tools.AskUserResponse{
		Answers: make(map[string]string),
		Notes:   make(map[string]string),
	}
	for _, q := range questions {
		m := newSelectModel(q)
		p := tea.NewProgram(m, tea.WithOutput(os.Stderr))
		final, err := p.Run()
		if err != nil {
			return resp, err
		}
		result := final.(selectModel)
		if result.cancelled {
			continue
		}
		resp.Answers[q.Question] = result.answer
		if result.isCustom {
			resp.Notes[q.Question] = result.answer
		}
	}
	return resp, nil
}

// ---------- 交互式选择器（bubbletea mini program）----------

var (
	selectCursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("212"))
	selectDescStyle   = lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
	selectHeaderStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("99"))
	selectInputStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("212"))
)

type selectModel struct {
	question  tools.Question
	items     []string // label 列表，最后一项是"自由输入"
	descs     []string // 描述列表
	cursor    int
	answer    string
	isCustom  bool
	cancelled bool
	typing    bool   // 是否进入自由输入模式
	input     string // 自由输入缓冲
}

func newSelectModel(q tools.Question) selectModel {
	items := make([]string, 0, len(q.Options)+1)
	descs := make([]string, 0, len(q.Options)+1)
	for _, opt := range q.Options {
		items = append(items, opt.Label)
		descs = append(descs, opt.Description)
	}
	items = append(items, "自由输入")
	descs = append(descs, "以上都不合适，我自己写")
	return selectModel{question: q, items: items, descs: descs}
}

func (m selectModel) Init() tea.Cmd { return nil }

func (m selectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	if m.typing {
		return m.updateTyping(msg)
	}
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "up", "k":
			if m.cursor > 0 {
				m.cursor--
			}
		case "down", "j":
			if m.cursor < len(m.items)-1 {
				m.cursor++
			}
		case "enter":
			if m.cursor == len(m.items)-1 {
				m.typing = true
				return m, nil
			}
			m.answer = m.items[m.cursor]
			return m, tea.Quit
		case "q", "esc", "ctrl+c":
			m.cancelled = true
			return m, tea.Quit
		}
	}
	return m, nil
}

func (m selectModel) updateTyping(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "enter":
			text := strings.TrimSpace(m.input)
			if text == "" {
				return m, nil
			}
			m.answer = text
			m.isCustom = true
			return m, tea.Quit
		case "esc":
			m.typing = false
			m.input = ""
			return m, nil
		case "ctrl+c":
			m.cancelled = true
			return m, tea.Quit
		case "backspace":
			if len(m.input) > 0 {
				runes := []rune(m.input)
				m.input = string(runes[:len(runes)-1])
			}
		default:
			if msg.Type == tea.KeyRunes {
				m.input += string(msg.Runes)
			} else if msg.Type == tea.KeySpace {
				m.input += " "
			}
		}
	}
	return m, nil
}

func (m selectModel) View() string {
	var b strings.Builder
	b.WriteString(selectHeaderStyle.Render(fmt.Sprintf("[%s] %s", m.question.Header, m.question.Question)))
	b.WriteString("\n\n")

	for i, item := range m.items {
		cursor := "  "
		if i == m.cursor {
			cursor = selectCursorStyle.Render("❯ ")
		}
		label := item
		if i == m.cursor {
			label = selectCursorStyle.Render(item)
		}
		desc := selectDescStyle.Render(" " + m.descs[i])
		b.WriteString(fmt.Sprintf("%s%s%s\n", cursor, label, desc))
	}

	if m.typing {
		b.WriteString("\n")
		b.WriteString(selectInputStyle.Render("  ✎ "))
		b.WriteString(m.input)
		b.WriteString(selectCursorStyle.Render("▌"))
		b.WriteString(selectDescStyle.Render("  (Enter 确认, Esc 返回)"))
	} else {
		b.WriteString(selectDescStyle.Render("\n  ↑↓ 选择  Enter 确认  Esc 取消"))
	}

	return b.String()
}
