# src/core/hand_evaluator.gd
# Pure functions — zero Godot dependency
# Evaluate: [Card] + current_rank + RuleConfig -> EvaluatedPlay | null

class_name HandEvaluator
extends RefCounted

const INVALID := -1

class EvaluatedPlay:
    extends RefCounted
    var type: int = INVALID
    var primary_rank: int = 0
    var cards: Array[Card] = []
    var is_pure_bomb: bool = false

    func _to_string() -> String:
        var names := ["SINGLE","PAIR","TRIPLE","TRIPLE+2","STRAIGHT","PAIRS","STEEL","FLUSH","BOMB","ROCKET"]
        return "EvaluatedPlay(%s, rank=%d, pure=%s)" % [names[type], primary_rank, is_pure_bomb]

static func evaluate(cards: Array[Card], current_rank: int, config: RuleConfig) -> EvaluatedPlay:
    if cards.is_empty():
        return null
    var sorted := cards.duplicate(false)
    _sort_by_rank_and_suit(sorted)
    var n := sorted.size()
    if n == 1:
        return _make_result(TYPE_SINGLE, sorted[0].rank(), false, sorted)
    var wilds: Array[Card] = []
    var reals: Array[Card] = []
    for c in sorted:
        if c.is_heart() and c.rank() == current_rank:
            wilds.append(c)
        else:
            reals.append(c)
    var has_wild := wilds.size() > 0
    if n == 2:
        return _eval_two(reals, wilds, has_wild)
    if n == 3:
        return _eval_three(reals, wilds, has_wild)
    if n == 4:
        var result := _eval_four(reals, wilds, has_wild, current_rank, config)
        if result != null:
            return result
    return _eval_multi(reals, wilds, has_wild, current_rank, n, config)

static func _make_result(type: int, primary_rank: int, is_pure: bool, cards1: Array[Card]) -> EvaluatedPlay:
    var r := EvaluatedPlay.new()
    r.type = type
    r.primary_rank = primary_rank
    r.cards = cards1.duplicate(false)
    r.is_pure_bomb = is_pure
    return r

static func _eval_two(reals: Array[Card], wilds: Array[Card], has_wild: bool) -> EvaluatedPlay:
    if not has_wild:
        if reals.size() == 2 and reals[0].rank() == reals[1].rank():
            return _make_result(TYPE_PAIR, reals[0].rank(), true, reals)
    else:
        if reals.size() == 1:
            return _make_result(TYPE_PAIR, reals[0].rank(), false, reals + wilds)
    return null

static func _eval_three(reals: Array[Card], wilds: Array[Card], has_wild: bool) -> EvaluatedPlay:
    if not has_wild:
        if reals.size() == 3 and _all_same_rank(reals):
            return _make_result(TYPE_TRIPLE, reals[0].rank(), true, reals)
    else:
        if reals.size() == 2 and reals[0].rank() == reals[1].rank():
            return _make_result(TYPE_TRIPLE, reals[0].rank(), false, reals + wilds)
        if reals.size() == 1 and wilds.size() >= 2:
            return _make_result(TYPE_TRIPLE, reals[0].rank(), false, reals + wilds.slice(0, 2))
    return null

static func _eval_four(reals: Array[Card], wilds: Array[Card], has_wild: bool, current_rank: int, config: RuleConfig) -> EvaluatedPlay:
    var all_cards := reals + wilds
    if not has_wild and reals.size() == 4:
        if _all_same_rank(reals):
            return _make_result(TYPE_BOMB, reals[0].rank(), true, reals)
        var sj_count := 0
        var bj_count := 0
        for c in reals:
            if c.rank() == 15: sj_count += 1
            if c.rank() == 16: bj_count += 1
        if sj_count == 2 and bj_count == 2:
            return _make_result(TYPE_ROCKET, 999, true, reals)
        return null
    if has_wild:
        if reals.size() == 3 and _all_same_rank(reals):
            return _make_result(TYPE_BOMB, reals[0].rank(), false, all_cards)
        if reals.size() == 2 and reals[0].rank() == reals[1].rank():
            return _make_result(TYPE_BOMB, reals[0].rank(), false, all_cards)
    return null

static func _eval_multi(reals: Array[Card], wilds: Array[Card], has_wild: bool, current_rank: int, n: int, config: RuleConfig) -> EvaluatedPlay:
    if not has_wild:
        return _eval_pure_multiple(reals, n, config)
    if n == 5:
        var result := _eval_triple_plus_two(reals, wilds)
        if result != null: return result
        result = _eval_straight(reals, wilds, 5, config)
        if result != null: return result
    if n >= 6 and n % 2 == 0:
        var result := _eval_consecutive_pairs(reals, wilds, n / 2, config)
        if result != null: return result
    if n >= 6 and n % 3 == 0:
        var result := _eval_steel_plate(reals, wilds, n / 3, config)
        if result != null: return result
    if n >= 5:
        var result := _eval_straight(reals, wilds, n, config)
        if result != null: return result
    return null

