package copymission

import (
	"strings"

	libkg "haixun-backend/internal/library/knowledge"
	libmatrix "haixun-backend/internal/library/matrix"
	missionentity "haixun-backend/internal/model/copy_mission/domain/entity"
)

type MissionContext struct {
	Label       string
	SeedQuery   string
	Brief       string
	ResearchMap missionentity.ResearchMap
}

type PersonaContext struct {
	DisplayName string
	Persona     string
}

func PlanInput(
	mission MissionContext,
	seed string,
	l1Labels []string,
	supplemental bool,
	strategy libkg.ExpandStrategy,
) libkg.PlanInput {
	return libkg.PlanInput{
		Seed:           seed,
		TargetAudience: strings.TrimSpace(mission.ResearchMap.AudienceSummary),
		ProductBrief:   strings.TrimSpace(mission.Brief),
		Pillars:        mission.ResearchMap.Pillars,
		Questions:      mission.ResearchMap.Questions,
		L1Labels:       l1Labels,
		Supplemental:   supplemental,
		Strategy:       strategy,
	}
}

func SynthInput(mission MissionContext, persona PersonaContext, sources []libkg.BraveSource) libkg.SynthInput {
	label := strings.TrimSpace(mission.Label)
	if label == "" {
		label = strings.TrimSpace(mission.SeedQuery)
	}
	return libkg.SynthInput{
		BrandDisplayName:  label,
		TopicName:         label,
		Seed:              strings.TrimSpace(mission.SeedQuery),
		ProductBrief:      strings.TrimSpace(mission.Brief),
		TargetAudience:    strings.TrimSpace(mission.ResearchMap.AudienceSummary),
		Persona:           strings.TrimSpace(persona.Persona),
		ResearchPillars:   mission.ResearchMap.Pillars,
		ResearchQuestions: mission.ResearchMap.Questions,
		Sources:           sources,
	}
}

func PatrolTagInput(mission MissionContext) libkg.PatrolTagInput {
	return libkg.PatrolTagInput{
		Questions: mission.ResearchMap.Questions,
		Pillars:   mission.ResearchMap.Pillars,
	}
}

func FormatNodeKnowledge(node libkg.Node) string {
	label := strings.TrimSpace(node.Label)
	if label == "" {
		return ""
	}
	detail := strings.TrimSpace(node.PlacementValue)
	if detail == "" {
		detail = strings.TrimSpace(node.Relation)
	}
	if detail != "" {
		return label + "：" + detail
	}
	return label
}

// FormatNodeKnowledgeForMatrix includes label, detail, patrol tags, and evidence for copy generation.
func FormatNodeKnowledgeForMatrix(node libkg.Node) string {
	label := strings.TrimSpace(node.Label)
	if label == "" {
		return ""
	}
	var parts []string
	if detail := strings.TrimSpace(node.PlacementValue); detail != "" {
		parts = append(parts, detail)
	} else if detail := strings.TrimSpace(node.Relation); detail != "" {
		parts = append(parts, detail)
	}
	tags := append(append([]string{}, node.DerivedTags.Relevance...), node.DerivedTags.Recency...)
	if len(tags) > 0 {
		parts = append(parts, "標籤："+strings.Join(tags, "、"))
	}
	if len(node.Evidence) > 0 {
		if snippet := strings.TrimSpace(node.Evidence[0].Snippet); snippet != "" {
			parts = append(parts, "參考："+snippet)
		}
	}
	if len(parts) == 0 {
		return label
	}
	return label + "｜" + strings.Join(parts, "；")
}

func KnowledgeItemsFromNodes(nodes []libkg.Node) (all []string, selected []string) {
	seen := map[string]struct{}{}
	for _, node := range nodes {
		line := FormatNodeKnowledge(node)
		if line == "" {
			continue
		}
		key := strings.ToLower(line)
		if _, ok := seen[key]; ok {
			if node.SelectedForScan {
				selected = mergeLine(selected, line)
			}
			continue
		}
		seen[key] = struct{}{}
		all = append(all, line)
		if node.SelectedForScan {
			selected = append(selected, line)
		}
	}
	return all, selected
}

