package domain

import (
	"sort"
	"strings"
)

// regionAliases maps common Taiwan place names (zh) → service area code.
// Exact alias match only — never geo-infer (OP-04).
var regionAliases = map[string]string{
	"台北": "TPE", "臺北": "TPE", "台北市": "TPE", "臺北市": "TPE", "tpe": "TPE",
	"新北": "NWT", "新北市": "NWT", "nwt": "NWT",
	"桃園": "TAO", "桃園市": "TAO", "tao": "TAO",
	"台中": "TXG", "臺中": "TXG", "台中市": "TXG", "臺中市": "TXG", "txg": "TXG",
	"台南": "TNN", "臺南": "TNN", "台南市": "TNN", "臺南市": "TNN", "tnn": "TNN",
	"高雄": "KHH", "高雄市": "KHH", "khh": "KHH",
	"基隆": "KEE", "基隆市": "KEE", "kee": "KEE",
	"新竹市": "HSZ", "hsz": "HSZ",
	"新竹縣": "HSQ", "hsq": "HSQ", "新竹": "HSZ",
	"苗栗": "MIA", "苗栗縣": "MIA", "mia": "MIA",
	"彰化": "CHA", "彰化縣": "CHA", "cha": "CHA",
	"南投": "NAN", "南投縣": "NAN", "nan": "NAN",
	"雲林": "YUN", "雲林縣": "YUN", "yun": "YUN",
	"嘉義市": "CYI", "cyi": "CYI",
	"嘉義縣": "CYQ", "cyq": "CYQ", "嘉義": "CYI",
	"屏東": "PIF", "屏東縣": "PIF", "pif": "PIF",
	"宜蘭": "ILA", "宜蘭縣": "ILA", "ila": "ILA",
	"花蓮": "HUA", "花蓮縣": "HUA", "hua": "HUA",
	"台東": "TTT", "臺東": "TTT", "台東縣": "TTT", "臺東縣": "TTT", "ttt": "TTT",
	"澎湖": "PEN", "澎湖縣": "PEN", "pen": "PEN",
	"金門": "KIN", "金門縣": "KIN", "kin": "KIN",
	"連江": "LIE", "連江縣": "LIE", "馬祖": "LIE", "lie": "LIE",
}

type regionAliasEntry struct {
	runes []rune
	code  string
}

// sortedRegionAliases is built once at startup: longest alias first so 「台北市」
// wins over 「台北」, with the alias itself breaking ties so detection order is
// reproducible instead of following map iteration.
var sortedRegionAliases = buildSortedRegionAliases()

func buildSortedRegionAliases() []regionAliasEntry {
	out := make([]regionAliasEntry, 0, len(regionAliases))
	for alias, code := range regionAliases {
		if alias == "" {
			continue
		}
		out = append(out, regionAliasEntry{runes: []rune(strings.ToLower(alias)), code: code})
	}
	sort.Slice(out, func(i, j int) bool {
		if len(out[i].runes) != len(out[j].runes) {
			return len(out[i].runes) > len(out[j].runes)
		}
		return string(out[i].runes) < string(out[j].runes)
	})
	return out
}

// DetectRegionCodes finds service-area codes mentioned in free text (exact alias only).
func DetectRegionCodes(text string) []string {
	lower := strings.ToLower(text)
	runes := []rune(lower)
	if len(runes) == 0 {
		return nil
	}
	seen := map[string]bool{}
	var out []string
	covered := make([]bool, len(runes))
	for _, p := range sortedRegionAliases {
		ar := p.runes
		if len(ar) > len(runes) {
			continue
		}
		for i := 0; i+len(ar) <= len(runes); i++ {
			ok := true
			for k := 0; k < len(ar); k++ {
				if runes[i+k] != ar[k] || covered[i+k] {
					ok = false
					break
				}
			}
			if !ok {
				continue
			}
			for k := 0; k < len(ar); k++ {
				covered[i+k] = true
			}
			if !seen[p.code] {
				seen[p.code] = true
				out = append(out, p.code)
			}
		}
	}
	// also direct code tokens；map 迭代無序，排序後再併入以維持輸出穩定
	direct := make([]string, 0)
	for code := range serviceAreaCodes {
		if strings.Contains(lower, strings.ToLower(code)) && !seen[code] {
			seen[code] = true
			direct = append(direct, code)
		}
	}
	sort.Strings(direct)
	return append(out, direct...)
}

// MatchRegion compares detected codes against the owner's service areas.
// unknown when nothing detected; mismatch when only non-overlapping codes; match on any overlap.
func MatchRegion(detected, serviceAreas []string, remoteOK bool) (regionMatch string, score int) {
	if len(detected) == 0 {
		// partial credit for unknown (spec §3.3)
		return RegionUnknown, WeightRegion / 2
	}
	if len(serviceAreas) == 0 {
		return RegionUnknown, WeightRegion / 2
	}
	svc := map[string]bool{}
	for _, a := range serviceAreas {
		svc[strings.ToUpper(strings.TrimSpace(a))] = true
	}
	for _, d := range detected {
		if svc[strings.ToUpper(strings.TrimSpace(d))] {
			return RegionMatch, WeightRegion
		}
	}
	if remoteOK {
		// OP-05: remote_ok → mismatch not hard-reject; full region points
		return RegionMismatch, WeightRegion
	}
	return RegionMismatch, 0
}
