package usecase

import (
	"encoding/json"
	"strings"
	"unicode"
	"unicode/utf8"

	radarDomain "apps/backend/internal/module/radar/domain"
)

// 話題（activity）口語錨點：與 Threads 上常見「可跟／可討論」貼文語感對齊。
// 每詞 2–4 字，方便與主題核組 2-token 短查詢。
var activityAnchors = []string{
	"求推薦", "推薦", "分享", "心得", "活動", "怎麼辦", "詢問", "討論",
}

// 搜尋修飾詞不算主題核；它們用來要求 provider 偏向近期／熱門結果，
// 但正文只要真的提到使用者的主題即可，不要求作者自己寫出「熱門」。
var activityDiscoveryModifiers = []string{"熱門", "最新", "近期"}

var activityWorkMarkers = []string{
	"外包", "接案", "案件", "工作", "職缺", "徵人", "求職", "freelance", "contract",
}

var activityRoleSpecialties = []string{
	"後端", "前端", "全端", "網站", "網頁", "app", "設計", "剪輯", "行銷", "文案",
}

// 台灣常見地名（作前綴 token，2–3 字）
var activityRegions = []string{
	"台北", "新北", "桃園", "台中", "台南", "高雄", "新竹", "基隆",
	"嘉義", "宜蘭", "花蓮", "台東", "屏東", "彰化", "雲林", "南投",
	"苗栗", "金門", "澎湖", "板橋", "中和", "三重", "淡水", "竹北",
}

/*
planActivityTerms 依使用者意圖**產生**可搜的短詞變體，不是只把原文拆開。

策略：
 1. 抽出主題核（2–4 字 CJK 或 2+ 英數）與地區
 2. 核本身 + 核×口語錨點 + 地區×核
 3. 一律過 Threads 短詞規則（與商機 suggest/explore 同一契約）
*/
func planActivityTerms(intent string) []string {
	intent = radarDomain.NormalizeSearchTerm(intent)
	if intent == "" {
		return nil
	}

	cores := extractTopicCores(intent)
	regions := extractRegions(intent)

	var candidates []string
	// 原句若已合規，保留（使用者自己打的短詞）
	if radarDomain.IsThreadsSearchable(intent) {
		candidates = append(candidates, intent)
	}

	// 先放「整體意圖」查詢，再放單核變體。舊邏輯逐核塞滿 8 組，三詞輸入
	// 會只剩第一詞（例：外包），後面的「工程師／後端」完全遺失。
	candidates = append(candidates, inferActivityIntentTerms(intent, cores)...)
	candidates = append(candidates, titleActivityTerms(intent)...)

	// 先 round-robin 放每個主題核，確保每個概念至少保留一次。
	for _, core := range cores {
		if radarDomain.IsThreadsSearchable(core) {
			candidates = append(candidates, core)
		}
	}

	// 近期／熱門是可選搜尋修飾；先於一般口語變體，讓作品、人物、事件詞
	// 能直接形成「鬼滅之刃 熱門」這類 discovery query。
	for _, modifier := range activityDiscoveryModifiers {
		for _, core := range cores {
			candidates = appendSearchable(candidates, core+" "+modifier)
		}
	}

	// 再按錨點 round-robin 擴充，避免第一個 core 的變體吃完整個上限。
	for _, anchor := range activityAnchors {
		for _, core := range cores {
			pair := core + " " + anchor
			if radarDomain.IsThreadsSearchable(pair) {
				candidates = append(candidates, pair)
			}
		}
	}

	for _, core := range cores {
		for _, region := range regions {
			pair := region + " " + core
			if radarDomain.IsThreadsSearchable(pair) {
				candidates = append(candidates, pair)
			}
		}
	}

	// 地區 + 錨點（例：台北 活動）— 主題核不足時的備援
	if len(cores) == 0 {
		for _, region := range regions {
			for _, anchor := range []string{"活動", "推薦", "分享"} {
				pair := region + " " + anchor
				if radarDomain.IsThreadsSearchable(pair) {
					candidates = append(candidates, pair)
				}
			}
		}
	}

	return capTerms(dedupeTerms(candidates), 8)
}

// inferActivityIntentTerms 把使用者的「幾個概念」組回真正想找的 query。
// activity 頁仍是話題探索，但工作／推薦類輸入也應找得到相關討論與公開需求。
func inferActivityIntentTerms(intent string, cores []string) []string {
	if !containsAnyFold(intent, activityWorkMarkers) {
		return adjacentCorePairs(cores)
	}

	var specialties []string
	for _, specialty := range activityRoleSpecialties {
		if strings.Contains(strings.ToLower(intent), specialty) {
			specialties = append(specialties, specialty)
		}
	}
	if len(specialties) == 0 {
		for _, core := range cores {
			if !containsAnyFold(core, activityWorkMarkers) && core != "工程師" {
				specialties = append(specialties, core)
			}
		}
	}

	var out []string
	for _, specialty := range specialties {
		out = appendSearchable(out,
			specialty+" 外包",
			specialty+" 接案",
			specialty+" 工程師",
			specialty+" 推薦",
		)
	}
	out = appendSearchable(out, "外包 案件", "工程師 外包")
	if containsAnyFold(intent, []string{"後端"}) {
		out = appendSearchable(out, "徵人 後端", "尋找 後端")
	}
	return dedupeTerms(out, adjacentCorePairs(cores))
}