func ResearchItemsFromSources(sources []libkg.BraveSource) []missionentity.ResearchItem {
	items := make([]missionentity.ResearchItem, 0, len(sources))
	for _, src := range sources {
		if strings.TrimSpace(src.URL) == "" && strings.TrimSpace(src.Snippet) == "" {
			continue
		}
		items = append(items, missionentity.ResearchItem{
			Title:   src.Title,
			URL:     src.URL,
			Snippet: src.Snippet,
			Query:   src.Query,
		})
	}
	return items
}

func ApplyDefaultNodeSelection(nodes []libkg.Node) {
	ApplyDefaultNodeSelectionPreserving(nodes, nil)
}

func ApplyDefaultNodeSelectionPreserving(nodes []libkg.Node, preserve map[string]bool) {
	for i := range nodes {
		if preserve != nil {
			if sel, ok := preserve[nodes[i].ID]; ok {
				nodes[i].SelectedForScan = sel
				continue
			}
		}
		kind := strings.TrimSpace(nodes[i].NodeKind)
		nodes[i].SelectedForScan = kind == "pain" ||
			kind == "symptom" ||
			kind == "cause" ||
			nodes[i].ProductFitScore >= 70
	}
}

func mergeLine(list []string, line string) []string {
	for _, item := range list {
		if item == line {
			return list
		}
	}
	return append(list, line)
}

func mergeKnowledgeLines(base, extra []string) []string {
	out := append([]string(nil), base...)
	seen := map[string]struct{}{}
	for _, item := range out {
		seen[strings.ToLower(strings.TrimSpace(item))] = struct{}{}
	}
	for _, item := range extra {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		key := strings.ToLower(item)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, item)
	}
	return out
}

func knowledgeItemsFromNodesForMatrix(nodes []libkg.Node) (all []string, selected []string) {
	seen := map[string]struct{}{}
	for _, node := range nodes {
		line := FormatNodeKnowledgeForMatrix(node)
		if line == "" {
			continue
		}
		key := strings.ToLower(line)
		if _, ok := seen[key]; ok {
			if node.SelectedForScan {
				selected = mergeLine(selected, line)
			}
			continue
		}
		seen[key] = struct{}{}
		all = append(all, line)
		if node.SelectedForScan {
			selected = append(selected, line)
		}
	}
	return all, selected
}

func knowledgeItemsFromSelectedTags(nodes []libkg.Node, tags []string) []string {
	normalized := make([]string, 0, len(tags))
	seen := map[string]struct{}{}
	for _, tag := range tags {
		tag = strings.TrimSpace(tag)
		if tag == "" {
			continue
		}
		key := strings.ToLower(tag)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		normalized = append(normalized, key)
	}
	if len(normalized) == 0 {
		return nil
	}
	out := make([]string, 0)
	lineSeen := map[string]struct{}{}
	for _, node := range nodes {
		if !nodeMatchesTags(node, normalized) {
			continue
		}
		line := FormatNodeKnowledgeForMatrix(node)
		if line == "" {
			continue
		}
		if _, ok := lineSeen[line]; ok {
			continue
		}
		lineSeen[line] = struct{}{}
		out = append(out, line)
	}
	return out
}

func nodeMatchesTags(node libkg.Node, tags []string) bool {
	candidates := []string{node.Label, node.PlacementValue, node.Relation}
	candidates = append(candidates, node.DerivedTags.Relevance...)
	candidates = append(candidates, node.DerivedTags.Recency...)
	for _, raw := range candidates {
		lower := strings.ToLower(strings.TrimSpace(raw))
		if lower == "" {
			continue
		}
		for _, tag := range tags {
			if strings.Contains(lower, tag) {
				return true
			}
		}
	}
	return false
}

