package room

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// saveWorkspace 保存文件到 workspace 目录，自动版本追踪
func (r *Room) saveWorkspace(filename, content string) {
	dir := filepath.Join(r.Dir, "workspace")
	os.MkdirAll(dir, 0755)
	fpath := filepath.Join(dir, filename)

	// 版本追踪：保存旧版本到数据库
	if r.Store != nil {
		if old, err := os.ReadFile(fpath); err == nil && len(old) > 0 {
			r.Store.InsertFileVersion(r.Config.Name, filename, string(old), "")
		}
	}

	os.WriteFile(fpath, []byte(content), 0644)
	r.emit(Event{Type: EvtWorkspaceFile, Filename: filename, Action: "updated"})
}

func (r *Room) listWorkspaceFiles() []string {
	dir := filepath.Join(r.Dir, "workspace")
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil
	}
	var files []string
	for _, e := range entries {
		if !e.IsDir() && !strings.HasPrefix(e.Name(), ".") {
			files = append(files, e.Name())
		}
	}
	return files
}

// buildWorkspaceContext 读取 workspace 目录下所有文件内容，作为上下文
func (r *Room) buildWorkspaceContext() string {
	wsDir := filepath.Join(r.Dir, "workspace")
	entries, err := os.ReadDir(wsDir)
	if err != nil {
		return ""
	}
	const perFileMax = 4000  // 单文件最大字符数
	const totalMax = 20000   // workspace 上下文总最大字符数
	var sb strings.Builder
	totalChars := 0
	for _, e := range entries {
		if e.IsDir() || strings.HasPrefix(e.Name(), ".") {
			continue
		}
		content, err := os.ReadFile(filepath.Join(wsDir, e.Name()))
		if err != nil || len(content) == 0 {
			continue
		}
		text := string(content)
		runes := []rune(text)
		if len(runes) > perFileMax {
			runes = runes[:perFileMax]
			text = string(runes) + "\n...(截断)"
		}
		if totalChars+len(runes) > totalMax {
			sb.WriteString(fmt.Sprintf("\n--- 📎 %s --- (已省略，超出上下文限制)\n", e.Name()))
			continue
		}
		sb.WriteString(fmt.Sprintf("\n--- 📎 %s ---\n%s\n", e.Name(), text))
		totalChars += len(runes)
	}
	if sb.Len() == 0 {
		return ""
	}
	return r.Prompt.Render("workspace_header", map[string]string{
		"FileList": sb.String(),
	})
}

// syncTodoList 扫描 TodoList.md，自动将已产出文件的任务勾选为 [x]
// 同时修正路径：如果文件名带子目录前缀但实际文件在 workspace 根目录，自动去掉前缀
func (r *Room) syncTodoList() {
	fpath := filepath.Join(r.Dir, "workspace", "TodoList.md")
	data, err := os.ReadFile(fpath)
	if err != nil {
		return
	}

	fileRe := regexp.MustCompile(`《(.+?)》`)
	lines := strings.Split(string(data), "\n")
	changed := false
	for i, line := range lines {
		// 修正路径：所有行（不限 [ ] / [x]）中带子目录前缀的文件名，如果实际文件在根目录则去掉前缀
		for _, m := range fileRe.FindAllStringSubmatch(line, -1) {
			filename := m[1]
			baseName := filepath.Base(filename)
			if baseName != filename {
				fpSub := filepath.Join(r.Dir, "workspace", filename)
				fpFlat := filepath.Join(r.Dir, "workspace", baseName)
				// 子目录下不存在但根目录下存在 → 修正路径
				if _, err := os.Stat(fpSub); os.IsNotExist(err) {
					if _, err := os.Stat(fpFlat); err == nil {
						lines[i] = strings.Replace(lines[i], "《"+filename+"》", "《"+baseName+"》", 1)
						changed = true
					}
				}
			}
		}

		// 勾选：检查未完成任务对应的文件是否已存在
		trimmed := strings.TrimSpace(lines[i])
		if !strings.HasPrefix(trimmed, "- [ ]") {
			continue
		}
		m := fileRe.FindStringSubmatch(lines[i])
		if m == nil {
			continue
		}
		filename := m[1]
		fp := filepath.Join(r.Dir, "workspace", filename)
		if _, err := os.Stat(fp); err == nil {
			lines[i] = strings.Replace(lines[i], "- [ ]", "- [x]", 1)
			changed = true
		}
	}
	if changed {
		os.WriteFile(fpath, []byte(strings.Join(lines, "\n")), 0644)
		r.emit(Event{Type: EvtWorkspaceFile, Filename: "TodoList.md", Action: "updated"})
	}
}

