package interest import ( "context" "github.com/bluesky-social/indigo/atproto/syntax" "atmoquest/internal/profile" ) // Match describes an attendee who shares interests with the viewer. type Match struct { DID syntax.DID DisplayName string AvatarURL string SharedInterests []string Connected bool } // FindMatches returns attendees who share at least one interest with the // viewer, sorted by number of shared interests (descending). It fetches // each attendee's quest profile from their PDS to read interests. func FindMatches( ctx context.Context, viewerInterests []string, attendeeDIDs []string, viewerDID syntax.DID, connSet map[string]bool, pdsLookup func(syntax.DID) string, avatarFn func(pds string, did syntax.DID) (string, string), // returns (displayName, avatarURL) ) []Match { if len(viewerInterests) == 0 { return nil } viewerSet := make(map[string]bool, len(viewerInterests)) for _, i := range viewerInterests { viewerSet[i] = true } var matches []Match for _, didStr := range attendeeDIDs { if didStr == viewerDID.String() { continue } did, err := syntax.ParseDID(didStr) if err != nil { continue } pds := pdsLookup(did) quest, err := profile.FetchQuest(ctx, pds, did) if err != nil || quest == nil || len(quest.Interests) == 0 { continue } var shared []string for _, interest := range quest.Interests { if viewerSet[interest] { shared = append(shared, interest) } } if len(shared) == 0 { continue } displayName, avatarURL := avatarFn(pds, did) matches = append(matches, Match{ DID: did, DisplayName: displayName, AvatarURL: avatarURL, SharedInterests: shared, Connected: connSet[didStr], }) } // Sort by number of shared interests descending (bubble sort — small N). for i := 0; i < len(matches); i++ { for j := i + 1; j < len(matches); j++ { if len(matches[j].SharedInterests) > len(matches[i].SharedInterests) { matches[i], matches[j] = matches[j], matches[i] } } } // Cap at 20 results. if len(matches) > 20 { matches = matches[:20] } return matches }