use crate::board::{Bitboard, Position};
use crate::types::{Color, File, Move16, PieceType, Square, SQ_D, SQ_U};

use super::promotions::must_promote;
use super::types::MoveGenType;
use super::MoveSink;

fn push_move(list: &mut impl MoveSink, from: Square, to: Square) {
    list.push_move(Move16::make_move_fast(from, to));
}

fn push_promote(list: &mut impl MoveSink, from: Square, to: Square) {
    list.push_move(Move16::make_promote_fast(from, to));
}

/// 歩の移動手を生成
fn pawn_bb_effect(pawns: Bitboard, us: Color) -> Bitboard {
    let mut out = Bitboard::EMPTY;
    for file_idx in 0..9 {
        let file = File::new(file_idx);
        let file_mask = Bitboard::file_mask(file);
        let file_bits = pawns & file_mask;
        if file_bits.is_empty() {
            continue;
        }
        let mask_bits = file_mask.packed_bits();
        let shifted = if us == Color::BLACK {
            file_bits.packed_bits() >> 1
        } else {
            file_bits.packed_bits() << 1
        };
        out |= Bitboard::from_packed_bits(shifted & mask_bits);
    }
    out
}

/// 歩の移動手を生成
#[allow(clippy::extra_unused_type_parameters)]
pub(super) fn generate_pawn_moves<T: MoveGenType>(
    pos: &Position,
    list: &mut impl MoveSink,
    target: Bitboard,
    us: Color,
) {
    let bb = pos.bitboards();
    let allow_underpromote = T::generate_all_legal();
    let pawns = bb.pieces_for(PieceType::PAWN, us);
    let mut destinations = pawn_bb_effect(pawns, us).and(target);

    if us == Color::BLACK {
        while let Some(to) = destinations.pop_lsb() {
            let from = Square::new(to.raw() + SQ_D);
            let to_rank = to.rank().raw();
            if to_rank == 0 {
                push_promote(list, from, to);
                continue;
            }

            let can_promote = from.rank().raw() <= 2 || to_rank <= 2;
            if can_promote {
                push_promote(list, from, to);
            }
            if allow_underpromote || !can_promote {
                push_move(list, from, to);
            }
        }
    } else {
        while let Some(to) = destinations.pop_lsb() {
            let from = Square::new(to.raw() + SQ_U);
            let to_rank = to.rank().raw();
            if to_rank == 8 {
                push_promote(list, from, to);
                continue;
            }

            let can_promote = from.rank().raw() >= 6 || to_rank >= 6;
            if can_promote {
                push_promote(list, from, to);
            }
            if allow_underpromote || !can_promote {
                push_move(list, from, to);
            }
        }
    }
}

/// 桂馬の移動手を生成
#[allow(clippy::extra_unused_type_parameters)]
pub(super) fn generate_knight_moves<T: MoveGenType>(
    pos: &Position,
    list: &mut impl MoveSink,
    target: Bitboard,
    us: Color,
) {
    use crate::board::attack_tables::knight_attacks_unchecked;

    let bb = pos.bitboards();
    let mut knights = bb.pieces_for(PieceType::KNIGHT, us);

    while let Some(from) = knights.pop_lsb() {
        let from_rank = from.raw() % 9;
        let attacks = knight_attacks_unchecked(from, us);
        let mut destinations = attacks.and(target);

        while let Some(to) = destinations.pop_lsb() {
            let to_rank = to.raw() % 9;
            let must_promote = if us == Color::BLACK { to_rank <= 1 } else { to_rank >= 7 };
            if must_promote {
                push_promote(list, from, to);
                continue;
            }

            let can_promote = if us == Color::BLACK {
                from_rank <= 2 || to_rank <= 2
            } else {
                from_rank >= 6 || to_rank >= 6
            };
            if can_promote {
                push_promote(list, from, to);
            }
            push_move(list, from, to);
        }
    }
}

/// 銀の移動手を生成
#[allow(clippy::extra_unused_type_parameters)]
pub(super) fn generate_silver_moves<T: MoveGenType>(
    pos: &Position,
    list: &mut impl MoveSink,
    target: Bitboard,
    us: Color,
) {
    use crate::board::attack_tables::silver_attacks_unchecked;
    use crate::types::Rank;

    let bb = pos.bitboards();
    let mut silvers = bb.pieces_for(PieceType::SILVER, us);
    let enemy_territory = match us {
        Color::BLACK => {
            Bitboard::rank_mask(Rank::RANK_1)
                | Bitboard::rank_mask(Rank::RANK_2)
                | Bitboard::rank_mask(Rank::RANK_3)
        }
        Color::WHITE => {
            Bitboard::rank_mask(Rank::RANK_7)
                | Bitboard::rank_mask(Rank::RANK_8)
                | Bitboard::rank_mask(Rank::RANK_9)
        }
    };

    while let Some(from) = silvers.pop_lsb() {
        let attacks = silver_attacks_unchecked(from, us);
        let destinations = attacks.and(target);
        if enemy_territory.test(from) {
            let mut squares = destinations;
            while let Some(to) = squares.pop_lsb() {
                push_promote(list, from, to);
                push_move(list, from, to);
            }
            continue;
        }

        let mut promotions = destinations.and(enemy_territory);
        while let Some(to) = promotions.pop_lsb() {
            push_promote(list, from, to);
            push_move(list, from, to);
        }

        let mut non_promotions = destinations.and_not(enemy_territory);
        while let Some(to) = non_promotions.pop_lsb() {
            push_move(list, from, to);
        }
    }
}

