// Package cards holds the deck primitives every card game on Pete shares.
//
// gogobee never had this: blackjack carried its own deck, UNO carried another,
// and hold'em leaned on a third-party one. Three shuffles, three bugs to fix
// three times. The games ported over here consolidate onto this instead.
//
// Two rules hold throughout:
//
// The RNG is threaded, never global. Every shuffle takes an explicit *rand.Rand,
// so a hand is reproducible from its seed — which is what makes the engines
// testable, and what lets us re-deal a disputed hand and show the player exactly
// what the shoe did.
//
// A Deck is a plain value. No pointers into it, no timers hanging off it, so a
// game in progress serializes to JSON and survives a redeploy.
package cards

import "math/rand/v2"

// Suit is one of the four French suits.
type Suit uint8

const (
	Spades Suit = iota
	Hearts
	Diamonds
	Clubs
)

// Rank runs Ace(1) through King(13). Ace is low here; games that want it high
// (blackjack's soft 11, hold'em's wheel) say so themselves.
type Rank uint8

const (
	Ace   Rank = 1
	Jack  Rank = 11
	Queen Rank = 12
	King  Rank = 13
)

var (
	suitGlyphs = [4]string{"♠", "♥", "♦", "♣"}
	rankNames  = [14]string{"", "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"}
)

// Card is one playing card. The short JSON keys keep a serialized shoe small —
// a six-deck blackjack state is 312 of these.
type Card struct {
	Rank Rank `json:"r"`
	Suit Suit `json:"s"`
}

// String renders the card the way a table shows it: "A♠", "10♥".
func (c Card) String() string {
	if c.Rank < Ace || c.Rank > King || c.Suit > Clubs {
		return "??"
	}
	return rankNames[c.Rank] + suitGlyphs[c.Suit]
}

// Red reports whether the card is a red suit — the one thing every renderer
// needs and nobody should re-derive.
func (c Card) Red() bool { return c.Suit == Hearts || c.Suit == Diamonds }

// Deck is an ordered pile of cards. The next card to come off is at index 0.
type Deck []Card

// NewDeck builds n standard 52-card decks in fixed order. Shuffle before use:
// an unshuffled deck is a bug at a table, but it's exactly what a test wants.
func NewDeck(n int) Deck {
	if n < 1 {
		n = 1
	}
	d := make(Deck, 0, 52*n)
	for i := 0; i < n; i++ {
		for s := Spades; s <= Clubs; s++ {
			for r := Ace; r <= King; r++ {
				d = append(d, Card{Rank: r, Suit: s})
			}
		}
	}
	return d
}

// Shuffle permutes the deck in place using the supplied RNG. Passing a seeded
// *rand.Rand gives the same shuffle every time, which is the whole point.
func (d Deck) Shuffle(rng *rand.Rand) {
	rng.Shuffle(len(d), func(i, j int) { d[i], d[j] = d[j], d[i] })
}

// Draw takes the top card. ok is false when the deck is spent; the caller
// decides whether that means reshuffle or fold, because the two games that hit
// it disagree.
func (d *Deck) Draw() (c Card, ok bool) {
	if len(*d) == 0 {
		return Card{}, false
	}
	c = (*d)[0]
	*d = (*d)[1:]
	return c, true
}

// Hand renders a run of cards for display: "A♠ 10♥".
func Hand(cs []Card) string {
	s := ""
	for i, c := range cs {
		if i > 0 {
			s += " "
		}
		s += c.String()
	}
	return s
}

// NewRNG seeds a generator from two uint64s. Games store the seed alongside the
// hand so a finished hand can be replayed exactly as it was dealt.
func NewRNG(seed1, seed2 uint64) *rand.Rand {
	return rand.New(rand.NewPCG(seed1, seed2))
}

func (s Suit) String() string {
	if s > Clubs {
		return "?"
	}
	return suitGlyphs[s]
}
