package viral import ( "sort" "strings" missionentity "haixun-backend/internal/model/copy_mission/domain/entity" ) // MergeSimilarAccounts merges previous and newly discovered similar accounts // keyed by lowercase username. Newly discovered accounts overwrite per-account // statistics, but pinned/excluded status and previously seen accounts are // preserved. Pinned accounts stay at the top even when not refreshed. func MergeSimilarAccounts(prev, next []missionentity.SimilarAccount) []missionentity.SimilarAccount { if len(prev) == 0 { return capSimilarAccounts(append([]missionentity.SimilarAccount(nil), next...)) } if len(next) == 0 { return prioritizePinnedSimilarAccounts(append([]missionentity.SimilarAccount(nil), prev...)) } byUser := map[string]missionentity.SimilarAccount{} newOrder := make([]string, 0, len(next)) for _, item := range next { key := strings.ToLower(strings.TrimSpace(item.Username)) if key == "" { continue } if _, ok := byUser[key]; !ok { newOrder = append(newOrder, key) } byUser[key] = mergeAccountRecord(byUser[key], item) } prevOrder := make([]string, 0, len(prev)) for _, item := range prev { key := strings.ToLower(strings.TrimSpace(item.Username)) if key == "" { continue } if _, ok := byUser[key]; ok { byUser[key] = mergeAccountRecord(item, byUser[key]) continue } byUser[key] = item prevOrder = append(prevOrder, key) } out := make([]missionentity.SimilarAccount, 0, len(newOrder)+len(prevOrder)) for _, key := range newOrder { if acc, ok := byUser[key]; ok { out = append(out, acc) } } for _, key := range prevOrder { if acc, ok := byUser[key]; ok { out = append(out, acc) } } return prioritizePinnedSimilarAccounts(out) } func mergeAccountRecord(prev, next missionentity.SimilarAccount) missionentity.SimilarAccount { out := next out.MatchedSource = unionMatchedSource(prev.MatchedSource, next.MatchedSource) switch prev.Status { case missionentity.SimilarAccountStatusPinned, missionentity.SimilarAccountStatusExcluded: out.Status = prev.Status default: if strings.TrimSpace(out.Status) == "" { out.Status = missionentity.SimilarAccountStatusRecommended } } return out } func prioritizePinnedSimilarAccounts(accounts []missionentity.SimilarAccount) []missionentity.SimilarAccount { if len(accounts) == 0 { return nil } pinned := make([]missionentity.SimilarAccount, 0) rest := make([]missionentity.SimilarAccount, 0, len(accounts)) for _, item := range accounts { if item.Status == missionentity.SimilarAccountStatusPinned { pinned = append(pinned, item) } else { rest = append(rest, item) } } return capSimilarAccounts(append(pinned, rest...)) } func capSimilarAccounts(accounts []missionentity.SimilarAccount) []missionentity.SimilarAccount { if len(accounts) <= MaxSimilarAccounts { return accounts } pinned := make([]missionentity.SimilarAccount, 0) rest := make([]missionentity.SimilarAccount, 0, len(accounts)) for _, item := range accounts { if item.Status == missionentity.SimilarAccountStatusPinned { pinned = append(pinned, item) } else { rest = append(rest, item) } } remaining := MaxSimilarAccounts - len(pinned) if remaining < 0 { return pinned[:MaxSimilarAccounts] } if len(rest) > remaining { rest = rest[:remaining] } return append(pinned, rest...) } func unionMatchedSource(a, b []string) []string { if len(a) == 0 && len(b) == 0 { return nil } seen := map[string]struct{}{} out := make([]string, 0, len(a)+len(b)) for _, item := range append(append([]string{}, a...), b...) { s := strings.TrimSpace(item) if s == "" { continue } if _, ok := seen[s]; ok { continue } seen[s] = struct{}{} out = append(out, s) } sort.Strings(out) if len(out) == 0 { return nil } return out } func ExcludedSimilarAccountUsernames(accounts []missionentity.SimilarAccount) []string { out := make([]string, 0) seen := map[string]struct{}{} for _, item := range accounts { if item.Status != missionentity.SimilarAccountStatusExcluded { continue } user := strings.TrimSpace(item.Username) if user == "" { continue } key := strings.ToLower(user) if _, ok := seen[key]; ok { continue } seen[key] = struct{}{} out = append(out, user) } return out } func SimilarAccountUsernames(accounts []missionentity.SimilarAccount) []string { out := make([]string, 0, len(accounts)) seen := map[string]struct{}{} for _, item := range accounts { user := strings.TrimSpace(item.Username) if user == "" { continue } key := strings.ToLower(user) if _, ok := seen[key]; ok { continue } seen[key] = struct{}{} out = append(out, user) } return out } func isExcluded(excluded []string, username string) bool { user := strings.ToLower(strings.TrimSpace(username)) if user == "" { return false } for _, item := range excluded { if strings.ToLower(strings.TrimSpace(item)) == user { return true } } return false }