func adjacentCorePairs(cores []string) []string {
	var out []string
	for i := 0; i+1 < len(cores); i++ {
		out = appendSearchable(out, cores[i]+" "+cores[i+1])
	}
	return out
}

func titleActivityTerms(intent string) []string {
	parts := splitTitleCores(intent)
	if len(parts) != 2 {
		return nil
	}
	var out []string
	out = appendSearchable(out, parts[0]+" "+parts[1], parts[0])
	for _, modifier := range activityDiscoveryModifiers {
		out = appendSearchable(out, parts[0]+" "+modifier)
	}
	return out
}

// splitTitleCores 保留 5+ 字作品／專名的連接語意，例如：
// 「進擊的巨人」→「進擊 的巨人」。四字內專名則保留原名。
func splitTitleCores(intent string) []string {
	intent = radarDomain.NormalizeSearchTerm(intent)
	if intent == "" || strings.Contains(intent, " ") {
		return nil
	}
	runes := []rune(intent)
	if len(runes) < 5 || len(runes) > 12 {
		return nil
	}
	for i, r := range runes {
		if i < 2 || i >= len(runes)-1 || (r != '之' && r != '的' && r != '與') {
			continue
		}
		left, right := string(runes[:i]), string(runes[i:])
		if radarDomain.IsThreadsSearchable(left) && radarDomain.IsThreadsSearchable(right) &&
			radarDomain.IsThreadsSearchable(left+" "+right) {
			return []string{left, right}
		}
	}
	return nil
}

func appendSearchable(out []string, terms ...string) []string {
	for _, term := range terms {
		if radarDomain.IsThreadsSearchable(term) {
			out = append(out, radarDomain.NormalizeSearchTerm(term))
		}
	}
	return out
}

func containsAnyFold(text string, terms []string) bool {
	text = strings.ToLower(text)
	for _, term := range terms {
		if strings.Contains(text, strings.ToLower(term)) {
			return true
		}
	}
	return false
}

// extractTopicCores 從意圖抽出可當搜尋主詞的 2–4 字核。
func extractTopicCores(intent string) []string {
	intent = radarDomain.NormalizeSearchTerm(intent)
	if intent == "" {
		return nil
	}
	if !strings.Contains(intent, " ") && radarDomain.IsThreadsSearchable(intent) {
		return []string{intent}
	}
	if titleParts := splitTitleCores(intent); len(titleParts) > 0 {
		return titleParts
	}

	var cores []string
	// 已有空白：每段當候選（再壓成 2–4 字）
	if strings.Contains(intent, " ") {
		for _, tok := range strings.Fields(intent) {
			if c := compactCore(tok); c != "" {
				cores = append(cores, c)
			}
		}
		return dedupeTerms(cores)
	}

	// 連續中文：用標點／空白切完後，對每段取 2–4 字滑窗（優先較長有意義核）
	segments := tokenizeIntent(intent)
	if len(segments) == 0 {
		segments = []string{intent}
	}
	for _, seg := range segments {
		seg = strings.TrimSpace(seg)
		if seg == "" {
			continue
		}
		// 去掉地區再抽核，避免「台北」佔掉唯一核
		stripped := stripRegions(seg)
		if stripped == "" {
			stripped = seg
		}
		if c := compactCore(stripped); c != "" {
			cores = append(cores, c)
		}
		// 滑窗 4→3→2，取不重疊優先
		cores = append(cores, slidingCJKCores(stripped)...)
	}
	return dedupeTerms(cores)
}

func compactCore(tok string) string {
	tok = strings.TrimSpace(tok)
	if tok == "" {
		return ""
	}
	// 去掉常見口語虛詞尾巴
	for _, filler := range []string{
		"怎麼辦", "求推薦", "有沒有人", "有人知道", "請問一下", "請問",
		"想問", "想找", "有沒有", "可以嗎", "好不好",
	} {
		tok = strings.ReplaceAll(tok, filler, "")
	}
	tok = strings.Trim(tok, " ,，、。！？!?：:的了嗎呢啊喔唷")
	if tok == "" {
		return ""
	}
	n := utf8.RuneCountInString(tok)
	// 單 token 2–4 CJK 或 2–12 英數才當核
	if radarDomain.IsThreadsSearchable(tok) {
		return radarDomain.NormalizeSearchTerm(tok)
	}
	// 過長：取前 4 字當核
	if n > 4 {
		r := []rune(tok)
		cand := string(r[:4])
		if radarDomain.IsThreadsSearchable(cand) {
			return cand
		}
		cand = string(r[:3])
		if radarDomain.IsThreadsSearchable(cand) {
			return cand
		}
		cand = string(r[:2])
		if radarDomain.IsThreadsSearchable(cand) {
			return cand
		}
	}
	return ""
}

