package game

import (
	"github.com/google/uuid"

	"scrabble/backend/internal/engine"
	"scrabble/backend/internal/notify"
)

// The mappers below project the game domain into the wire-agnostic notify.* input
// structs the enriched live events carry. They keep the wire schema out of the
// game package: notify owns the FlatBuffers encoding, this file only resolves the
// values (seat display names, last-activity sort key) into its input shapes.

// gameSummary projects a game.Game into the notify.GameSummary embedded in enriched
// events. names is the seat-indexed display-name slice from seatNames; LastActivityUnix
// mirrors the gateway view (the current turn's start while active, the finish time once
// finished).
func gameSummary(g Game, names []string) notify.GameSummary {
	seats := make([]notify.SeatStanding, 0, len(g.Seats))
	for _, s := range g.Seats {
		name := ""
		if s.Seat >= 0 && s.Seat < len(names) {
			name = names[s.Seat]
		}
		// An open game's still-empty opponent seat carries no account: send an empty id
		// (not the nil-UUID string) so the client renders it as "searching for opponent".
		accountID := ""
		if s.AccountID != uuid.Nil {
			accountID = s.AccountID.String()
		}
		seats = append(seats, notify.SeatStanding{
			Seat:        s.Seat,
			AccountID:   accountID,
			DisplayName: name,
			Score:       s.Score,
			HintsUsed:   s.HintsUsed,
			IsWinner:    s.IsWinner,
		})
	}
	last := g.TurnStartedAt
	if g.FinishedAt != nil {
		last = *g.FinishedAt
	}
	return notify.GameSummary{
		ID:                   g.ID.String(),
		Variant:              g.Variant.String(),
		DictVersion:          g.DictVersion,
		Status:               g.Status,
		Players:              g.Players,
		ToMove:               g.ToMove,
		TurnTimeoutSecs:      int(g.TurnTimeout.Seconds()),
		MultipleWordsPerTurn: g.MultipleWordsPerTurn,
		VsAI:                 g.VsAI,
		Kind:                 int(g.Kind),
		MoveCount:            g.MoveCount,
		EndReason:            g.EndReason,
		Seats:                seats,
		LastActivityUnix:     last.Unix(),
		NoRepeatWords:        g.NoRepeatWords,
	}
}

// playerState projects a StateView into the notify.PlayerState carried by the
// match_found / game_started events. The rack is re-encoded to wire alphabet indices;
// the variant alphabet display table is embedded when includeAlphabet is set (an
// initial view whose recipient may not have cached the variant yet).
func playerState(v StateView, names []string, includeAlphabet bool) (notify.PlayerState, error) {
	rack, err := engine.EncodeRack(v.Game.Variant, v.Rack)
	if err != nil {
		return notify.PlayerState{}, err
	}
	ps := notify.PlayerState{
		Game:           gameSummary(v.Game, names),
		Seat:           v.Seat,
		Rack:           rack,
		BagLen:         v.BagLen,
		HintsRemaining: v.HintsRemaining,
	}
	if includeAlphabet {
		tab, err := engine.AlphabetTable(v.Game.Variant)
		if err != nil {
			return notify.PlayerState{}, err
		}
		ps.Alphabet = make([]notify.AlphabetLetter, len(tab))
		for i, e := range tab {
			ps.Alphabet[i] = notify.AlphabetLetter{Index: int(e.Index), Letter: e.Letter, Value: e.Value}
		}
	}
	return ps, nil
}
