// Small helpers for posting live events to the SSE bus (#392). Producers
// call these from their handler success paths so the wire shape stays in
// one place. Each helper is a no-op when h.eventbus is nil (older callers
// or tests that don't construct a bus); production always has one.

package api

import (
	"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
	"git.fabledsword.com/bvandeusen/minstrel/internal/eventbus"
	"github.com/jackc/pgx/v5/pgtype"
)

// publishLikeEvent broadcasts a track/album/artist like or unlike. The
// event is scoped to the user (so only their own clients invalidate); the
// payload includes the entity_type and entity_id so the dispatcher can
// invalidate the right query keys.
func (h *handlers) publishLikeEvent(userID, entityID pgtype.UUID, entityType string, liked bool) {
	if h.eventbus == nil {
		return
	}
	kind := "track.liked"
	if !liked {
		kind = "track.unliked"
	}
	if entityType == "album" {
		if liked {
			kind = "album.liked"
		} else {
			kind = "album.unliked"
		}
	}
	if entityType == "artist" {
		if liked {
			kind = "artist.liked"
		} else {
			kind = "artist.unliked"
		}
	}
	h.eventbus.Publish(eventbus.Event{
		Kind:   kind,
		UserID: uuidToString(userID),
		Data: map[string]any{
			"entity_type": entityType,
			"entity_id":   uuidToString(entityID),
		},
	})
}

// publishQuarantineEvent broadcasts a quarantine action. User-side
// flag/unflag are scoped to the user; admin-side resolve / delete-file /
// delete-via-lidarr are broadcast (empty UserID) because a single admin
// action can affect every user who'd flagged that track — every client
// invalidates and the non-affected ones no-op when the query result
// matches their cache.
func (h *handlers) publishQuarantineEvent(kind string, userID, trackID pgtype.UUID, broadcast bool) {
	if h.eventbus == nil {
		return
	}
	scope := ""
	if !broadcast {
		scope = uuidToString(userID)
	}
	h.eventbus.Publish(eventbus.Event{
		Kind:   kind,
		UserID: scope,
		Data: map[string]any{
			"track_id": uuidToString(trackID),
		},
	})
}

// publishPlaylistEvent broadcasts a playlist mutation to the owner so
// their other clients invalidate the affected playlist (and playlist list)
// providers. Public-playlist subscribers are out of scope here — they'd
// need a separate "playlists.public_updated" broadcast, deferred until
// the multi-user case is exercised.
func (h *handlers) publishPlaylistEvent(kind string, ownerID, playlistID pgtype.UUID) {
	if h.eventbus == nil {
		return
	}
	h.eventbus.Publish(eventbus.Event{
		Kind:   kind,
		UserID: uuidToString(ownerID),
		Data: map[string]any{
			"playlist_id": uuidToString(playlistID),
		},
	})
}

// publishSystemRebuilt notifies the owner's clients that their system
// playlists (and You-might-like rows) were regenerated, so they invalidate
// the home / system-playlist providers and a stale active queue can re-pull.
// Mirrors the daily scheduler's event; fired here from the manual refresh.
func (h *handlers) publishSystemRebuilt(userID pgtype.UUID) {
	if h.eventbus == nil {
		return
	}
	h.eventbus.Publish(eventbus.Event{
		Kind:   "playlist.system_rebuilt",
		UserID: uuidToString(userID),
		Data:   map[string]any{},
	})
}

// publishRequestStatusChanged broadcasts a Lidarr request status flip to
// the request's original requester so their /requests page reflects the
// new state without manual refresh. Admin actors (approve / reject) still
// route to the requester's user_id, not the admin's — the original user
// is who needs to see the update.
func (h *handlers) publishRequestStatusChanged(row dbq.LidarrRequest) {
	if h.eventbus == nil {
		return
	}
	h.eventbus.Publish(eventbus.Event{
		Kind:   "request.status_changed",
		UserID: uuidToString(row.UserID),
		Data: map[string]any{
			"request_id": uuidToString(row.ID),
			"status":     string(row.Status),
			"kind":       string(row.Kind),
		},
	})
}