func slidingCJKCores(seg string) []string {
	runes := []rune(seg)
	// 只保留 CJK／字母數字
	var cleaned []rune
	for _, r := range runes {
		if unicode.Is(unicode.Han, r) || unicode.IsLetter(r) || unicode.IsDigit(r) {
			cleaned = append(cleaned, r)
		}
	}
	if len(cleaned) < 2 {
		return nil
	}
	var out []string
	// 優先較長窗
	for size := 4; size >= 2; size-- {
		if len(cleaned) < size {
			continue
		}
		for i := 0; i+size <= len(cleaned); i++ {
			cand := string(cleaned[i : i+size])
			if radarDomain.IsThreadsSearchable(cand) {
				out = append(out, cand)
			}
			// 限制數量，避免長句爆炸
			if len(out) >= 6 {
				return dedupeTerms(out)
			}
		}
	}
	return dedupeTerms(out)
}

func extractRegions(intent string) []string {
	var out []string
	for _, r := range activityRegions {
		if strings.Contains(intent, r) {
			out = append(out, r)
		}
	}
	return out
}

func stripRegions(s string) string {
	for _, r := range activityRegions {
		s = strings.ReplaceAll(s, r, "")
	}
	return strings.TrimSpace(s)
}

// parseActivityAITerms 解析模型回傳的 JSON 字串陣列或 {term} 物件陣列。
func parseActivityAITerms(raw string) []string {
	start := strings.Index(raw, "[")
	end := strings.LastIndex(raw, "]")
	if start < 0 || end <= start {
		return nil
	}
	chunk := raw[start : end+1]

	var asStrings []string
	if err := json.Unmarshal([]byte(chunk), &asStrings); err == nil {
		return asStrings
	}
	var asObjs []struct {
		Term string `json:"term"`
	}
	if err := json.Unmarshal([]byte(chunk), &asObjs); err == nil {
		out := make([]string, 0, len(asObjs))
		for _, o := range asObjs {
			if t := strings.TrimSpace(o.Term); t != "" {
				out = append(out, t)
			}
		}
		return out
	}
	return nil
}

func filterThreadsSearchableTerms(in []string) []string {
	var out []string
	for _, t := range in {
		t = radarDomain.NormalizeSearchTerm(t)
		if radarDomain.IsThreadsSearchable(t) {
			out = append(out, t)
		}
	}
	return dedupeTerms(out)
}

// mergeActivityTerms 先保留可解釋、可重現的使用者意圖查詢，再由 AI 補變體。
// AI 不得把主查詢換成另一個意思。
func mergeActivityTerms(aiTerms, ruleTerms []string, max int) []string {
	ai := filterThreadsSearchableTerms(aiTerms)
	rules := filterThreadsSearchableTerms(ruleTerms)
	return capTerms(dedupeTerms(rules, ai), max)
}

func activityTermsPrompt(intent string, limit int) string {
	var b strings.Builder
	b.WriteString("你是台灣 Threads 搜尋意圖助理。使用者想找與輸入真正相關、近期且有討論動能的貼文。\n")
	b.WriteString("主題意圖：")
	b.WriteString(intent)
	b.WriteString("\n\n")
	b.WriteString("請產出搜尋關鍵字，幫助在 Threads 找到正在討論這個主題的貼文。\n")
	b.WriteString("只輸出 JSON 字串陣列，不要其他文字。格式：\n")
	b.WriteString(`["關鍵字1","關鍵字2"]`)
	b.WriteString("\n規則：\n")
	b.WriteString("1. 最多 ")
	b.WriteString(itoaASCII(limit))
	b.WriteString(" 組。\n")
	b.WriteString("2. 【Threads 短詞硬約束】每組最多 2 個詞（半形空格分隔）；中文每詞 2–4 字；整組去掉空格後 ≤12 字；禁止標點、引號、AND/OR、emoji、#。\n")
	b.WriteString("3. 先判斷意圖，不可只拆字：工作型輸入要保留職種＋外包／接案／職缺語意；作品或人物名稱要保留專名，可加熱門／最新。\n")
	b.WriteString("4. 用台灣口語：求推薦、分享、心得、活動、怎麼辦、有人知道、討論。\n")
	b.WriteString("5. 每個意圖給多組短變體（例：「後端 外包」「後端 接案」；「鬼滅之刃」「鬼滅之刃 最新」），不要只拆使用者原句。\n")
	b.WriteString("6. 不要輸出價格、連結、帳號。\n")
	return b.String()
}

func itoaASCII(n int) string {
	if n <= 0 {
		return "0"
	}
	var b [12]byte
	i := len(b)
	for n > 0 {
		i--
		b[i] = byte('0' + n%10)
		n /= 10
	}
	return string(b[i:])
}