// hasTodoList 检查 workspace 中是否存在 TodoList.md
func (r *Room) hasTodoList() bool {
	fpath := filepath.Join(r.Dir, "workspace", "TodoList.md")
	_, err := os.Stat(fpath)
	return err == nil
}

// hasPendingTodo 检查 TodoList.md 中是否还有未完成的任务（`- [ ]`）
func (r *Room) hasPendingTodo() bool {
	fpath := filepath.Join(r.Dir, "workspace", "TodoList.md")
	data, err := os.ReadFile(fpath)
	if err != nil {
		return false
	}
	return strings.Contains(string(data), "- [ ]")
}

// saveAgentOutput 统一处理成员产出的文件保存路由（旧路径兼容，无模板时使用）。
// 返回 (filename, routed)：routed=true 表示走了文件路由。
func (r *Room) saveAgentOutput(name, finalReply, task string) (string, bool) {
	if r.projectTemplate != nil {
		ownerFiles := r.findOwnerFiles(name)
		if len(ownerFiles) >= 1 {
			var targetFile *ProjectFile
			if len(ownerFiles) == 1 {
				targetFile = &ownerFiles[0]
			} else {
				title := extractTitle(finalReply)
				if title != "" {
					if tf := r.matchTemplateFile(title); tf != nil && tf.Owner == name {
						targetFile = tf
					}
				}
				if targetFile == nil {
					firstLine := strings.TrimSpace(strings.SplitN(finalReply, "\n", 2)[0])
					firstLine = strings.TrimLeft(firstLine, "# ")
					if firstLine != "" {
						for i := range ownerFiles {
							base := strings.TrimSuffix(ownerFiles[i].Path, ".md")
							if strings.Contains(firstLine, base) || strings.Contains(base, firstLine) {
								targetFile = &ownerFiles[i]
								break
							}
						}
					}
				}
				if targetFile == nil {
					for i := range ownerFiles {
						fpath := filepath.Join(r.Dir, "workspace", ownerFiles[i].Path)
						if _, err := os.Stat(fpath); os.IsNotExist(err) {
							targetFile = &ownerFiles[i]
							break
						}
					}
				}
				if targetFile == nil && len([]rune(finalReply)) > 200 {
					targetFile = &ownerFiles[len(ownerFiles)-1]
				}
			}

			if targetFile != nil {
				fileContent, _ := splitContentAndStatus(finalReply, targetFile.Path)
				if !strings.HasPrefix(strings.TrimSpace(fileContent), "# ") {
					docTitle := strings.TrimSuffix(targetFile.Path, ".md")
					fileContent = "# " + docTitle + "\n\n" + fileContent
				}
				r.saveWorkspace(targetFile.Path, fileContent)
				if r.memberArtifacts == nil {
					r.memberArtifacts = make(map[string]string)
				}
				r.memberArtifacts[name] = targetFile.Path
				r.emit(Event{Type: EvtTaskDone, Agent: name, Task: task})
				return targetFile.Path, true
			}
		}
	}

	// 动态章节：检查成员是否为动态文件负责人（如写手负责章节）
	if dynDir, dynOwner, _ := r.getDynamicFileInfo(); dynDir != "" && dynOwner == name && r.allStaticFilesDone() {
		if isDocument(finalReply) {
			chapterFilename := r.extractChapterFilename(finalReply, dynDir)
			content := strings.TrimSpace(finalReply)
			if !strings.HasPrefix(content, "# ") {
				content = "# " + strings.TrimSuffix(chapterFilename, ".md") + "\n\n" + content
			}
			r.saveWorkspace(chapterFilename, content)
			if r.memberArtifacts == nil {
				r.memberArtifacts = make(map[string]string)
			}
			r.memberArtifacts[name] = chapterFilename
			docName := strings.TrimSuffix(chapterFilename, ".md")
			r.emit(Event{Type: EvtArtifact, Agent: name, Filename: chapterFilename, Title: docName})
			r.emit(Event{Type: EvtFileDone, Agent: name, Filename: chapterFilename, Title: docName})
			r.emit(Event{Type: EvtTaskDone, Agent: name, Task: task})
			return chapterFilename, true
		}
	}

	// 降级：原有 isDocument 逻辑
	if isDocument(finalReply) {
		title := extractTitle(finalReply)
		if r.memberArtifacts == nil {
			r.memberArtifacts = make(map[string]string)
		}
		var filename string
		if existing, ok := r.memberArtifacts[name]; ok {
			filename = existing
		} else {
			filename = titleToFilename(title, name)
			r.memberArtifacts[name] = filename
		}
		r.saveWorkspace(filename, finalReply)
		r.emit(Event{Type: EvtArtifact, Agent: name, Filename: filename, Title: title})
		r.emit(Event{Type: EvtTaskDone, Agent: name, Task: task})
		return filename, true
	}

	// 纯交流型输出
	r.emit(Event{Type: EvtAgentMessage, Agent: name, Role: "member", Content: finalReply})
	r.lastActiveMember = name
	r.emit(Event{Type: EvtTaskDone, Agent: name, Task: task})
	return "", false
}

