package poster

import (
	"log/slog"
	"sync"
	"time"

	"pete/internal/storage"

	"maunium.net/go/mautrix/id"
)

// ReactionCallback is an optional hook fired after a reaction is recorded.
// Used to wire reaction-driven features (e.g. ❓ → summary) without coupling
// poster to those packages.
type ReactionCallback func(roomID id.RoomID, targetEventID id.EventID, guid, channel, emoji string)

var (
	reactionCallbackMu sync.RWMutex
	reactionCallback   ReactionCallback

	// reactionSem bounds concurrent in-flight reaction callbacks. Each callback
	// may do DB / Matrix / LLM I/O, so an unbounded fan-out (`go cb(...)` per
	// reaction) lets a reaction flood — legitimate burst or an abusive room
	// member spamming emoji — exhaust goroutines, connections, and memory.
	// Tries to acquire non-blocking: if the cap is saturated we drop the
	// callback with a warn rather than queueing, on the theory that the room
	// is already busy enough.
	reactionSem = make(chan struct{}, 8)
)

// SetReactionCallback installs a hook called after every recorded reaction.
// Pass nil to clear. Safe to call before or after the matrix client starts.
func SetReactionCallback(fn ReactionCallback) {
	reactionCallbackMu.Lock()
	reactionCallback = fn
	reactionCallbackMu.Unlock()
}

// HandleReaction processes a reaction event by mapping it back to the story GUID.
func HandleReaction(roomID id.RoomID, eventID id.EventID, targetEventID id.EventID, emoji string, userID id.UserID) {
	guid, channel, found := storage.LookupPostGUID(string(targetEventID))
	if !found {
		slog.Info("reaction on event not in post_log, ignoring",
			"target_event", targetEventID, "emoji", emoji, "user", userID)
		return
	}

	storage.InsertReaction(guid, channel, string(eventID), emoji, string(userID), time.Now().Unix())
	slog.Debug("reaction recorded",
		"guid", guid,
		"channel", channel,
		"emoji", emoji,
		"user", userID,
	)

	reactionCallbackMu.RLock()
	cb := reactionCallback
	reactionCallbackMu.RUnlock()
	if cb == nil {
		return
	}

	select {
	case reactionSem <- struct{}{}:
	default:
		slog.Warn("reaction callback dropped: worker pool saturated",
			"guid", guid, "channel", channel, "emoji", emoji)
		return
	}
	go func() {
		defer func() { <-reactionSem }()
		cb(roomID, targetEventID, guid, channel, emoji)
	}()
}
