package knowledge

import (
	"strings"
	"unicode/utf8"
)

const (
	minPatrolTagRunes = 4
	maxPatrolTagRunes = 16
	maxRelevanceTags  = 1
	maxRecencyTags    = 1
)

func DeriveSearchTagsFromGraph(graph *Graph, in PatrolTagInput) {
	if graph == nil {
		return
	}
	for i := range graph.Nodes {
		graph.Nodes[i].DerivedTags = derivePatrolTagsForNode(graph.Nodes[i], in)
	}
	graph.PainTagCount = CountPainTagCandidates(graph.Nodes)
}

// CollectPatrolTags returns精選海巡搜尋句（研究地圖 + 產品 + 高相關節點）。
func CollectPatrolTags(in PatrolTagInput) []string {
	return CollectPatrolTagsFromGraph(in, nil)
}

// DerivePatrolTagsForNode resolves a single node's patrol tags.
func DerivePatrolTagsForNode(node Node, in PatrolTagInput) DerivedTags {
	return derivePatrolTagsForNode(node, in)
}

func derivePatrolTagsForNode(node Node, in PatrolTagInput) DerivedTags {
	presetRel := sanitizePatrolTags(node.PatrolRelevance)
	presetRec := sanitizePatrolTags(node.PatrolRecency)
	if len(presetRel) > 0 || len(presetRec) > 0 {
		return DerivedTags{
			Relevance: capTags(presetRel, maxRelevanceTags),
			Recency:   capTags(presetRec, maxRecencyTags),
		}
	}
	if in.HasResearchMap() || in.HasProductContext() {
		derived := deriveFromResearchMapAndProduct(node, in)
		if len(derived.Relevance) > 0 || len(derived.Recency) > 0 {
			return derived
		}
	}
	return deriveFallbackPatrolTags(node)
}

func deriveFromResearchMapAndProduct(node Node, in PatrolTagInput) DerivedTags {
	candidates := []PatrolTagCandidate{}
	matches := isCoreNode(node)

	for i, q := range in.Questions {
		if matches || nodeMatchesResearchText(node, q) {
			addPatrolCandidate(&candidates, PatrolTagFromQuestion(q), scoreQuestion(i)+10, "受眾提問", patrolIntentRelevance)
			addPatrolCandidate(&candidates, patrolTagFromSource(q, patrolIntentRecency), scoreQuestion(i)-2, "受眾提問", patrolIntentRecency)
		}
	}
	for i, pillar := range in.Pillars {
		if matches || nodeMatchesResearchText(node, pillar) {
			addPatrolCandidate(&candidates, PatrolTagFromPillar(pillar), scorePillar(i)+4, "內容支柱", patrolIntentRelevance)
		}
	}
	for i, matchTag := range in.MatchTags {
		if matches || nodeMatchesResearchText(node, matchTag) {
			addPatrolCandidate(&candidates, patrolTagFromSource(matchTag, patrolIntentRelevance), scoreMatchTag(i), "產品匹配", patrolIntentRelevance)
		}
	}
	if in.ProductName != "" && (matches || nodeMatchesResearchText(node, in.ProductName)) {
		addPatrolCandidate(&candidates, patrolTagFromSource(in.ProductName, patrolIntentRelevance), scoreProduct(), "置入產品", patrolIntentRelevance)
	}

	if len(candidates) == 0 && IsPainNode(node) {
		for i, q := range in.Questions {
			if i >= 2 {
				break
			}
			addPatrolCandidate(&candidates, PatrolTagFromQuestion(q), scoreQuestion(i)+8, "受眾提問", patrolIntentRelevance)
		}
	}
	if len(candidates) == 0 && IsPainNode(node) {
		addPatrolCandidate(&candidates, patrolTagFromSource(in.ProductName, patrolIntentRelevance), scoreProduct(), "置入產品", patrolIntentRelevance)
		for i, matchTag := range in.MatchTags {
			if i >= 1 {
				break
			}
			addPatrolCandidate(&candidates, patrolTagFromSource(matchTag, patrolIntentRelevance), scoreMatchTag(i), "產品匹配", patrolIntentRelevance)
		}
	}

	return selectBestDerivedTags(candidates, maxRelevanceTags, maxRecencyTags)
}