// stripTrailingAssignments 去除文档末尾混入的 @分配 指令段落
// 用于 TodoList 等文档文件，防止 master 将工作流指令写入文件正文
func stripTrailingAssignments(content string) string {
	lines := strings.Split(content, "\n")
	cutAt := -1
	for i, line := range lines {
		trimmed := strings.TrimSpace(line)
		// 分隔线 "---" 后面跟的是工作流指令，截断
		if trimmed == "---" {
			// 检查后续是否有 @ 分配或非任务内容
			rest := strings.Join(lines[i+1:], "\n")
			if strings.Contains(rest, "@") || strings.Contains(rest, "Phase") {
				cutAt = i
				break
			}
		}
		// 独立的 @成员名 开头的行（非任务列表项）
		if strings.HasPrefix(trimmed, "@") && !strings.HasPrefix(trimmed, "@主编") {
			// 检查是否在列表项中（任务列表有 - [ ] 前缀）
			cutAt = i
			break
		}
	}
	if cutAt >= 0 {
		return strings.TrimRight(strings.Join(lines[:cutAt], "\n"), "\n ")
	}
	return content
}

// splitDocuments 从文本中拆分出独立文档段落（旧路径兼容）。
func splitDocuments(text string) []string {
	parts := strings.Split("\n"+text, "\n# ")
	if len(parts) <= 1 {
		return nil
	}
	var docs []string
	for _, part := range parts[1:] {
		doc := "# " + part
		doc = strings.TrimSpace(doc)
		if len([]rune(doc)) > 200 {
			docs = append(docs, doc)
		}
	}
	return docs
}

// isDocument 判断内容是否为文档产出物
func isDocument(content string) bool {
	runeLen := len([]rune(content))
	hasH1 := strings.Contains(content, "\n# ") || strings.HasPrefix(content, "# ")
	if hasH1 && runeLen > 300 {
		return true
	}
	h2Count := strings.Count(content, "\n## ")
	if h2Count >= 2 && runeLen > 200 {
		return true
	}
	if strings.Contains(content, "《") && strings.Contains(content, "》") && runeLen > 300 {
		if strings.Count(content, "\n") > 5 {
			return true
		}
	}
	return false
}