/// 金相当 + 馬/龍 + 玉の移動手を生成
#[allow(clippy::extra_unused_type_parameters)]
pub(super) fn generate_gold_hdk_moves<T: MoveGenType>(
    pos: &Position,
    list: &mut impl MoveSink,
    target: Bitboard,
    occupied: Bitboard,
    us: Color,
) {
    use crate::board::attack_tables::{
        bishop_attacks, gold_attacks_unchecked, king_attacks_unchecked, rook_attacks,
    };

    let bb = pos.bitboards();
    let color_mask = bb.color_pieces(us);

    // 金相当
    let mut golds = bb.golds() & color_mask;
    while let Some(from) = golds.pop_lsb() {
        let attacks = gold_attacks_unchecked(from, us);
        let mut destinations = attacks.and(target);
        while let Some(to) = destinations.pop_lsb() {
            push_move(list, from, to);
        }
    }

    // 玉
    let mut kings = bb.pieces_for(PieceType::KING, us);
    while let Some(from) = kings.pop_lsb() {
        let attacks = king_attacks_unchecked(from);
        let mut destinations = attacks.and(target);
        while let Some(to) = destinations.pop_lsb() {
            push_move(list, from, to);
        }
    }

    // 馬
    let mut horses = bb.pieces_for(PieceType::HORSE, us);
    while let Some(from) = horses.pop_lsb() {
        let bishop_moves = bishop_attacks(from, occupied);
        let king_moves = king_attacks_unchecked(from);
        let attacks = bishop_moves.or(king_moves.and_not(bishop_moves));
        let mut destinations = attacks.and(target);
        while let Some(to) = destinations.pop_lsb() {
            push_move(list, from, to);
        }
    }

    // 龍
    let mut dragons = bb.pieces_for(PieceType::DRAGON, us);
    while let Some(from) = dragons.pop_lsb() {
        let rook_moves = rook_attacks(from, occupied);
        let king_moves = king_attacks_unchecked(from);
        let attacks = rook_moves.or(king_moves.and_not(rook_moves));
        let mut destinations = attacks.and(target);
        while let Some(to) = destinations.pop_lsb() {
            push_move(list, from, to);
        }
    }
}

/// 金相当 + 馬/龍の移動手を生成（玉は除外）
#[allow(clippy::extra_unused_type_parameters)]
pub(super) fn generate_gold_hd_moves<T: MoveGenType>(
    pos: &Position,
    list: &mut impl MoveSink,
    target: Bitboard,
    occupied: Bitboard,
    us: Color,
) {
    use crate::board::attack_tables::{
        bishop_attacks, gold_attacks_unchecked, king_attacks_unchecked, rook_attacks,
    };

    let bb = pos.bitboards();
    let color_mask = bb.color_pieces(us);

    // 金相当
    let mut golds = bb.golds() & color_mask;
    while let Some(from) = golds.pop_lsb() {
        let attacks = gold_attacks_unchecked(from, us);
        let mut destinations = attacks.and(target);
        while let Some(to) = destinations.pop_lsb() {
            push_move(list, from, to);
        }
    }

    // 馬
    let mut horses = bb.pieces_for(PieceType::HORSE, us);
    while let Some(from) = horses.pop_lsb() {
        let bishop_moves = bishop_attacks(from, occupied);
        let king_moves = king_attacks_unchecked(from);
        let attacks = bishop_moves.or(king_moves.and_not(bishop_moves));
        let mut destinations = attacks.and(target);
        while let Some(to) = destinations.pop_lsb() {
            push_move(list, from, to);
        }
    }

    // 龍
    let mut dragons = bb.pieces_for(PieceType::DRAGON, us);
    while let Some(from) = dragons.pop_lsb() {
        let rook_moves = rook_attacks(from, occupied);
        let king_moves = king_attacks_unchecked(from);
        let attacks = rook_moves.or(king_moves.and_not(rook_moves));
        let mut destinations = attacks.and(target);
        while let Some(to) = destinations.pop_lsb() {
            push_move(list, from, to);
        }
    }
}

