# src/core/move_generator.gd
# Pure functions — zero Godot dependency
# Generate all legal plays from a hand

class_name MoveGenerator
extends RefCounted

const MAX_ENUM_NODES := 10000
const BEAM_WIDTH := 50

static func generate(hand: Array[Card], current_rank: int, config: RuleConfig) -> Array[HandEvaluator.EvaluatedPlay]:
    var results: Array[HandEvaluator.EvaluatedPlay] = []
    var pass := HandEvaluator.EvaluatedPlay.new()
    pass.type = -1
    pass.primary_rank = 0
    results.append(pass)
    if hand.is_empty():
        return results
    var sorted := hand.duplicate(false)
    _sort(sorted)
    var seen_ranks := {}
    for c in sorted:
        var rk := c.rank()
        if seen_ranks.get(rk, 0) < 4:
            var ep := HandEvaluator.evaluate([c], current_rank, config)
            if ep != null and ep.type != HandEvaluator.INVALID:
                if not _contains_duplicate(results, ep):
                    results.append(ep)
            seen_ranks[rk] = seen_ranks.get(rk, 0) + 1
    _gen_pairs(sorted, results, current_rank, config)
    _gen_triples(sorted, results, current_rank, config)
    _gen_bombs(sorted, results, current_rank, config)
    _gen_straights(sorted, results, current_rank, config)
    _gen_rocket(sorted, results, current_rank, config)
    if results.size() > MAX_ENUM_NODES:
        results = results.slice(0, MAX_ENUM_NODES)
    return results

static func _gen_pairs(sorted: Array[Card], results: Array[HandEvaluator.EvaluatedPlay], current_rank: int, config: RuleConfig) -> void:
    var rank_counts := {}
    for c in sorted:
        var rk := c.rank()
        if not rank_counts.has(rk): rank_counts[rk] = []
        rank_counts[rk].append(c)
    for rk in rank_counts:
        var cards: Array = rank_counts[rk]
        if cards.size() >= 2:
            var ep := HandEvaluator.evaluate(cards.slice(0, 2), current_rank, config)
            if ep != null: results.append(ep)

static func _gen_triples(sorted: Array[Card], results: Array[HandEvaluator.EvaluatedPlay], current_rank: int, config: RuleConfig) -> void:
    var rank_counts := {}
    for c in sorted:
        var rk := c.rank()
        if not rank_counts.has(rk): rank_counts[rk] = []
        rank_counts[rk].append(c)
    for rk in rank_counts:
        var cards: Array = rank_counts[rk]
        if cards.size() >= 3:
            var ep := HandEvaluator.evaluate(cards.slice(0, 3), current_rank, config)
            if ep != null: results.append(ep)
            if cards.size() >= 3:
                _gen_triple_plus_kickers(sorted, cards.slice(0, 3), results, current_rank, config)

static func _gen_triple_plus_kickers(hand: Array[Card], triple: Array[Card], results: Array[HandEvaluator.EvaluatedPlay], current_rank: int, config: RuleConfig) -> void:
    var remaining: Array[Card] = []
    for c in hand:
        if not _card_in(triple, c): remaining.append(c)
    var pairs := _find_pairs(remaining)
    for pair in pairs:
        var ep := HandEvaluator.evaluate(triple + pair, current_rank, config)
        if ep != null: results.append(ep)

static func _gen_bombs(sorted: Array[Card], results: Array[HandEvaluator.EvaluatedPlay], current_rank: int, config: RuleConfig) -> void:
    var rank_counts := {}
    for c in sorted:
        var rk := c.rank()
        if not rank_counts.has(rk): rank_counts[rk] = []
        rank_counts[rk].append(c)
    for rk in rank_counts:
        var cards: Array = rank_counts[rk]
        if cards.size() >= 4:
            var ep := HandEvaluator.evaluate(cards.slice(0, 4), current_rank, config)
            if ep != null: results.append(ep)
        for count in range(5, cards.size() + 1):
            var ep := HandEvaluator.evaluate(cards.slice(0, count), current_rank, config)
            if ep != null: results.append(ep)

static func _gen_straights(sorted: Array[Card], results: Array[HandEvaluator.EvaluatedPlay], current_rank: int, config: RuleConfig) -> void:
    var unique := _unique_ranks(sorted)
    for start in unique:
        for length in range(5, 13):
            var needed: Array[int] = []
            for r in range(start, start + length): needed.append(r)
            var found := _pick_consecutive(sorted, needed)
            if found.size() == length:
                var ep := HandEvaluator.evaluate(found, current_rank, config)
                if ep != null: results.append(ep)

static func _gen_rocket(sorted: Array[Card], results: Array[HandEvaluator.EvaluatedPlay], current_rank: int, config: RuleConfig) -> void:
    var sj_indices: Array[int] = []
    var bj_indices: Array[int] = []
    for i in range(sorted.size()):
        if sorted[i].rank() == 15: sj_indices.append(i)
        if sorted[i].rank() == 16: bj_indices.append(i)
    if sj_indices.size() >= 2 and bj_indices.size() >= 2:
        var cards: Array[Card] = []
        for idx in sj_indices.slice(0, 2) + bj_indices.slice(0, 2):
            cards.append(sorted[idx])
        var ep := HandEvaluator.evaluate(cards, current_rank, config)
        if ep != null: results.append(ep)

static func _find_pairs(hand: Array[Card]) -> Array:
    var result := []
    var rank_groups := {}
    for c in hand:
        var rk := c.rank()
        if not rank_groups.has(rk): rank_groups[rk] = []
        rank_groups[rk].append(c)
    for rk in rank_groups:
        if rank_groups[rk].size() >= 2:
            result.append(rank_groups[rk].slice(0, 2))
    return result

static func _card_in(cards: Array[Card], target: Card) -> bool:
    for c in cards:
        if c.card_id == target.card_id: return true
    return false

static func _unique_ranks(hand: Array[Card]) -> Array:
    var seen := {}
    var result: Array = []
    for c in hand:
        var rk := c.rank()
        if not seen.has(rk):
            seen[rk] = true
            result.append(rk)
    result.sort()
    return result

static func _pick_consecutive(hand: Array[Card], needed: Array[int]) -> Array[Card]:
    var result: Array[Card] = []
    var used := {}
    for target in needed:
        var found := false
        for c in hand:
            if c.rank() == target and not used.has(c.card_id):
                result.append(c)
                used[c.card_id] = true
                found = true
                break
        if not found: return []
    return result

static func _contains_duplicate(results: Array[HandEvaluator.EvaluatedPlay], ep: HandEvaluator.EvaluatedPlay) -> bool:
    for existing in results:
        if existing.type == ep.type and existing.primary_rank == ep.primary_rank:
            if existing.cards.size() == ep.cards.size():
                return true
    return false

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