// stripDocuments 从文本中去除文档段落，只保留非文档部分。
func (r *Room) stripDocuments(text string) string {
	docs := splitDocuments(text)
	if len(docs) == 0 {
		return text
	}
	result := text
	for _, doc := range docs {
		result = strings.Replace(result, doc, "", 1)
	}
	for strings.Contains(result, "\n\n\n") {
		result = strings.ReplaceAll(result, "\n\n\n", "\n\n")
	}
	return strings.TrimSpace(result)
}

// splitContentAndStatus 将 agent 输出分为文件内容和状态消息。
func splitContentAndStatus(reply, filename string) (fileContent, statusMsg string) {
	fileContent = reply
	name := strings.TrimSuffix(filename, ".md")
	statusMsg = fmt.Sprintf("已完成《%s》", name)
	return
}

// parseTargetFileFromTask 从任务描述中解析《文件名》，匹配 workspace 已有文件（支持修订已有文件）
func (r *Room) parseTargetFileFromTask(memberName, task string) *ProjectFile {
	re := regexp.MustCompile(`《(.+?)》`)
	matches := re.FindAllStringSubmatch(task, -1)
	for _, m := range matches {
		filename := m[1]
		if !strings.HasSuffix(filename, ".md") {
			filename += ".md"
		}
		fpath := filepath.Join(r.Dir, "workspace", filename)
		if _, err := os.Stat(fpath); err == nil {
			// 文件存在 → 优先匹配模板定义（保留正确的 Owner/Phase）
			if tf := r.matchTemplateFile(strings.TrimSuffix(filename, ".md")); tf != nil {
				// 只有文件负责人才能写入
				if tf.Owner == memberName {
					return tf
				}
				continue
			}
			// 模板中没有 → 构造虚拟 ProjectFile
			return &ProjectFile{
				Path:    filename,
				Owner:   memberName,
				Phase:   2,
				Dynamic: true,
			}
		}
	}
	return nil
}

// applyDocumentEdit 解析并应用文档编辑指令
// 支持两种格式：
// 1. 全文替换：<<<REPLACE filename>>>\n新内容\n<<<END>>>
// 2. 局部替换：<<<EDIT filename>>><<<FIND>>>旧内容<<<REPLACE>>>新内容<<<END>>>
func (r *Room) applyDocumentEdit(content string) (filename string, applied bool) {
	// 格式1: 全文替换（优先处理）
	replaceRe := regexp.MustCompile(`(?s)<<<REPLACE\s+(.+?)>>>\s*\n(.*?)\n?<<<END>>>`)
	replaceMatches := replaceRe.FindAllStringSubmatch(content, -1)
	for _, m := range replaceMatches {
		fname := strings.TrimSpace(m[1])
		newContent := strings.TrimSpace(m[2])
		if newContent == "" {
			continue
		}
		r.saveWorkspace(fname, newContent)
		filename = fname
		applied = true
	}
	if applied {
		return
	}

	// 格式2: 局部替换
	editRe := regexp.MustCompile(`(?s)<<<EDIT\s+(.+?)>>>\s*<<<FIND>>>\s*(.+?)\s*<<<REPLACE>>>\s*(.+?)\s*<<<END>>>`)
	matches := editRe.FindAllStringSubmatch(content, -1)
	if len(matches) == 0 {
		return "", false
	}
	for _, m := range matches {
		fname := strings.TrimSpace(m[1])
		oldText := strings.TrimSpace(m[2])
		newText := strings.TrimSpace(m[3])
		fpath := filepath.Join(r.Dir, "workspace", fname)
		data, err := os.ReadFile(fpath)
		if err != nil {
			continue
		}
		original := string(data)
		if !strings.Contains(original, oldText) {
			continue
		}
		updated := strings.Replace(original, oldText, newText, 1)
		r.saveWorkspace(fname, updated)
		filename = fname
		applied = true
	}
	return
}