/// 香車の移動手を生成
#[allow(clippy::extra_unused_type_parameters)]
pub(super) fn generate_lance_moves<T: MoveGenType>(
    pos: &Position,
    list: &mut impl MoveSink,
    target: Bitboard,
    occupied: Bitboard,
    us: Color,
) {
    use crate::board::attack_tables::lance_attacks;
    use crate::types::Rank;

    let bb = pos.bitboards();
    let mut lances = bb.pieces_for(PieceType::LANCE, us);
    let allow_underpromote = T::generate_all_legal();
    let enemy_territory = match us {
        Color::BLACK => {
            Bitboard::rank_mask(Rank::RANK_1)
                | Bitboard::rank_mask(Rank::RANK_2)
                | Bitboard::rank_mask(Rank::RANK_3)
        }
        Color::WHITE => {
            Bitboard::rank_mask(Rank::RANK_7)
                | Bitboard::rank_mask(Rank::RANK_8)
                | Bitboard::rank_mask(Rank::RANK_9)
        }
    };
    let non_promote_mask = if allow_underpromote {
        if us == Color::BLACK {
            Bitboard::ALL.and_not(Bitboard::rank_mask(Rank::RANK_1))
        } else {
            Bitboard::ALL.and_not(Bitboard::rank_mask(Rank::RANK_9))
        }
    } else if us == Color::BLACK {
        Bitboard::ALL
            .and_not(Bitboard::rank_mask(Rank::RANK_1))
            .and_not(Bitboard::rank_mask(Rank::RANK_2))
    } else {
        Bitboard::ALL
            .and_not(Bitboard::rank_mask(Rank::RANK_8))
            .and_not(Bitboard::rank_mask(Rank::RANK_9))
    };

    while let Some(from) = lances.pop_lsb() {
        let attacks = lance_attacks(from, occupied, us);
        let destinations = attacks.and(target);

        let mut promotions = destinations.and(enemy_territory);
        while let Some(to) = promotions.pop_lsb() {
            push_promote(list, from, to);
        }

        let mut non_promotions = destinations.and(non_promote_mask);
        while let Some(to) = non_promotions.pop_lsb() {
            if must_promote(PieceType::LANCE, to, us) {
                continue;
            }
            push_move(list, from, to);
        }
    }
}

/// 角・飛車の移動手を生成
#[allow(clippy::extra_unused_type_parameters)]
pub(super) fn generate_br_moves<T: MoveGenType + 'static>(
    pos: &Position,
    list: &mut impl MoveSink,
    target: Bitboard,
    occupied: Bitboard,
    us: Color,
) {
    use crate::board::attack_tables::{bishop_attacks, rook_attacks};
    use crate::types::Rank;

    let bb = pos.bitboards();
    let allow_underpromote = T::generate_all_legal();
    let enemy_territory = match us {
        Color::BLACK => {
            Bitboard::rank_mask(Rank::RANK_1)
                | Bitboard::rank_mask(Rank::RANK_2)
                | Bitboard::rank_mask(Rank::RANK_3)
        }
        Color::WHITE => {
            Bitboard::rank_mask(Rank::RANK_7)
                | Bitboard::rank_mask(Rank::RANK_8)
                | Bitboard::rank_mask(Rank::RANK_9)
        }
    };

    // 角と飛車を分けて回すことで `piece_on()` と分岐を避ける（ホットパス）。
    let mut bishops = bb.pieces_for(PieceType::BISHOP, us);
    while let Some(from) = bishops.pop_lsb() {
        let attacks = bishop_attacks(from, occupied);
        let destinations = attacks.and(target);
        let from_in_enemy = enemy_territory.test(from);

        if from_in_enemy {
            let mut squares = destinations;
            while let Some(to) = squares.pop_lsb() {
                push_promote(list, from, to);
                if allow_underpromote {
                    push_move(list, from, to);
                }
            }
            continue;
        }

        let mut promotions = destinations.and(enemy_territory);
        while let Some(to) = promotions.pop_lsb() {
            push_promote(list, from, to);
            if allow_underpromote {
                push_move(list, from, to);
            }
        }

        let mut non_promotions = destinations.and_not(enemy_territory);
        while let Some(to) = non_promotions.pop_lsb() {
            push_move(list, from, to);
        }
    }

    let mut rooks = bb.pieces_for(PieceType::ROOK, us);
    while let Some(from) = rooks.pop_lsb() {
        let attacks = rook_attacks(from, occupied);
        let destinations = attacks.and(target);
        let from_in_enemy = enemy_territory.test(from);

        if from_in_enemy {
            let mut squares = destinations;
            while let Some(to) = squares.pop_lsb() {
                push_promote(list, from, to);
                if allow_underpromote {
                    push_move(list, from, to);
                }
            }
            continue;
        }

        let mut promotions = destinations.and(enemy_territory);
        while let Some(to) = promotions.pop_lsb() {
            push_promote(list, from, to);
            if allow_underpromote {
                push_move(list, from, to);
            }
        }

        let mut non_promotions = destinations.and_not(enemy_territory);
        while let Some(to) = non_promotions.pop_lsb() {
            push_move(list, from, to);
        }
    }
}