static func _eval_pure_multiple(reals: Array[Card], n: int, config: RuleConfig) -> EvaluatedPlay:
    if n >= 5 and _is_straight(reals, config):
        return _make_result(TYPE_STRAIGHT, reals[n-1].rank(), true, reals)
    if n >= 6 and n % 2 == 0 and _is_consecutive_pairs(reals, n / 2):
        return _make_result(TYPE_CONSECUTIVE_PAIRS, reals[n-1].rank(), true, reals)
    if n == 5 and _is_triple_plus_two(reals):
        return _make_result(TYPE_TRIPLE_PLUS_TWO, _get_triple_rank(reals), true, reals)
    if n >= 6 and n % 3 == 0 and _is_steel_plate(reals):
        return _make_result(TYPE_STEEL_PLATE, reals[n-1].rank(), true, reals)
    if n >= 5 and _is_straight_flush(reals, config):
        return _make_result(TYPE_STRAIGHT_FLUSH, reals[n-1].rank(), true, reals)
    return null

static func _eval_triple_plus_two(reals: Array[Card], wilds: Array[Card]) -> EvaluatedPlay:
    if reals.size() + wilds.size() != 5: return null
    if reals.is_empty(): return null
    var rank_counts := _rank_counts(reals)
    if rank_counts.values().has(3):
        return _make_result(TYPE_TRIPLE_PLUS_TWO, _get_triple_rank(reals), wilds.is_empty(), reals + wilds)
    return null

static func _eval_straight(reals: Array[Card], wilds: Array[Card], n: int, config: RuleConfig) -> EvaluatedPlay:
    if reals.size() + wilds.size() != n: return null
    var sorted := reals.duplicate(false)
    _sort_by_rank_and_suit(sorted)
    var w := wilds.size()
    var gaps: Array[int] = []
    for i in range(sorted.size() - 1):
        var delta := sorted[i+1].rank() - sorted[i].rank()
        if delta > 1:
            gaps.append(delta - 1)
    var total_gaps := 0
    for g in gaps: total_gaps += g
    if total_gaps == w or (total_gaps <= w and w >= total_gaps):
        var max_rank := sorted[sorted.size()-1].rank()
        if not config.straight_extends_to_ace and max_rank > 14: return null
        return _make_result(TYPE_STRAIGHT, max_rank, w == 0, reals + wilds)
    return null

static func _eval_consecutive_pairs(reals: Array[Card], wilds: Array[Card], pair_count: int, config: RuleConfig) -> EvaluatedPlay:
    if reals.size() + wilds.size() != pair_count * 2: return null
    if wilds.is_empty() and _is_consecutive_pairs(reals, pair_count):
        return _make_result(TYPE_CONSECUTIVE_PAIRS, reals[reals.size()-1].rank(), true, reals)
    return null

static func _eval_steel_plate(reals: Array[Card], wilds: Array[Card], triple_count: int, config: RuleConfig) -> EvaluatedPlay:
    if reals.size() + wilds.size() != triple_count * 3: return null
    if wilds.is_empty() and _is_steel_plate(reals):
        return _make_result(TYPE_STEEL_PLATE, reals[reals.size()-1].rank(), true, reals)
    return null

static func _all_same_rank(cards: Array[Card]) -> bool:
    var r := cards[0].rank()
    for i in range(1, cards.size()):
        if cards[i].rank() != r: return false
    return true

static func _is_straight(cards: Array[Card], config: RuleConfig) -> bool:
    for i in range(cards.size() - 1):
        if cards[i+1].rank() - cards[i].rank() != 1: return false
    if not config.straight_extends_to_ace and cards[cards.size()-1].rank() > 14: return false
    return true

static func _is_consecutive_pairs(cards: Array[Card], pair_count: int) -> bool:
    for i in range(0, cards.size(), 2):
        if cards[i].rank() != cards[i+1].rank(): return false
    for i in range(0, cards.size() - 2, 2):
        if cards[i+2].rank() - cards[i].rank() != 1: return false
    return true

static func _is_triple_plus_two(cards: Array[Card]) -> bool:
    var rank_counts := _rank_counts(cards)
    return rank_counts.values().has(3) and rank_counts.values().has(2)

static func _is_steel_plate(cards: Array[Card]) -> bool:
    var rank_counts := _rank_counts(cards)
    for count in rank_counts.values():
        if count != 3: return false
    var ranks := rank_counts.keys()
    ranks.sort()
    for i in range(ranks.size() - 1):
        if ranks[i+1] - ranks[i] != 1: return false
    return true

static func _is_straight_flush(cards: Array[Card], config: RuleConfig) -> bool:
    if not _is_straight(cards, config): return false
    var suit := cards[0].suit()
    for i in range(1, cards.size()):
        if cards[i].suit() != suit: return false
    return true

static func _rank_counts(cards: Array[Card]) -> Dictionary:
    var d := {}
    for c in cards:
        var r := c.rank()
        d[r] = d.get(r, 0) + 1
    return d

static func _get_triple_rank(cards: Array[Card]) -> int:
    var rc := _rank_counts(cards)
    for rank in rc:
        if rc[rank] == 3: return rank
    return 0

static func _sort_by_rank_and_suit(cards: Array[Card]) -> void:
    cards.sort_custom(func(a: Card, b: Card): return a.compare_to(b) < 0)
