package recommendation

import (
	"strings"

	"github.com/jackc/pgx/v5/pgtype"

	"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)

type SessionVector struct {
	Seed           bool           `json:"seed"`
	Artists        []string       `json:"artists"`
	Tags           map[string]int `json:"tags"`
	RecentTrackIDs []string       `json:"recent_track_ids"`
	// DeviceClass is the current request's device (#1551), set by the radio
	// handler from the user's latest play; drives the device dimension of the
	// context-affinity term. Empty (omitted) for the daily mixes and for the
	// snapshot stored on play_events, so it never narrows those.
	DeviceClass string `json:"device_class,omitempty"`
}

func BuildSessionVector(priorTracks []dbq.Track) SessionVector {
	v := SessionVector{
		Seed:           len(priorTracks) < 3,
		Artists:        []string{},
		Tags:           map[string]int{},
		RecentTrackIDs: []string{},
	}
	seen := map[string]bool{}
	for _, t := range priorTracks {
		artistID := uuidString(t.ArtistID)
		if !seen[artistID] {
			seen[artistID] = true
			v.Artists = append(v.Artists, artistID)
		}
		if t.Genre != nil && *t.Genre != "" {
			for _, g := range splitGenres(*t.Genre) {
				v.Tags[g]++
			}
		}
		v.RecentTrackIDs = append(v.RecentTrackIDs, uuidString(t.ID))
	}
	return v
}

func uuidString(u pgtype.UUID) string {
	if !u.Valid {
		return ""
	}
	return u.String()
}

// splitGenres splits a track's denormalized genre string on the common
// multi-genre delimiters (`;`, `,`) used by various tag editors. Trims
// whitespace; drops empty fragments. Strings with no delimiter come back
// as a single-element slice.
//
// This comment used to blame concatenated inputs like
// "ElectronicComplextroGlitch Hop" on broken tag editors. They were ours: the
// scanner stored dhowden/tag's welded multi-value frames verbatim. Fixed in
// #2499 — the scanner now writes ";"-delimited values, so such tokens only
// survive on rows not yet re-scanned.
func splitGenres(s string) []string {
	parts := strings.FieldsFunc(s, func(r rune) bool {
		return r == ';' || r == ','
	})
	out := make([]string, 0, len(parts))
	for _, p := range parts {
		if p = strings.TrimSpace(p); p != "" {
			out = append(out, p)
		}
	}
	return out
}
