package com.pandoli365.bibimbap.jam;

import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 순위점수 유틸 — jam_awards rank 부여와 GRAND 정규화 점수 산정을 공유(중복 0).
 *
 * <p>두 메서드 모두 정렬 대상 리스트 + 점수 내림차순 비교자만 받는다(트랙/잼 컨텍스트는
 * 호출자가 보유 — inflate 차단, design concern 1). 비교자는 점수 DESC + tiebreak(game_id ASC)
 * 를 캡슐화하므로 정렬은 결정적이고 재산정 시 안정적이다.
 *
 * <ul>
 *   <li>{@link #standardCompetitionRank} — 동점이면 같은 rank, 다음 순위 건너뜀(1,2,2,4).</li>
 *   <li>{@link #rankScore} — (N - rank + 1) / N ∈ [1/N, 1]. 1위=1.0, 최하위=1/N.
 *       트랙 내 상대 순위만 쓰므로 트랙 간 스케일(numeric 1~5 vs vote count 0~수백) 무관.
 *       GRAND 가중합 입력.</li>
 * </ul>
 *
 * <p>동점 정의: 비교자가 0 을 반환하는 인접 항목은 같은 점수로 간주(같은 rank·같은 rankScore).
 * 따라서 비교자에 tiebreak(game_id 등)를 포함하면 동점이 깨질 수 있다 — 호출자는 동점을
 * 같은 rank 로 묶으려면 점수 키만으로 동등성을 판정하도록 별도 동등 비교자를 넘기지 않고,
 * 본 유틸은 정렬 후 인접 항목의 비교자 결과가 0 인 경우를 동점으로 처리한다. 호출자는
 * tiebreak 정렬용 비교자(점수 DESC, then game_id ASC)를 그대로 전달하면 된다 — 이때 동점
 * 판정은 점수 부분만으로 0 이 되도록 별도 동점 비교자(scoreEqualityComparator)를 함께 받는다.
 */
public final class RankScores {

    private RankScores() {
    }

    /**
     * standard competition ranking(1,2,2,4). 동점은 같은 rank.
     *
     * @param items   산정 대상(트랙 모집단)
     * @param byOrder 표시·정렬 순서 비교자(점수 DESC + tiebreak game_id ASC) — 결정적 정렬용
     * @param byScore 동점 판정 비교자(점수만; 0 이면 같은 rank) — tiebreak 미포함
     * @return item -> rank(1-based). 입력 순서 보존(LinkedHashMap, byOrder 정렬 순).
     */
    public static <T> Map<T, Integer> standardCompetitionRank(List<T> items,
                                                              Comparator<T> byOrder,
                                                              Comparator<T> byScore) {
        List<T> sorted = items.stream().sorted(byOrder).toList();
        Map<T, Integer> ranks = new LinkedHashMap<>();
        int rank = 0;
        int processed = 0;
        T prev = null;
        for (T item : sorted) {
            processed++;
            if (prev == null || byScore.compare(prev, item) != 0) {
                rank = processed; // 새 점수 구간 시작 — 건너뛴 순위 반영(competition)
            }
            ranks.put(item, rank);
            prev = item;
        }
        return ranks;
    }

    /**
     * 순위점수 (N - rank + 1) / N ∈ [1/N, 1]. 동점은 같은 rank → 같은 rankScore.
     * N=0 이면 빈 맵(가용 모집단 없음).
     *
     * @param items   동일 모집단
     * @param byOrder 정렬 순서 비교자(점수 DESC + tiebreak)
     * @param byScore 동점 판정 비교자(점수만)
     * @return item -> rankScore. GRAND 가중합 입력.
     */
    public static <T> Map<T, Double> rankScore(List<T> items,
                                               Comparator<T> byOrder,
                                               Comparator<T> byScore) {
        int n = items.size();
        Map<T, Double> scores = new LinkedHashMap<>();
        if (n == 0) {
            return scores;
        }
        Map<T, Integer> ranks = standardCompetitionRank(items, byOrder, byScore);
        for (Map.Entry<T, Integer> e : ranks.entrySet()) {
            scores.put(e.getKey(), (n - e.getValue() + 1) / (double) n);
        }
        return scores;
    }
}
