package viral

import (
	"strings"
	"unicode"

	"haixun-backend/internal/library/placement"
)

type topicMatchTerms struct {
	Anchors []string
	Terms   []string
}

func missionTopicMatchTerms(seed, label string, hints []string) topicMatchTerms {
	seenAnchors := map[string]struct{}{}
	seenTerms := map[string]struct{}{}
	out := topicMatchTerms{}
	addTerm := func(term string, anchor bool) {
		term = normaliseTopicToken(term)
		if term == "" || isGenericTopicToken(term) {
			return
		}
		if _, ok := seenTerms[term]; !ok {
			seenTerms[term] = struct{}{}
			out.Terms = append(out.Terms, term)
		}
		if anchor {
			if _, ok := seenAnchors[term]; !ok {
				seenAnchors[term] = struct{}{}
				out.Anchors = append(out.Anchors, term)
			}
		}
	}
	addSource := func(raw string, anchor bool) {
		raw = strings.TrimSpace(raw)
		if raw == "" {
			return
		}
		compact := compactTopicPhrase(raw)
		addTerm(compact, anchor)
		for _, token := range splitTopicTokens(raw) {
			addTerm(token, anchor)
		}
	}

	addSource(seed, true)
	addSource(label, true)
	anchorHints := len(out.Anchors) == 0
	for _, hint := range hints {
		addSource(hint, anchorHints)
	}
	return out
}

// missionPostMatchesTopic is intentionally stricter than topicTopicHits:
// mission scan candidates must match the post body, not only the query/tag
// that produced the crawl result. This prevents crawler/search drift from
// admitting high-engagement but unrelated posts.
func missionPostMatchesTopic(post placement.ScanCandidate, terms topicMatchTerms) bool {
	text := strings.ToLower(strings.TrimSpace(post.Text))
	if len(terms.Anchors) == 0 && len(terms.Terms) == 0 {
		return text != ""
	}
	if text == "" {
		return false
	}
	for _, term := range terms.Anchors {
		if term != "" && strings.Contains(text, term) {
			return true
		}
	}
	hits := 0
	for _, term := range terms.Terms {
		if term != "" && strings.Contains(text, term) {
			hits++
			if hits >= 2 {
				return true
			}
		}
	}
	return false
}

func splitTopicTokens(raw string) []string {
	return strings.FieldsFunc(raw, func(r rune) bool {
		if unicode.IsSpace(r) || unicode.IsPunct(r) || unicode.IsSymbol(r) {
			return true
		}
		switch r {
		case '，', '。', '、', '：', '；', '！', '？', '「', '」', '『', '』', '（', '）', '【', '】':
			return true
		default:
			return false
		}
	})
}

func compactTopicPhrase(raw string) string {
	var b strings.Builder
	for _, r := range raw {
		if unicode.IsSpace(r) || unicode.IsPunct(r) || unicode.IsSymbol(r) {
			continue
		}
		b.WriteRune(r)
	}
	return b.String()
}

func normaliseTopicToken(raw string) string {
	token := strings.ToLower(strings.TrimSpace(raw))
	if token == "" {
		return ""
	}
	token = strings.Trim(token, "#＃,，.。:：;；!！?？()（）[]【】\"'「」『』")
	if len([]rune(token)) < 2 {
		return ""
	}
	return token
}

func isGenericTopicToken(token string) bool {
	switch token {
	case "分享", "心得", "推薦", "請問", "求助", "問題", "方法", "技巧", "經驗",
		"熱門", "話題", "最近", "大家", "有人", "可以", "如何", "怎麼", "為什麼",
		"品質", "閱讀", "更多", "多閱讀", "老公", "老婆", "男友", "女友":
		return true
	default:
		return false
	}
}