func patrolTagFromSource(raw, intent string) string {
	tag := humanizePatrolPhrase(raw, intent)
	if tag == "" || isMechanicalTag(tag) {
		return ""
	}
	return tag
}

func isCoreNode(node Node) bool {
	return node.Layer == 0 || strings.EqualFold(strings.TrimSpace(node.Type), "core")
}

func nodeMatchesResearchText(node Node, text string) bool {
	label := strings.TrimSpace(node.Label)
	text = strings.TrimSpace(text)
	if label == "" || text == "" {
		return false
	}
	if label == text || strings.Contains(text, label) || strings.Contains(label, text) {
		return true
	}
	return sharesMeaningfulSubstring(label, text)
}

func deriveFallbackPatrolTags(node Node) DerivedTags {
	label := strings.TrimSpace(node.Label)
	if label == "" || !IsPainNode(node) {
		return DerivedTags{}
	}
	relevance := []string{patrolTagFromSource(label, patrolIntentRelevance)}
	recency := []string{patrolTagFromSource(label, patrolIntentRecency)}
	return DerivedTags{
		Relevance: capTags(uniqueTags(relevance), maxRelevanceTags),
		Recency:   capTags(uniqueTags(recency), maxRecencyTags),
	}
}

func capTags(items []string, max int) []string {
	if max <= 0 || len(items) <= max {
		return items
	}
	return items[:max]
}

func sharesMeaningfulSubstring(a, b string) bool {
	ar := []rune(a)
	br := []rune(b)
	if len(ar) < 2 || len(br) < 2 {
		return false
	}
	for size := minInt(len(ar), len(br), 4); size >= 2; size-- {
		for i := 0; i+size <= len(ar); i++ {
			chunk := string(ar[i : i+size])
			if strings.Contains(b, chunk) {
				return true
			}
		}
	}
	return false
}

func minInt(values ...int) int {
	if len(values) == 0 {
		return 0
	}
	min := values[0]
	for _, v := range values[1:] {
		if v < min {
			min = v
		}
	}
	return min
}

// NormalizePatrolKeywordList keeps user-edited patrol keywords as typed (trim + dedupe only).
func NormalizePatrolKeywordList(items []string) []string {
	out := make([]string, 0, len(items))
	seen := make(map[string]struct{}, len(items))
	for _, item := range items {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		runes := utf8.RuneCountInString(item)
		if runes < 2 || runes > maxPatrolTagRunes {
			continue
		}
		if _, ok := seen[item]; ok {
			continue
		}
		seen[item] = struct{}{}
		out = append(out, item)
		if len(out) >= MaxTopPatrolTags {
			break
		}
	}
	return out
}

// SanitizePatrolKeywordList normalizes auto-derived海巡 tag（圖譜 / AI 產生）。
func SanitizePatrolKeywordList(items []string) []string {
	out := sanitizePatrolTags(items)
	if len(out) > MaxTopPatrolTags {
		return out[:MaxTopPatrolTags]
	}
	return out
}

func sanitizePatrolTags(items []string) []string {
	out := make([]string, 0, len(items))
	for _, item := range items {
		if tag := sanitizePatrolTag(item); tag != "" {
			out = append(out, tag)
		}
	}
	return uniqueTags(out)
}

func sanitizePatrolTag(text string) string {
	text = humanizePatrolPhrase(text, patrolIntentRelevance)
	if text == "" {
		return ""
	}
	if isMechanicalTag(text) {
		return ""
	}
	return text
}

func isMechanicalTag(text string) bool {
	if strings.Contains(text, " ") {
		return false
	}
	return utf8.RuneCountInString(text) <= 4
}

func uniqueTags(items []string) []string {
	seen := map[string]struct{}{}
	out := make([]string, 0, len(items))
	for _, item := range items {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		if _, ok := seen[item]; ok {
			continue
		}
		seen[item] = struct{}{}
		out = append(out, item)
	}
	return out
}