// MatrixKnowledgeItems uses graph-selected nodes as the source of truth and only merges
// manual-only selected_knowledge_items that are not graph-derived duplicates.
func MatrixKnowledgeItems(selected, _ []string, graphNodes []libkg.Node, _ []string) []string {
	_, fromGraph := knowledgeItemsFromNodesForMatrix(graphNodes)
	graphKeys := map[string]struct{}{}
	for _, node := range graphNodes {
		line := FormatNodeKnowledge(node)
		if line == "" {
			continue
		}
		graphKeys[strings.ToLower(line)] = struct{}{}
	}
	manualSelected := make([]string, 0, len(selected))
	for _, item := range selected {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		if _, fromGraph := graphKeys[strings.ToLower(item)]; fromGraph {
			continue
		}
		manualSelected = append(manualSelected, item)
	}
	return libmatrix.MergeKnowledgeItems(fromGraph, manualSelected)
}

func manualResearchKnowledge(prev missionentity.ResearchMap, nodes []libkg.Node) (all []string, selected []string) {
	graphKeys := map[string]struct{}{}
	for _, node := range nodes {
		line := FormatNodeKnowledge(node)
		if line == "" {
			continue
		}
		graphKeys[strings.ToLower(line)] = struct{}{}
	}
	selectedKeys := map[string]struct{}{}
	for _, item := range prev.SelectedKnowledgeItems {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		selectedKeys[strings.ToLower(item)] = struct{}{}
	}
	for _, item := range prev.KnowledgeItems {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		lower := strings.ToLower(item)
		if _, fromGraph := graphKeys[lower]; fromGraph {
			continue
		}
		all = append(all, item)
		if _, ok := selectedKeys[lower]; ok {
			selected = append(selected, item)
		}
	}
	return all, selected
}

func graphKnowledgeDeselected(prev missionentity.ResearchMap) map[string]struct{} {
	selectedKeys := map[string]struct{}{}
	for _, item := range prev.SelectedKnowledgeItems {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		selectedKeys[strings.ToLower(item)] = struct{}{}
	}
	deselected := map[string]struct{}{}
	for _, item := range prev.KnowledgeItems {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		if _, ok := selectedKeys[strings.ToLower(item)]; !ok {
			deselected[strings.ToLower(item)] = struct{}{}
		}
	}
	return deselected
}

func BuildResearchMapFromGraph(prev missionentity.ResearchMap, nodes []libkg.Node, sources []libkg.BraveSource) missionentity.ResearchMap {
	allItems, selectedItems := KnowledgeItemsFromNodes(nodes)
	deselected := graphKnowledgeDeselected(prev)
	if len(deselected) > 0 {
		filtered := selectedItems[:0]
		for _, line := range selectedItems {
			if _, skip := deselected[strings.ToLower(line)]; skip {
				continue
			}
			filtered = append(filtered, line)
		}
		selectedItems = filtered
	}
	manualAll, manualSelected := manualResearchKnowledge(prev, nodes)
	allItems = mergeKnowledgeLines(allItems, manualAll)
	selectedItems = mergeKnowledgeLines(selectedItems, manualSelected)
	return missionentity.ResearchMap{
		AudienceSummary:        prev.AudienceSummary,
		ContentGoal:            prev.ContentGoal,
		Questions:              append([]string(nil), prev.Questions...),
		Pillars:                append([]string(nil), prev.Pillars...),
		Exclusions:             append([]string(nil), prev.Exclusions...),
		SuggestedTags:          append([]missionentity.SuggestedTag(nil), prev.SuggestedTags...),
		SimilarAccounts:        append([]missionentity.SimilarAccount(nil), prev.SimilarAccounts...),
		AudienceSamples:        append([]missionentity.AudienceSample(nil), prev.AudienceSamples...),
		BenchmarkNotes:         prev.BenchmarkNotes,
		KnowledgeItems:         allItems,
		SelectedKnowledgeItems: selectedItems,
		ResearchItems:          ResearchItemsFromSources(sources),
	}
}