package message

import "strings"

const (
	MentionMetadataKey       = "mentions"
	MaxPersistedMentionHints = 32
)

type PersistedMention struct {
	Type  string
	ID    string
	Token string
}

func MentionHintsWithResolvedLinks(hints []MessageMentionHint, links []ResolvedMentionLink) []MessageMentionHint {
	if len(links) == 0 {
		return hints
	}

	out := make([]MessageMentionHint, 0, min(len(hints)+len(links), MaxPersistedMentionHints))
	seen := make(map[string]struct{}, len(hints)+len(links))
	appendHint := func(hint MessageMentionHint) {
		if len(out) >= MaxPersistedMentionHints {
			return
		}
		mentionType := strings.ToLower(strings.TrimSpace(hint.Type))
		id := strings.TrimSpace(hint.ID)
		token := strings.TrimSpace(hint.Token)
		if mentionType == "" || id == "" || token == "" {
			return
		}
		key := mentionType + "\x00" + id + "\x00" + strings.ToLower(token)
		if _, ok := seen[key]; ok {
			return
		}
		seen[key] = struct{}{}
		hint.Type = mentionType
		hint.ID = id
		hint.Token = token
		out = append(out, hint)
	}

	for _, hint := range hints {
		appendHint(hint)
	}
	for _, link := range links {
		appendHint(MessageMentionHint{
			Type:  link.Type,
			ID:    link.ID,
			Token: link.Token,
		})
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func BuildPersistedMentionMetadata(hints []MessageMentionHint, recipients []AgentRecipient, directTokens []string) []map[string]any {
	return BuildPersistedMentionMetadataWithTeams(hints, recipients, nil, directTokens)
}

func BuildPersistedMentionMetadataWithTeams(hints []MessageMentionHint, recipients []AgentRecipient, teamMentions []PersistedMention, directTokens []string) []map[string]any {
	if (len(recipients) == 0 && len(teamMentions) == 0) || len(directTokens) == 0 {
		return nil
	}

	allowedAgentIDs := make(map[string]struct{}, len(recipients))
	for _, recipient := range recipients {
		agentID := strings.TrimSpace(recipient.AgentID)
		if agentID != "" {
			allowedAgentIDs[agentID] = struct{}{}
		}
	}
	allowedTokens := make([]string, 0, len(directTokens))
	seenAllowedTokens := make(map[string]struct{}, len(directTokens))
	for _, token := range directTokens {
		normalized := strings.ToLower(strings.TrimSpace(token))
		if normalized == "" {
			continue
		}
		if _, ok := seenAllowedTokens[normalized]; ok {
			continue
		}
		seenAllowedTokens[normalized] = struct{}{}
		allowedTokens = append(allowedTokens, normalized)
	}
	if len(allowedTokens) == 0 {
		return nil
	}

	allowedTeamMentions := normalizePersistedTeamMentions(teamMentions, allowedTokens)
	allowedTeamIDs := make(map[string]struct{}, len(allowedTeamMentions))
	for _, mention := range allowedTeamMentions {
		allowedTeamIDs[mention.ID] = struct{}{}
	}
	if len(allowedAgentIDs) == 0 && len(allowedTeamIDs) == 0 {
		return nil
	}

	seen := make(map[string]struct{}, len(hints)+len(recipients)+len(allowedTeamMentions))
	mentions := make([]map[string]any, 0, len(hints)+len(allowedTeamMentions))
	for _, hint := range hints {
		if len(mentions) >= MaxPersistedMentionHints {
			break
		}
		mentionType := strings.ToLower(strings.TrimSpace(hint.Type))
		if mentionType != "agent" && mentionType != "team" {
			continue
		}
		mentionID := strings.TrimSpace(hint.ID)
		switch mentionType {
		case "agent":
			if _, ok := allowedAgentIDs[mentionID]; !ok {
				continue
			}
		case "team":
			if _, ok := allowedTeamIDs[mentionID]; !ok {
				continue
			}
		default:
			continue
		}
		token := strings.ToLower(strings.TrimSpace(hint.Token))
		if !mentionTokenMatchesAny(token, allowedTokens) {
			continue
		}
		key := mentionType + "\x00" + mentionID + "\x00" + token
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		mentions = append(mentions, map[string]any{
			"type":  mentionType,
			"id":    mentionID,
			"token": token,
		})
	}
	for _, mention := range allowedTeamMentions {
		if len(mentions) >= MaxPersistedMentionHints {
			break
		}
		token := strings.ToLower(strings.TrimSpace(mention.Token))
		if token == "" || !mentionTokenMatchesAny(token, allowedTokens) {
			continue
		}
		key := "team" + "\x00" + mention.ID + "\x00" + token
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		mentions = append(mentions, map[string]any{
			"type":  "team",
			"id":    mention.ID,
			"token": token,
		})
	}
	for _, recipient := range recipients {
		if len(mentions) >= MaxPersistedMentionHints {
			break
		}
		agentID := strings.TrimSpace(recipient.AgentID)
		if _, ok := allowedAgentIDs[agentID]; !ok {
			continue
		}
		token := strings.ToLower(strings.TrimSpace(recipient.MentionToken))
		if token == "" || !mentionTokenMatchesAny(token, allowedTokens) {
			continue
		}
		key := "agent" + "\x00" + agentID + "\x00" + token
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		mentions = append(mentions, map[string]any{
			"type":  "agent",
			"id":    agentID,
			"token": token,
		})
	}
	return mentions
}

func normalizePersistedTeamMentions(mentions []PersistedMention, allowedTokens []string) []PersistedMention {
	if len(mentions) == 0 {
		return nil
	}
	normalized := make([]PersistedMention, 0, len(mentions))
	seen := make(map[string]struct{}, len(mentions))
	for _, mention := range mentions {
		mentionType := strings.ToLower(strings.TrimSpace(mention.Type))
		if mentionType != "team" {
			continue
		}
		id := strings.TrimSpace(mention.ID)
		token := strings.ToLower(strings.TrimSpace(mention.Token))
		if id == "" || token == "" || !mentionTokenMatchesAny(token, allowedTokens) {
			continue
		}
		key := id + "\x00" + token
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		normalized = append(normalized, PersistedMention{Type: "team", ID: id, Token: token})
	}
	return normalized
}

func ExtractPersistedMentionMetadata(metadata map[string]any) []PersistedMention {
	if len(metadata) == 0 {
		return nil
	}
	rawMentions, ok := metadata[MentionMetadataKey]
	if !ok {
		return nil
	}

	var values []any
	switch typed := rawMentions.(type) {
	case []any:
		values = typed
	case []map[string]any:
		values = make([]any, 0, len(typed))
		for _, value := range typed {
			values = append(values, value)
		}
	default:
		return nil
	}

	mentions := make([]PersistedMention, 0, len(values))
	seen := make(map[string]struct{}, len(values))
	for _, value := range values {
		record, ok := value.(map[string]any)
		if !ok {
			continue
		}
		mentionType, _ := record["type"].(string)
		mentionID, _ := record["id"].(string)
		token, _ := record["token"].(string)
		mention := PersistedMention{
			Type:  strings.ToLower(strings.TrimSpace(mentionType)),
			ID:    strings.TrimSpace(mentionID),
			Token: strings.ToLower(strings.TrimSpace(token)),
		}
		if (mention.Type != "agent" && mention.Type != "team") || mention.ID == "" || mention.Token == "" {
			continue
		}
		key := mention.Type + "\x00" + mention.ID + "\x00" + mention.Token
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		mentions = append(mentions, mention)
		if len(mentions) >= MaxPersistedMentionHints {
			break
		}
	}
	return mentions
}

func mentionTokenMatchesAny(target string, tokens []string) bool {
	for _, token := range tokens {
		if MentionTokenMatchesTarget(token, target) {
			return true
		}
	}
	return false
}
