package store

import "context"

// pickBest выбирает победителя среди кандидатов на одни координаты.
//
// **Победитель — функция МНОЖЕСТВА кандидатов, а не порядка их поступления.**
// Попарная свёртка этого не даёт: полнота (или покрытие) — частичный порядок,
// тай-брейк — тотальный, и вместе они образуют нетранзитивное отношение победы,
// то есть цикл. При цикле повторная свёртка одной и той же доставки меняет
// содержимое витрины, и она перестаёт быть свёрткой журнала. Проверено дважды:
// сперва на точках, где нетранзитивность нашлась перебором троек, потом на
// сущностях, где ту же ошибку повторили молча.
//
// Отсюда и общий помощник вместо второй рукописной копии: механизм один,
// отношения разные. Правка одного экземпляра при живом втором уже случилась:
// тай-брейк точек сменён с байтового порядка на «побеждает пришедшая», а
// тай-брейк сущностей остался позицией журнала — потому что у сущности есть
// колонка провенанса, а у точки нет. Механизм при этом не разъехался ровно
// благодаря общему помощнику; критерий выбора между двумя правилами записан в
// `architecture.md`, раздел «Разрешение столкновений».
//
// dominates(a, b) обязан быть СТРОГИМ превосходством: a не хуже b и b не не
// хуже a. Иначе взаимно покрывающие друг друга кандидаты выбьют друг друга, и
// множество непревзойдённых окажется пустым.
//
// less обязан быть ТОТАЛЬНЫМ строгим порядком: при неединственном минимуме
// победителем оказывается просто первый в срезе, то есть порядок элементов на
// проводе, а он у HAE нестабилен.
//
// Отбор непревзойдённых КВАДРАТИЧЕН по числу кандидатов, и это названо вслух,
// потому что число кандидатов приходит из чужого тела. Отсюда `ctx`: цикл, чья
// стоимость определяется размером входа, обязан видеть отмену. Без него тело с
// двадцатью тысячами версий одного ключа занимало бы единственного воркера
// свёртки дольше, чем длится его же дедлайн, — то есть дедлайн, заведённый
// ровно против такого случая, не значил бы ничего. Замерено: n=4000 — 3.9 с,
// n=8000 — вчетверо больше.
//
// Вызывающий обязан сокращать множество до входа сюда: совпавших кандидатов
// схлопывать, а число различных — ограничивать. Помощник этого не делает
// сам — что считать «тем же» кандидатом, знает только он.
//
// Возвращает индекс победителя и индексы непревзойдённых — вторые нужны тем,
// кто считает несравнимость среди них. Пустой срез кандидатов — нарушенный
// инвариант вызывающего: оба сегодняшних вызова собирают множество из карты и
// пустого дать не могут.
func pickBest[T any](ctx context.Context, cands []T, dominates func(a, b T) bool, less func(a, b T) bool) (winner int, maximal []int, err error) {
	if len(cands) == 0 {
		return 0, nil, errNoCandidates
	}

	maximal = make([]int, 0, len(cands))
	for i := range cands {
		if err := ctx.Err(); err != nil {
			return 0, nil, err
		}
		beaten := false
		for j := range cands {
			if i == j {
				continue
			}
			if dominates(cands[j], cands[i]) {
				beaten = true
				break
			}
		}
		if !beaten {
			maximal = append(maximal, i)
		}
	}

	winner = maximal[0]
	for _, i := range maximal[1:] {
		if less(cands[i], cands[winner]) {
			winner = i
		}
	}
	return winner, maximal, nil
}
