package main

import "sort"

// PokerHandCategory — категория покерной комбинации (чем больше,
// тем сильнее).
type PokerHandCategory int

const (
	PokerHighCard PokerHandCategory = iota
	PokerPair
	PokerTwoPair
	PokerThreeOfAKind
	PokerStraight
	PokerFlush
	PokerFullHouse
	PokerFourOfAKind
	PokerStraightFlush
)

// PokerHandValue — сравнимая оценка одной пятикарточной комбинации:
// категория плюс до пяти значений для сравнения кикеров, в порядке
// значимости (не все пять слотов задействованы каждой категорией —
// неиспользуемые остаются нулевыми, что не мешает сравнению, так
// как категория и более значимые слоты уже решают дело раньше).
type PokerHandValue struct {
	Category PokerHandCategory
	Tiebreak [5]int
}

// ComparePokerHands возвращает 1, если a сильнее b, -1, если b
// сильнее a, и 0 при полном равенстве (сплит банка).
func ComparePokerHands(a, b PokerHandValue) int {
	if a.Category != b.Category {
		if a.Category > b.Category {
			return 1
		}
		return -1
	}
	for i := 0; i < 5; i++ {
		if a.Tiebreak[i] != b.Tiebreak[i] {
			if a.Tiebreak[i] > b.Tiebreak[i] {
				return 1
			}
			return -1
		}
	}
	return 0
}

// pokerRankValue переводит ранг карты в покерное значение (2-14,
// туз всегда старший) — в отличие от Rank.Value(), используемого
// для Тонка/Дурака, где туз равен 1.
func pokerRankValue(r Rank) int {
	if r == Ace {
		return 14
	}
	return int(r)
}

// EvaluateBest5 перебирает все сочетания по 5 карт из переданных
// (обычно 5 или 7 — например, 2 карты в руке + 5 общих) и возвращает
// оценку наилучшей возможной комбинации.
func EvaluateBest5(cards []Card) PokerHandValue {
	if len(cards) == 5 {
		return evaluate5(cards)
	}
	best := PokerHandValue{}
	first := true
	combinations5(cards, func(combo []Card) {
		v := evaluate5(combo)
		if first || ComparePokerHands(v, best) > 0 {
			best = v
			first = false
		}
	})
	return best
}

// combinations5 вызывает fn для каждого сочетания по 5 карт из cards.
func combinations5(cards []Card, fn func(combo []Card)) {
	n := len(cards)
	idx := make([]int, 5)
	for i := range idx {
		idx[i] = i
	}
	for {
		combo := make([]Card, 5)
		for i, ix := range idx {
			combo[i] = cards[ix]
		}
		fn(combo)

		// следующая комбинация индексов (стандартный алгоритм перебора сочетаний)
		i := 4
		for i >= 0 && idx[i] == n-5+i {
			i--
		}
		if i < 0 {
			return
		}
		idx[i]++
		for j := i + 1; j < 5; j++ {
			idx[j] = idx[j-1] + 1
		}
	}
}

// evaluate5 оценивает ровно пять карт.
func evaluate5(cards []Card) PokerHandValue {
	values := make([]int, 5)
	suitCounts := map[Suit]int{}
	for i, c := range cards {
		values[i] = pokerRankValue(c.Rank)
		suitCounts[c.Suit]++
	}
	sort.Sort(sort.Reverse(sort.IntSlice(values)))

	isFlush := len(suitCounts) == 1
	straightHigh, isStraight := detectStraight(values)

	if isStraight && isFlush {
		return PokerHandValue{Category: PokerStraightFlush, Tiebreak: [5]int{straightHigh}}
	}

	// группируем по количеству повторений ранга
	countByRank := map[int]int{}
	for _, v := range values {
		countByRank[v]++
	}
	type rankCount struct{ rank, count int }
	var groups []rankCount
	for r, c := range countByRank {
		groups = append(groups, rankCount{r, c})
	}
	sort.Slice(groups, func(i, j int) bool {
		if groups[i].count != groups[j].count {
			return groups[i].count > groups[j].count
		}
		return groups[i].rank > groups[j].rank
	})

	switch {
	case groups[0].count == 4:
		kicker := groups[1].rank
		return PokerHandValue{Category: PokerFourOfAKind, Tiebreak: [5]int{groups[0].rank, kicker}}
	case groups[0].count == 3 && groups[1].count == 2:
		return PokerHandValue{Category: PokerFullHouse, Tiebreak: [5]int{groups[0].rank, groups[1].rank}}
	case isFlush:
		var tb [5]int
		copy(tb[:], values)
		return PokerHandValue{Category: PokerFlush, Tiebreak: tb}
	case isStraight:
		return PokerHandValue{Category: PokerStraight, Tiebreak: [5]int{straightHigh}}
	case groups[0].count == 3:
		kickers := []int{groups[1].rank, groups[2].rank}
		return PokerHandValue{Category: PokerThreeOfAKind, Tiebreak: [5]int{groups[0].rank, kickers[0], kickers[1]}}
	case groups[0].count == 2 && groups[1].count == 2:
		hiPair, loPair := groups[0].rank, groups[1].rank
		if loPair > hiPair {
			hiPair, loPair = loPair, hiPair
		}
		return PokerHandValue{Category: PokerTwoPair, Tiebreak: [5]int{hiPair, loPair, groups[2].rank}}
	case groups[0].count == 2:
		return PokerHandValue{Category: PokerPair, Tiebreak: [5]int{groups[0].rank, groups[1].rank, groups[2].rank, groups[3].rank}}
	default:
		var tb [5]int
		copy(tb[:], values)
		return PokerHandValue{Category: PokerHighCard, Tiebreak: tb}
	}
}

// detectStraight проверяет пять ОТСОРТИРОВАННЫХ ПО УБЫВАНИЮ
// покерных значений на стрит, включая особый случай "колеса"
// (A-5-4-3-2, где туз считается младшей картой, а старшая карта
// стрита — пятёрка).
func detectStraight(sortedDesc []int) (high int, ok bool) {
	// особый случай колеса: A,5,4,3,2
	if sortedDesc[0] == 14 && sortedDesc[1] == 5 && sortedDesc[2] == 4 && sortedDesc[3] == 3 && sortedDesc[4] == 2 {
		return 5, true
	}
	for i := 0; i < 4; i++ {
		if sortedDesc[i]-sortedDesc[i+1] != 1 {
			return 0, false
		}
	}
	return sortedDesc[0], true
}
