package main

import "sort"

// IsSet проверяет, что переданные карты образуют валидный сет:
// 3 и более карты одного ранга, все с разными мастями (иначе была
// бы дублирующаяся карта, что невозможно в одной колоде, но
// проверка на всякий случай — например, при игре с несколькими
// колодами).
func IsSet(cards []Card) bool {
	if len(cards) < 3 {
		return false
	}
	rank := cards[0].Rank
	seenSuits := make(map[Suit]bool, len(cards))
	for _, c := range cards {
		if c.Rank != rank {
			return false
		}
		if seenSuits[c.Suit] {
			return false
		}
		seenSuits[c.Suit] = true
	}
	return true
}

// IsSequence проверяет, что карты образуют валидный сиквенс:
// 3 и более карты одной масти подряд по рангу. Туз может считаться
// низким (A-2-3) или высоким (Q-K-A), но не одновременно и то, и
// другое (K-A-2 невалиден).
func IsSequence(cards []Card) bool {
	if len(cards) < 3 {
		return false
	}
	suit := cards[0].Suit
	ranks := make([]int, 0, len(cards))
	for _, c := range cards {
		if c.Suit != suit {
			return false
		}
		ranks = append(ranks, int(c.Rank))
	}
	sort.Ints(ranks)

	// нет дубликатов рангов
	for i := 1; i < len(ranks); i++ {
		if ranks[i] == ranks[i-1] {
			return false
		}
	}

	if isConsecutive(ranks) {
		return true
	}

	// вариант с тузом как старшей картой: заменяем 1 (Ace) на 14
	// и пробуем снова.
	if ranks[0] == int(Ace) {
		aceHigh := make([]int, len(ranks))
		copy(aceHigh, ranks[1:])
		aceHigh[len(aceHigh)-1] = 14
		sort.Ints(aceHigh)
		return isConsecutive(aceHigh)
	}

	return false
}

func isConsecutive(sortedRanks []int) bool {
	for i := 1; i < len(sortedRanks); i++ {
		if sortedRanks[i] != sortedRanks[i-1]+1 {
			return false
		}
	}
	return true
}

// FindAllSets группирует карты руки по рангу и возвращает те
// группы, что образуют валидный сет (3+ карты).
func FindAllSets(hand []Card) [][]Card {
	byRank := make(map[Rank][]Card)
	for _, c := range hand {
		byRank[c.Rank] = append(byRank[c.Rank], c)
	}
	var sets [][]Card
	for _, group := range byRank {
		if IsSet(group) {
			sets = append(sets, group)
		}
	}
	return sets
}

// FindAllSequences группирует карты руки по масти, находит все
// максимальные последовательности подряд идущих рангов длиной 3+
// и возвращает их. Учитывает и туз-снизу, и туз-сверху отдельно,
// если рука позволяет оба варианта (сама карта туза при этом
// используется только один раз в каждом найденном варианте).
func FindAllSequences(hand []Card) [][]Card {
	bySuit := make(map[Suit][]Card)
	for _, c := range hand {
		bySuit[c.Suit] = append(bySuit[c.Suit], c)
	}

	var result [][]Card
	for _, cards := range bySuit {
		result = append(result, findRunsInSuit(cards)...)
	}
	return result
}

// findRunsInSuit находит все максимальные подряд идущие отрезки
// (длиной 3+) внутри одной масти, включая отдельную попытку с
// тузом как старшей картой.
func findRunsInSuit(cards []Card) [][]Card {
	byRank := make(map[int]Card)
	for _, c := range cards {
		byRank[int(c.Rank)] = c
	}

	var runs [][]Card
	runs = append(runs, extractRuns(byRank, 1, 13, false)...)

	if _, hasAce := byRank[int(Ace)]; hasAce {
		runs = append(runs, extractRuns(byRank, 2, 14, true)...)
	}
	return runs
}

// extractRuns проходит по диапазону [lo, hi] и собирает
// максимальные подряд идущие последовательности длиной 3+.
// Если aceHigh=true, ранг 14 интерпретируется как туз.
func extractRuns(byRank map[int]Card, lo, hi int, aceHigh bool) [][]Card {
	var runs [][]Card
	var current []Card

	flush := func() {
		if len(current) >= 3 {
			runCopy := make([]Card, len(current))
			copy(runCopy, current)
			runs = append(runs, runCopy)
		}
		current = nil
	}

	for r := lo; r <= hi; r++ {
		rank := r
		if aceHigh && r == 14 {
			rank = int(Ace)
		}
		c, ok := byRank[rank]
		if ok {
			current = append(current, c)
		} else {
			flush()
		}
	}
	flush()
	return runs
}

// HandValue возвращает суммарную очковую стоимость карт на руке —
// используется и для расчёта штрафа проигравшего, и для проверки
// возможности объявить Тонк.
func HandValue(hand []Card) int {
	total := 0
	for _, c := range hand {
		total += c.Value()
	}
	return total
}

// CanDeclareTonk проверяет, можно ли объявить Тонк при данной руке:
// суммарная стоимость карт должна быть не больше threshold.
func CanDeclareTonk(hand []Card, threshold int) bool {
	return HandValue(hand) <= threshold
}
