//! 局面（Position）

use super::board_effect::{
    BoardEffects, LongEffects, compute_board_effects_and_long_effects, rewind_by_capturing_piece,
    rewind_by_dropping_piece, rewind_by_no_capturing_piece, update_by_capturing_piece,
    update_by_dropping_piece, update_by_no_capturing_piece,
};
use super::state::StateInfo;
use super::zobrist::{zobrist_hand, zobrist_pass_rights, zobrist_psq, zobrist_side};
use crate::bitboard::{
    Bitboard, RANK_BB, bishop_effect, dragon_effect, gold_effect, horse_effect, king_effect,
    knight_effect, lance_effect, lance_step_effect, pawn_effect, rook_effect, silver_effect,
};
use crate::eval::material::{hand_piece_value, material_needs_board_effects, signed_piece_value};
use crate::nnue::piece_list::PieceList;
use crate::nnue::{ChangedBonaPiece, DirtyPiece, ExtBonaPiece};
use crate::prefetch::{NoPrefetch, TtPrefetch};
use crate::types::{
    Color, EnteringKingRule, File, Hand, Move, Piece, PieceType, PieceTypeSet, Rank,
    RepetitionState, Square, Value,
};

/// 小駒（香・桂・銀・金とその成り駒）かどうか
#[inline]
pub(super) fn is_minor_piece(pc: Piece) -> bool {
    matches!(
        pc.piece_type(),
        PieceType::Lance
            | PieceType::Knight
            | PieceType::Silver
            | PieceType::Gold
            | PieceType::ProPawn
            | PieceType::ProLance
            | PieceType::ProKnight
            | PieceType::ProSilver
    )
}

/// 将棋の局面
#[derive(Clone)]
pub struct Position {
    // === 盤面 ===
    /// 各マスの駒 [Square]
    pub(super) board: [Piece; Square::NUM],
    /// 駒種別Bitboard [PieceType]
    pub(super) by_type: [Bitboard; PieceType::NUM + 1],
    /// 先後別Bitboard
    pub(super) by_color: [Bitboard; Color::NUM],

    // === 盤面効果 ===
    /// 各升の利き数 [Color][Square]
    board_effects: BoardEffects,
    /// 長い利きの方向（盤面効果の差分更新用）
    long_effects: LongEffects,
    /// board_effectsが最新かどうか（手動配置時の整合性維持用）
    board_effects_dirty: bool,

    // === 合成Bitboard（attackers_to_occ最適化用）===
    /// 金相当の駒（Gold | ProPawn | ProLance | ProKnight | ProSilver）
    golds_bb: Bitboard,
    /// 角・馬（Bishop | Horse）
    bishop_horse_bb: Bitboard,
    /// 飛・龍（Rook | Dragon）
    rook_dragon_bb: Bitboard,
    /// Horse | Dragon | King（SILVER_HDK / GOLDS_HDK 計算用）
    hdk_bb: Bitboard,

    // === 手駒 ===
    /// 手駒 [Color]
    pub(super) hand: [Hand; Color::NUM],

    // === 状態 ===
    /// 状態スタック
    pub(super) state_stack: Vec<StateInfo>,
    /// 現在の状態インデックス
    state_idx: usize,
    /// 初期局面からの手数
    pub(super) game_ply: i32,
    /// 手番
    pub(super) side_to_move: Color,
    /// 玉の位置 [Color]
    pub(super) king_square: [Square; Color::NUM],
    /// パス権ルールが有効かどうか
    pass_rights_enabled: bool,

    // === PieceList (NNUE 高速化) ===
    /// 全40駒の BonaPiece 管理テーブル
    pub(super) piece_list: PieceList,
}

impl Position {
    /// 部分ハッシュを更新（XOR）
    #[inline]
    fn xor_partial_keys(&self, st: &mut StateInfo, pc: Piece, sq: Square) {
        if pc.piece_type() == PieceType::Pawn {
            st.pawn_key ^= zobrist_psq(pc, sq);
        } else {
            if is_minor_piece(pc) {
                st.minor_piece_key ^= zobrist_psq(pc, sq);
            }
            st.non_pawn_key[pc.color().index()] ^= zobrist_psq(pc, sq);
        }
    }

    #[inline]
    fn cur_state(&self) -> &StateInfo {
        debug_assert!(self.state_idx < self.state_stack.len());
        // SAFETY: state_idx は push_state で設定され、常に state_stack.len() 未満。
        //         state_stack は do_move で push / undo_move で pop され、不変条件を維持する。
        unsafe { self.state_stack.get_unchecked(self.state_idx) }
    }

    #[inline]
    fn cur_state_mut(&mut self) -> &mut StateInfo {
        debug_assert!(self.state_idx < self.state_stack.len());
        // SAFETY: state_idx は push_state で設定され、常に state_stack.len() 未満。
        unsafe { self.state_stack.get_unchecked_mut(self.state_idx) }
    }

    /// 状態スタックに新しい StateInfo を積む（必要なら再利用）
    #[inline]
    fn push_state(&mut self, mut st: StateInfo) {
        let next_idx = self.state_idx + 1;
        st.previous = self.state_idx;
        if self.state_stack.len() > next_idx {
            // SAFETY: 上の条件で next_idx < state_stack.len() を保証済み。
            unsafe { *self.state_stack.get_unchecked_mut(next_idx) = st };
        } else {
            self.state_stack.push(st);
        }
        self.state_idx = next_idx;
    }

    // ========== 局面設定 ==========

    /// 空の局面を生成
    pub fn new() -> Self {
        Position {
            board: [Piece::NONE; Square::NUM],
            by_type: [Bitboard::EMPTY; PieceType::NUM + 1],
            by_color: [Bitboard::EMPTY; Color::NUM],
            board_effects: BoardEffects::new(),
            long_effects: LongEffects::new(),
            board_effects_dirty: false,
            golds_bb: Bitboard::EMPTY,
            bishop_horse_bb: Bitboard::EMPTY,
            rook_dragon_bb: Bitboard::EMPTY,
            hdk_bb: Bitboard::EMPTY,
            hand: [Hand::EMPTY; Color::NUM],
            state_stack: vec![StateInfo::new()],
            state_idx: 0,
            game_ply: 0,
            side_to_move: Color::Black,
            king_square: [Square::SQ_11; Color::NUM],
            pass_rights_enabled: false,
            piece_list: PieceList::new(),
        }
    }

    // ========== 盤面アクセス ==========

    /// 指定マスの駒を取得
    #[inline]
    pub fn piece_on(&self, sq: Square) -> Piece {
        debug_assert!(sq.index() < Square::NUM);
        // SAFETY: Square::index() は 0..=80 (Square::NUM=81)、board の長さは Square::NUM。
        unsafe { *self.board.get_unchecked(sq.index()) }
    }

    /// 直前の手で取られた駒を返す
    ///
    /// pos.captured_piece()
    #[inline]
    pub fn captured_piece(&self) -> Piece {
        self.cur_state().captured_piece
    }

    /// 全駒のBitboard（占有）
    #[inline]
    pub fn occupied(&self) -> Bitboard {
        // SAFETY: Color::Black.index()=0, Color::White.index()=1、by_color の長さは Color::NUM=2。
        unsafe {
            *self.by_color.get_unchecked(Color::Black.index())
                | *self.by_color.get_unchecked(Color::White.index())
        }
    }

    /// 指定駒種のBitboard
    #[inline]
    pub fn pieces_pt(&self, pt: PieceType) -> Bitboard {
        debug_assert!((pt as usize) < self.by_type.len());
        // SAFETY: PieceType は 0..=14、by_type の長さは PieceType::NUM+1=15。
        unsafe { *self.by_type.get_unchecked(pt as usize) }
    }

    /// 指定手番の駒のBitboard
    #[inline]
    pub fn pieces_c(&self, c: Color) -> Bitboard {
        debug_assert!(c.index() < Color::NUM);
        // SAFETY: Color::index() は 0..=1、by_color の長さは Color::NUM=2。
        unsafe { *self.by_color.get_unchecked(c.index()) }
    }

    /// 指定手番・駒種のBitboard
    #[inline]
    pub fn pieces(&self, c: Color, pt: PieceType) -> Bitboard {
        debug_assert!(c.index() < Color::NUM);
        debug_assert!((pt as usize) < self.by_type.len());
        // SAFETY: Color::index() は 0..=1、by_color の長さは Color::NUM=2。
        //         PieceType は 0..=14、by_type の長さは PieceType::NUM+1=15。
        unsafe {
            *self.by_color.get_unchecked(c.index()) & *self.by_type.get_unchecked(pt as usize)
        }
    }

    /// 駒種集合のBitboard（先後無視）
    #[inline]
    pub fn pieces_by_types(&self, set: PieceTypeSet) -> Bitboard {
        if set.is_empty() {
            return Bitboard::EMPTY;
        }
        if set.is_all() {
            return self.occupied();
        }

        let mut bb = Bitboard::EMPTY;
        for pt in set.iter() {
            bb |= self.by_type[pt as usize];
        }
        bb
    }

    /// 駒種集合のBitboard（手番指定）
    #[inline]
    pub fn pieces_c_by_types(&self, c: Color, set: PieceTypeSet) -> Bitboard {
        if set.is_empty() {
            return Bitboard::EMPTY;
        }
        if set.is_all() {
            return self.by_color[c.index()];
        }

        let mut bb = Bitboard::EMPTY;
        for pt in set.iter() {
            bb |= self.by_type[pt as usize] & self.by_color[c.index()];
        }
        bb
    }

    // ========== 合成Bitboardアクセサ ==========

    /// 駒種が金相当（金、と、成香、成桂、成銀）かどうか
    #[inline]
    const fn is_gold_like(pt: PieceType) -> bool {
        matches!(
            pt,
            PieceType::Gold
                | PieceType::ProPawn
                | PieceType::ProLance
                | PieceType::ProKnight
                | PieceType::ProSilver
        )
    }

    /// 駒種が角・馬かどうか
    #[inline]
    const fn is_bishop_like(pt: PieceType) -> bool {
        matches!(pt, PieceType::Bishop | PieceType::Horse)
    }

    /// 駒種が飛・龍かどうか
    #[inline]
    const fn is_rook_like(pt: PieceType) -> bool {
        matches!(pt, PieceType::Rook | PieceType::Dragon)
    }

    /// 駒種が馬・龍・玉かどうか（HDK合成Bitboard用）
    #[inline]
    const fn is_hdk(pt: PieceType) -> bool {
        matches!(pt, PieceType::Horse | PieceType::Dragon | PieceType::King)
    }

    /// 金相当の駒のBitboard（先後両方）
    #[inline]
    pub fn golds(&self) -> Bitboard {
        self.golds_bb
    }

    /// 金相当の駒のBitboard（手番指定）
    #[inline]
    pub fn golds_c(&self, c: Color) -> Bitboard {
        self.golds_bb & self.by_color[c.index()]
    }

    /// 角・馬のBitboard
    #[inline]
    pub fn bishop_horse(&self) -> Bitboard {
        self.bishop_horse_bb
    }

    /// 飛・龍のBitboard
    #[inline]
    pub fn rook_dragon(&self) -> Bitboard {
        self.rook_dragon_bb
    }

    /// 手駒を取得
    #[inline]
    pub fn hand(&self, c: Color) -> Hand {
        self.hand[c.index()]
    }

    /// 玉の位置を取得
    #[inline]
    pub fn king_square(&self, c: Color) -> Square {
        self.king_square[c.index()]
    }

    /// PieceList への参照を取得
    #[inline]
    pub fn piece_list(&self) -> &PieceList {
        &self.piece_list
    }

    /// 手番を取得
    #[inline]
    pub fn side_to_move(&self) -> Color {
        self.side_to_move
    }

    /// TT等に保存された16bit指し手を安全に取り出す
    /// - 無効な符号化や手番不一致の手はNone
    /// - 合法性までは保証しないが、明らかに不整合な手を弾く
    /// - 駒情報（moved_piece_after）を上位16bitに付加して返す
    pub fn to_move(&self, mv: Move) -> Option<Move> {
        // 下位16bitの符号化を先に検証する。
        // TT競合で壊れた move16 をここで弾き、probe() 側でcontinueできるようにする。
        Move::from_u16_checked(mv.raw())?;

        if mv.is_none() {
            return Some(Move::NONE);
        }

        if mv.is_drop() {
            // 打ち駒の持ち駒有無はチェックしない。
            // TT衝突で別局面のmoveが入っている場合でも、move自体は返す。
            // 探索時のlegality checkで弾かれるため問題ない。
            let pt = mv.drop_piece_type();
            let dropped_pc = Piece::make(self.side_to_move, pt);
            Some(mv.with_piece(dropped_pc))
        } else {
            let from = mv.from();
            let pc = self.piece_on(from);
            if pc.is_some() && pc.color() == self.side_to_move {
                // 成りフラグが立っている場合、その駒種が成れるかをチェック
                // ハッシュ衝突等で不正な成りフラグを持つ指し手を弾く
                if mv.is_promote() && !pc.piece_type().can_promote() {
                    return None;
                }
                // 駒情報を付加
                let moved_pc = if mv.is_promote() {
                    // 229-231行目でcan_promote()を検証済みのため安全
                    pc.promote().expect("already validated can_promote")
                } else {
                    pc
                };
                Some(mv.with_piece(moved_pc))
            } else {
                None
            }
        }
    }

    /// 手数を取得
    #[inline]
    pub fn game_ply(&self) -> i32 {
        self.game_ply
    }

    /// 千日手/優劣局面判定（do_move 時に計算した情報を使用）
    ///
    /// `rep < ply` で判定する（`rep.abs() < ply` ではない）。
    /// - `rep > 0`: 通常の千日手。`rep` は何手前に同一局面があったかを表す。
    ///   `rep < ply` でルートより前の局面との千日手を除外する。
    /// - `rep < 0`: 連続王手の千日手（4回目以降）。負値は常に `ply`（正値）より小さいため
    ///   無条件で検出される。`rep.abs()` にすると連続王手千日手を見逃す。
    pub fn repetition_state(&self, ply: i32) -> RepetitionState {
        let rep = self.cur_state().repetition;
        if rep != 0 && rep < ply {
            return self.cur_state().repetition_type;
        }

        RepetitionState::None
    }

    /// 現在の状態を取得
    #[inline]
    pub fn state(&self) -> &StateInfo {
        self.cur_state()
    }

    /// 直前の局面の状態（StateInfo）を取得
    pub fn previous_state(&self) -> Option<&StateInfo> {
        self.cur_state().previous_index().map(|idx| &self.state_stack[idx])
    }

    /// 任意のインデックスのStateInfoを取得（NNUE祖先探索用）
    #[inline]
    pub fn state_at(&self, idx: usize) -> &StateInfo {
        &self.state_stack[idx]
    }

    /// 現在のstate_indexを取得（NNUE祖先探索用）
    #[inline]
    pub fn state_index(&self) -> usize {
        self.state_idx
    }

    /// 現在の状態を可変で取得（NNUE差分更新など内部状態の更新用）
    #[inline]
    pub fn state_mut(&mut self) -> &mut StateInfo {
        self.cur_state_mut()
    }

    /// 局面のハッシュキー
    #[inline]
    pub fn key(&self) -> u64 {
        self.cur_state().key()
    }

    /// 盤面の利き数を取得
    #[inline]
    pub fn board_effect(&self, color: Color, sq: Square) -> u8 {
        debug_assert!(!self.board_effects_dirty, "board_effects is dirty");
        self.board_effects.effect(color, sq)
    }

    /// 盤面の利き数マップを取得
    #[inline]
    pub(crate) fn board_effects(&self) -> &BoardEffects {
        debug_assert!(!self.board_effects_dirty, "board_effects is dirty");
        &self.board_effects
    }

    #[inline]
    fn should_update_board_effects() -> bool {
        if !crate::eval::is_material_enabled() && crate::nnue::is_nnue_initialized() {
            return false;
        }
        material_needs_board_effects()
    }

    #[inline]
    fn ensure_board_effects(&mut self) {
        if self.board_effects_dirty {
            self.recompute_board_effects();
        }
    }

    pub(crate) fn recompute_board_effects(&mut self) {
        let (effects, long_effects) = compute_board_effects_and_long_effects(self);
        self.board_effects = effects;
        self.long_effects = long_effects;
        self.board_effects_dirty = false;
    }

    #[cfg(debug_assertions)]
    fn debug_verify_board_effects(&self) {
        let (expected, expected_long) = compute_board_effects_and_long_effects(self);
        if expected != self.board_effects {
            for color in [Color::Black, Color::White] {
                for sq in Square::all() {
                    let actual = self.board_effects.effect(color, sq);
                    let want = expected.effect(color, sq);
                    if actual != want {
                        eprintln!(
                            "board_effect mismatch: color={color:?}, sq={sq:?}, actual={actual}, expected={want}, sfen={}",
                            self.to_sfen()
                        );
                        break;
                    }
                }
            }
            panic!("board_effect mismatch");
        }
        if expected_long != self.long_effects {
            for sq in Square::all() {
                let actual = self.long_effects.long_effect16(sq);
                let want = expected_long.long_effect16(sq);
                if actual != want {
                    eprintln!(
                        "long_effect mismatch: sq={sq:?}, actual=0x{actual:04x}, expected=0x{want:04x}, sfen={}",
                        self.to_sfen()
                    );
                    break;
                }
            }
            panic!("long_effect mismatch");
        }
    }

    /// 歩ハッシュ
    #[inline]
    pub fn pawn_key(&self) -> u64 {
        self.cur_state().pawn_key
    }

    /// 小駒ハッシュ
    #[inline]
    pub fn minor_piece_key(&self) -> u64 {
        self.cur_state().minor_piece_key
    }

    /// 歩以外のハッシュ（手番別）
    #[inline]
    pub fn non_pawn_key(&self, c: Color) -> u64 {
        self.cur_state().non_pawn_key[c.index()]
    }

    // ========== 利き計算 ==========

    /// 指定マスに利いている駒（全手番）
    pub fn attackers_to(&self, sq: Square) -> Bitboard {
        self.attackers_to_occ(sq, self.occupied())
    }

    /// 指定マスに利いている駒（占有指定）
    ///
    /// Apery/YaneuraOu式: silverEffect で HDK の斜め近接利き、
    /// goldEffect で HDK の直線近接利きを捕捉し、
    /// 個別の king_effect / horse近接 / dragon近接 を不要にする。
    /// また rook_effect を再利用して lance_effect の個別スライド計算を省略。
    pub fn attackers_to_occ(&self, sq: Square, occupied: Bitboard) -> Bitboard {
        let silver_hdk = self.pieces_pt(PieceType::Silver) | self.hdk_bb;
        let golds_hdk = self.golds_bb | self.hdk_bb;

        // 先手の攻め駒: 後手方向の effect で逆引き → pieces_c(Black) でフィルタ
        let black_attackers = ((pawn_effect(Color::White, sq) & self.pieces_pt(PieceType::Pawn))
            | (knight_effect(Color::White, sq) & self.pieces_pt(PieceType::Knight))
            | (silver_effect(Color::White, sq) & silver_hdk)
            | (gold_effect(Color::White, sq) & golds_hdk))
            & self.pieces_c(Color::Black);

        // 後手の攻め駒: 先手方向の effect で逆引き → pieces_c(White) でフィルタ
        let white_attackers = ((pawn_effect(Color::Black, sq) & self.pieces_pt(PieceType::Pawn))
            | (knight_effect(Color::Black, sq) & self.pieces_pt(PieceType::Knight))
            | (silver_effect(Color::Black, sq) & silver_hdk)
            | (gold_effect(Color::Black, sq) & golds_hdk))
            & self.pieces_c(Color::White);

        // 角・馬のスライド利き
        let bishop = bishop_effect(sq, occupied) & self.bishop_horse_bb;

        // 飛・龍 + 香: rookEffect を再利用して lance_effect の個別計算を省略
        let rook_eff = rook_effect(sq, occupied);
        let rook_lance = rook_eff
            & (self.rook_dragon_bb
                | (lance_step_effect(Color::White, sq)
                    & self.pieces(Color::Black, PieceType::Lance))
                | (lance_step_effect(Color::Black, sq)
                    & self.pieces(Color::White, PieceType::Lance)));

        black_attackers | white_attackers | bishop | rook_lance
    }

    /// 指定マスに利いている指定手番の駒
    pub fn attackers_to_c(&self, sq: Square, c: Color) -> Bitboard {
        self.attackers_to_occ(sq, self.occupied()) & self.pieces_c(c)
    }

    /// 自玉へのピン駒
    #[inline]
    pub fn blockers_for_king(&self, c: Color) -> Bitboard {
        self.cur_state().blockers_for_king[c.index()]
    }

    /// 王手している駒
    #[inline]
    pub fn checkers(&self) -> Bitboard {
        self.cur_state().checkers
    }

    /// 王手されているか
    #[inline]
    pub fn in_check(&self) -> bool {
        !self.cur_state().checkers.is_empty()
    }

    /// 指定駒種で王手となる升
    #[inline]
    pub fn check_squares(&self, pt: PieceType) -> Bitboard {
        self.cur_state().check_squares[pt as usize]
    }

    /// 現在のpin状態（指定升を除外）
    pub fn pinned_pieces(&self, them: Color, avoid: Square) -> Bitboard {
        self.pinned_pieces_excluding(them, avoid)
    }

    /// fromを取り除いた占有でのpin駒（やねうら王のpinned_pieces<Them>(from)相当）
    ///
    /// avoid升の駒をoccupiedとpinner候補の両方から除外する。
    /// update_slider_blockers相当のcompute_blockers_and_pinnersとは異なり、
    /// sniper同士の除外は行わない（YOのpinned_pieces<C>(avoid)に準拠）。
    pub fn pinned_pieces_excluding(&self, them: Color, avoid: Square) -> Bitboard {
        let avoid_bb = Bitboard::from_square(avoid);
        let avoid_not = !avoid_bb;
        let ksq = self.king_square[them.index()];
        let enemy = !them;

        let lance_bb = self.pieces(enemy, PieceType::Lance) & avoid_not;
        let bishop_bb = (self.bishop_horse_bb & self.by_color[enemy.index()]) & avoid_not;
        let rook_bb = (self.rook_dragon_bb & self.by_color[enemy.index()]) & avoid_not;

        let pinners = (lance_effect(them, ksq, Bitboard::EMPTY) & lance_bb)
            | (bishop_effect(ksq, Bitboard::EMPTY) & bishop_bb)
            | (rook_effect(ksq, Bitboard::EMPTY) & rook_bb);

        let pieces_without_avoid = self.occupied() & avoid_not;
        let mut result = Bitboard::EMPTY;
        for pinner_sq in pinners.iter() {
            let between = crate::bitboard::between_bb(ksq, pinner_sq) & pieces_without_avoid;
            if !between.is_empty() && !between.more_than_one() {
                result |= between & self.pieces_c(them);
            }
        }
        result
    }

    /// fromの駒を動かしたときに開き王手になるか（簡易判定）
    pub fn discovered(&self, from: Square, to: Square, ksq: Square, pinned: Bitboard) -> bool {
        pinned.contains(from) && !crate::mate::aligned(from, to, ksq)
    }

    // ========== 内部操作 ==========

    /// 盤面に駒を置く
    pub(super) fn put_piece(&mut self, pc: Piece, sq: Square) {
        self.put_piece_internal(pc, sq);
        self.board_effects_dirty = true;
    }

    fn put_piece_internal(&mut self, pc: Piece, sq: Square) {
        debug_assert!(sq.index() < Square::NUM);
        debug_assert!(self.board[sq.index()].is_none());
        let pt = pc.piece_type();

        // SAFETY: Square::index() は 0..=80 (Square::NUM=81)、board の長さは Square::NUM。
        unsafe { *self.board.get_unchecked_mut(sq.index()) = pc };
        debug_assert!((pt as usize) < self.by_type.len());
        debug_assert!(pc.color().index() < Color::NUM);
        // SAFETY: PieceType は 0..=14、by_type の長さは PieceType::NUM+1=15。
        //         Color::index() は 0..=1、by_color の長さは Color::NUM=2。
        unsafe { self.by_type.get_unchecked_mut(pt as usize) }.set(sq);
        unsafe { self.by_color.get_unchecked_mut(pc.color().index()) }.set(sq);

        // 合成Bitboardの差分更新
        if Self::is_gold_like(pt) {
            self.golds_bb.set(sq);
        } else if Self::is_bishop_like(pt) {
            self.bishop_horse_bb.set(sq);
        } else if Self::is_rook_like(pt) {
            self.rook_dragon_bb.set(sq);
        }
        if Self::is_hdk(pt) {
            self.hdk_bb.set(sq);
        }
    }

    /// 盤面から駒を取り除く
    #[cfg(test)]
    fn remove_piece(&mut self, sq: Square) {
        self.remove_piece_internal(sq);
        self.board_effects_dirty = true;
    }

    fn remove_piece_internal(&mut self, sq: Square) {
        debug_assert!(sq.index() < Square::NUM);
        // SAFETY: Square::index() は 0..=80 (Square::NUM=81)、board の長さは Square::NUM。
        let pc = unsafe { *self.board.get_unchecked(sq.index()) };
        debug_assert!(pc.is_some());
        let pt = pc.piece_type();

        unsafe { *self.board.get_unchecked_mut(sq.index()) = Piece::NONE };
        debug_assert!((pt as usize) < self.by_type.len());
        debug_assert!(pc.color().index() < Color::NUM);
        // SAFETY: PieceType は 0..=14、by_type の長さは PieceType::NUM+1=15。
        //         Color::index() は 0..=1、by_color の長さは Color::NUM=2。
        unsafe { self.by_type.get_unchecked_mut(pt as usize) }.clear(sq);
        unsafe { self.by_color.get_unchecked_mut(pc.color().index()) }.clear(sq);

        // 合成Bitboardの差分更新
        if Self::is_gold_like(pt) {
            self.golds_bb.clear(sq);
        } else if Self::is_bishop_like(pt) {
            self.bishop_horse_bb.clear(sq);
        } else if Self::is_rook_like(pt) {
            self.rook_dragon_bb.clear(sq);
        }
        if Self::is_hdk(pt) {
            self.hdk_bb.clear(sq);
        }
    }

    /// pin駒とpinしている駒を更新
    pub(super) fn update_blockers_and_pinners(&mut self) {
        for c in [Color::Black, Color::White] {
            let (blockers, pinners) =
                self.compute_blockers_and_pinners(c, self.occupied(), Bitboard::EMPTY);
            let st = self.cur_state_mut();
            st.blockers_for_king[c.index()] = blockers;
            st.pinners[c.index()] = pinners;
        }
    }

    /// 占有を指定してpin候補とpinnerを再計算
    fn compute_blockers_and_pinners(
        &self,
        king_color: Color,
        occupied: Bitboard,
        enemy_removed: Bitboard,
    ) -> (Bitboard, Bitboard) {
        let ksq = self.king_square[king_color.index()];
        let enemy = !king_color;

        let lance_bb = self.pieces(enemy, PieceType::Lance) & !enemy_removed;
        // 事前計算済みのbishop_horse_bb/rook_dragon_bbを使用
        let bishop_bb = (self.bishop_horse_bb & self.by_color[enemy.index()]) & !enemy_removed;
        let rook_bb = (self.rook_dragon_bb & self.by_color[enemy.index()]) & !enemy_removed;

        let snipers = (lance_effect(king_color, ksq, Bitboard::EMPTY) & lance_bb)
            | (bishop_effect(ksq, Bitboard::EMPTY) & bishop_bb)
            | (rook_effect(ksq, Bitboard::EMPTY) & rook_bb);

        let mut blockers = Bitboard::EMPTY;
        let mut pinners = Bitboard::EMPTY;
        // sniper自身をoccupiedから除外して、一直線上に複数sniperがある場合
        // （例: 王-歩-飛-飛）でも遠い方のsniperのblocker/pinnerを正しく認識する
        let occ_without_snipers = occupied & !snipers;
        for sniper_sq in snipers.iter() {
            let between = crate::bitboard::between_bb(ksq, sniper_sq) & occ_without_snipers;
            if between.is_empty() || between.more_than_one() {
                continue;
            }

            // blockerが自駒のときのみpin対象
            if (between & self.pieces_c(enemy)).is_empty() {
                blockers |= between;
                pinners.set(sniper_sq);
            } else {
                blockers |= between;
            }
        }

        (blockers, pinners)
    }

    /// 王手マスを更新
    pub(super) fn update_check_squares(&mut self) {
        let them = !self.side_to_move;
        let ksq = self.king_square[them.index()];
        let occupied = self.occupied();
        let st = self.cur_state_mut();

        // 各駒種で王手となるマス
        // SAFETY: 全 PieceType 定数は 1..=14 (Pawn=1, Dragon=14)、
        //         check_squares の長さは PieceType::NUM+1=15 なので全インデックスが範囲内。
        unsafe {
            *st.check_squares.get_unchecked_mut(PieceType::Pawn as usize) = pawn_effect(them, ksq);
            *st.check_squares.get_unchecked_mut(PieceType::Knight as usize) =
                knight_effect(them, ksq);
            *st.check_squares.get_unchecked_mut(PieceType::Silver as usize) =
                silver_effect(them, ksq);
            *st.check_squares.get_unchecked_mut(PieceType::Gold as usize) = gold_effect(them, ksq);
            *st.check_squares.get_unchecked_mut(PieceType::King as usize) = Bitboard::EMPTY;
            *st.check_squares.get_unchecked_mut(PieceType::Lance as usize) =
                lance_effect(them, ksq, occupied);
            *st.check_squares.get_unchecked_mut(PieceType::Bishop as usize) =
                bishop_effect(ksq, occupied);
            *st.check_squares.get_unchecked_mut(PieceType::Rook as usize) =
                rook_effect(ksq, occupied);

            // 成駒
            *st.check_squares.get_unchecked_mut(PieceType::ProPawn as usize) =
                gold_effect(them, ksq);
            *st.check_squares.get_unchecked_mut(PieceType::ProLance as usize) =
                gold_effect(them, ksq);
            *st.check_squares.get_unchecked_mut(PieceType::ProKnight as usize) =
                gold_effect(them, ksq);
            *st.check_squares.get_unchecked_mut(PieceType::ProSilver as usize) =
                gold_effect(them, ksq);
            *st.check_squares.get_unchecked_mut(PieceType::Horse as usize) =
                horse_effect(ksq, occupied);
            *st.check_squares.get_unchecked_mut(PieceType::Dragon as usize) =
                dragon_effect(ksq, occupied);
        }
    }

    // ========== パス権 ==========

    /// 指定した手番のパス権残数を取得
    #[inline]
    pub fn pass_rights(&self, color: Color) -> u8 {
        self.cur_state().get_pass_rights(color)
    }

    /// パス権ルールが有効かどうか
    #[inline]
    pub fn is_pass_rights_enabled(&self) -> bool {
        self.pass_rights_enabled
    }

    /// 現在の手番がパス可能か
    ///
    /// 条件:
    /// - パス権ルールが有効
    /// - 王手されていない
    /// - パス権が残っている
    #[inline]
    pub fn can_pass(&self) -> bool {
        self.pass_rights_enabled && !self.in_check() && self.pass_rights(self.side_to_move) > 0
    }

    /// パス権ルールの有効/無効を設定
    ///
    /// 【重要】無効化時に状態を正規化
    /// - `enabled=false` のとき、pass_rights を (0,0) にリセット
    /// - これにより「無効なのにキー体系が異なる」経路を閉じる
    pub fn set_pass_rights_enabled(&mut self, enabled: bool) {
        self.pass_rights_enabled = enabled;

        // 無効化時は状態を正規化（キー互換性を保証）
        if !enabled {
            self.set_pass_rights_pair(0, 0);
        }
    }

    /// 両者のパス権をまとめて設定（初期化用）
    ///
    /// 差分更新により冪等性を保証（同じ値で2回呼んでもkeyは変わらない）
    pub(crate) fn set_pass_rights_pair(&mut self, black: u8, white: u8) {
        let black = black.min(15);
        let white = white.min(15);
        let st = self.cur_state_mut();

        let old_black = st.get_pass_rights(Color::Black);
        let old_white = st.get_pass_rights(Color::White);

        st.set_pass_rights_internal(Color::Black, black);
        st.set_pass_rights_internal(Color::White, white);

        // Zobrist差分更新
        st.board_key ^= zobrist_pass_rights(old_black, old_white);
        st.board_key ^= zobrist_pass_rights(black, white);
    }

    /// パス権付きで局面を初期化（外部向けAPI）
    ///
    /// # Arguments
    /// * `sfen` - SFEN文字列
    /// * `black_rights` - 先手のパス権（0-15）
    /// * `white_rights` - 後手のパス権（0-15）
    pub fn set_sfen_with_pass_rights(
        &mut self,
        sfen: &str,
        black_rights: u8,
        white_rights: u8,
    ) -> Result<(), super::sfen::SfenError> {
        self.set_sfen(sfen)?;
        self.set_pass_rights_enabled(true);
        self.set_pass_rights_pair(black_rights, white_rights);
        Ok(())
    }

    /// パス権ルールを有効化してパス権を設定（外部向けAPI）
    ///
    /// 既に set_sfen() や set_hirate() で局面を設定した後に呼ぶことを想定。
    pub fn enable_pass_rights(&mut self, black_rights: u8, white_rights: u8) {
        self.set_pass_rights_enabled(true);
        self.set_pass_rights_pair(black_rights, white_rights);
    }

    /// 平手初期局面をパス権付きで初期化（外部向けAPI）
    pub fn set_startpos_with_pass_rights(&mut self, black_rights: u8, white_rights: u8) {
        self.set_hirate();
        self.set_pass_rights_enabled(true);
        self.set_pass_rights_pair(black_rights, white_rights);
    }

    // ========== 指し手実行 ==========

    /// 指し手を実行
    ///
    /// DirtyPieceを返す。探索時はAccumulatorStackと同期して使用する。
    /// NNUE評価を使わない場合は無視して良い。
    ///
    /// PASSの場合は do_pass_move に委譲する。
    pub fn do_move(&mut self, m: Move, gives_check: bool) -> DirtyPiece {
        if m.is_pass() {
            return self.do_pass_move();
        }
        let noop = NoPrefetch;
        self.do_move_with_prefetch(m, gives_check, &noop)
    }

    pub(crate) fn do_move_with_prefetch<P: TtPrefetch>(
        &mut self,
        m: Move,
        gives_check: bool,
        prefetcher: &P,
    ) -> DirtyPiece {
        // PASSの場合は do_pass_move に委譲
        if m.is_pass() {
            return self.do_pass_move();
        }

        let us = self.side_to_move;
        let them = !us;
        let prev_continuous = self.cur_state().continuous_check;
        let update_board_effects = Self::should_update_board_effects();

        // 現在の占有とblockers/pinners、玉位置を退避（差分更新で利用）
        let prev_blockers = self.cur_state().blockers_for_king;
        let prev_pinners = self.cur_state().pinners;
        let prev_king_sq = self.king_square;

        // 1. 新しいStateInfoを作成（NNUE関連はAccumulatorStackで管理）
        let mut new_state = self.cur_state().partial_clone();
        // NNUE用のDirtyPieceはローカルで構築して返す
        let mut dirty_piece = DirtyPiece::new();
        let mut material_value = new_state.material_value.raw();

        // 2. 局面情報の更新
        self.game_ply += 1;
        new_state.plies_from_null += 1;

        // 3. 手番の変更とハッシュ更新
        new_state.board_key ^= zobrist_side();

        // 4. 駒の移動
        let mut moved_from: Option<Square> = None;
        let moved_to: Square;
        let moved_pt: PieceType;

        if update_board_effects {
            self.ensure_board_effects();
        } else {
            self.board_effects_dirty = true;
        }

        if m.is_drop() {
            let pt = m.drop_piece_type();
            let to = m.to();
            let pc = Piece::new(us, pt);
            moved_to = to;
            moved_pt = pt;

            if update_board_effects {
                let occupied_before = self.occupied();
                update_by_dropping_piece(
                    &mut self.board_effects,
                    &mut self.long_effects,
                    occupied_before,
                    to,
                    pc,
                );
            }

            // PieceList + DirtyPiece: 手駒 → 盤上
            let old_count = self.hand[us.index()].count(pt) as u8;
            let old_bp = ExtBonaPiece::from_hand(us, pt, old_count);
            let piece_no = self.piece_list.piece_no_of_hand(old_bp.fb);

            // 手駒から減らす
            self.hand[us.index()] = self.hand[us.index()].sub(pt);
            new_state.hand_key = new_state.hand_key.wrapping_sub(zobrist_hand(us, pt));

            // 盤上に配置
            self.put_piece_internal(pc, to);
            new_state.board_key ^= zobrist_psq(pc, to);
            self.xor_partial_keys(&mut new_state, pc, to);

            let new_bp = ExtBonaPiece::from_board(pc, to);
            self.piece_list.put_piece_on_board(piece_no, new_bp, to);

            dirty_piece.dirty_num = 1;
            dirty_piece.piece_no[0] = piece_no;
            dirty_piece.changed_piece[0] = ChangedBonaPiece {
                old_piece: old_bp,
                new_piece: new_bp,
            };

            new_state.captured_piece = Piece::NONE;
        } else {
            let from = m.from();
            let to = m.to();
            moved_from = Some(from);
            moved_to = to;
            let pc = self.piece_on(from);
            let captured = self.piece_on(to);

            debug_assert!(
                !m.is_promote() || pc.piece_type().can_promote(),
                "Cannot promote piece {pc:?} (type={:?}) at {from:?} with move {} in position {}\n\
                 move raw bits: 0x{:08x}, is_drop={}, is_promote={}",
                pc.piece_type(),
                m.to_usi(),
                self.to_sfen(),
                m.raw(),
                m.is_drop(),
                m.is_promote()
            );

            moved_pt = if m.is_promote() {
                pc.piece_type().promote().unwrap()
            } else {
                pc.piece_type()
            };
            let moved_after_pc = if m.is_promote() {
                pc.promote().unwrap()
            } else {
                pc
            };
            if update_board_effects {
                let occupied_before = self.occupied();
                if captured.is_some() {
                    update_by_capturing_piece(
                        &mut self.board_effects,
                        &mut self.long_effects,
                        occupied_before,
                        from,
                        to,
                        pc,
                        moved_after_pc,
                        captured,
                    );
                } else {
                    update_by_no_capturing_piece(
                        &mut self.board_effects,
                        &mut self.long_effects,
                        occupied_before,
                        from,
                        to,
                        pc,
                        moved_after_pc,
                    );
                }
            }

            // PieceList: 動く駒の old BonaPiece を記録
            let piece_no_moved = self.piece_list.piece_no_of_board(from);
            let old_bp_moved = self.piece_list.bona_piece(piece_no_moved);

            // 駒を取る場合
            if captured.is_some() {
                let captured_pt = captured.piece_type().unpromote();
                debug_assert!(
                    captured_pt != PieceType::King,
                    "illegal capture of king at {} by move {} in position {}",
                    to.to_usi(),
                    m.to_usi(),
                    self.to_sfen()
                );

                // PieceList: 取られた駒の old BonaPiece を記録
                let piece_no_cap = self.piece_list.piece_no_of_board(to);
                let old_bp_cap = self.piece_list.bona_piece(piece_no_cap);

                self.remove_piece_internal(to);
                new_state.board_key ^= zobrist_psq(captured, to);
                self.xor_partial_keys(&mut new_state, captured, to);

                // material_value: 盤上から駒が消える
                material_value -= signed_piece_value(captured);

                // 手駒に追加（成駒は生駒に戻す）
                if matches!(
                    captured_pt,
                    PieceType::Pawn
                        | PieceType::Lance
                        | PieceType::Knight
                        | PieceType::Silver
                        | PieceType::Gold
                        | PieceType::Bishop
                        | PieceType::Rook
                ) {
                    self.hand[us.index()] = self.hand[us.index()].add(captured_pt);
                    new_state.hand_key =
                        new_state.hand_key.wrapping_add(zobrist_hand(us, captured_pt));
                    material_value += hand_piece_value(us, captured_pt);
                }

                // PieceList: 取られた駒を手駒に移す
                let new_count = self.hand[us.index()].count(captured_pt) as u8;
                let new_bp_cap = ExtBonaPiece::from_hand(us, captured_pt, new_count);
                self.piece_list.put_piece_on_hand(piece_no_cap, new_bp_cap);

                // DirtyPiece: 取られた駒（[1]に記録）
                dirty_piece.piece_no[1] = piece_no_cap;
                dirty_piece.changed_piece[1] = ChangedBonaPiece {
                    old_piece: old_bp_cap,
                    new_piece: new_bp_cap,
                };
                dirty_piece.dirty_num = 2;
            } else {
                dirty_piece.dirty_num = 1;
            }
            new_state.captured_piece = captured;

            // 駒を移動
            self.remove_piece_internal(from);
            new_state.board_key ^= zobrist_psq(pc, from);
            self.xor_partial_keys(&mut new_state, pc, from);

            self.put_piece_internal(moved_after_pc, to);
            new_state.board_key ^= zobrist_psq(moved_after_pc, to);
            self.xor_partial_keys(&mut new_state, moved_after_pc, to);

            // 成りによるmaterial差分
            if moved_after_pc != pc {
                material_value += signed_piece_value(moved_after_pc) - signed_piece_value(pc);
            }

            // 玉の移動
            if pc.piece_type() == PieceType::King {
                self.king_square[us.index()] = to;
                dirty_piece.king_moved[us.index()] = true;
            }

            // PieceList: 動いた駒を新位置に更新
            let new_bp_moved = ExtBonaPiece::from_board(moved_after_pc, to);
            self.piece_list.put_piece_on_board(piece_no_moved, new_bp_moved, to);

            // DirtyPiece: 動いた駒（[0]に記録）
            dirty_piece.piece_no[0] = piece_no_moved;
            dirty_piece.changed_piece[0] = ChangedBonaPiece {
                old_piece: old_bp_moved,
                new_piece: new_bp_moved,
            };
        }

        // do_move直後にTTをprefetch
        prefetcher.prefetch(new_state.key(), them);

        // 6. 王手情報の更新（diffベース）
        let mut checkers = Bitboard::EMPTY;
        if gives_check {
            let ksq = self.king_square[them.index()];
            // 直接王手
            debug_assert!((moved_pt as usize) < PieceType::NUM + 1);
            // SAFETY: moved_pt は PieceType (0..=14)、check_squares の長さは PieceType::NUM+1=15。
            checkers |= unsafe { *self.cur_state().check_squares.get_unchecked(moved_pt as usize) }
                & Bitboard::from_square(moved_to);

            // 開き王手（動かした駒が遮断駒だった場合）
            // discovered(from, to, ksq, blockers) と同等の判定
            // - fromがblockersに含まれている
            // - from, to, ksq が同一直線上にない（aligned でない）場合のみ開き王手
            if let Some(from_sq) = moved_from {
                let prev_blockers = self.cur_state().blockers_for_king[them.index()];
                if prev_blockers.contains(from_sq)
                    && !crate::mate::aligned(from_sq, moved_to, ksq)
                    && let Some(dir) = crate::bitboard::direct_of(ksq, from_sq)
                {
                    let ray = crate::bitboard::direct_effect(from_sq, dir, self.occupied());
                    checkers |= ray & self.pieces_c(us);
                }
            }
        }
        // gives_check=false の場合は checkers=EMPTY のまま
        // この最適化により、王手にならない手の場合に attackers_to_c() の呼び出しを回避できる
        // 前提条件: 呼び出し側で gives_check() の判定が正確に行われていること
        // デバッグビルドでは debug_assert で検証を実施
        debug_assert!(
            {
                let expected = self.attackers_to_c(self.king_square[them.index()], us);
                let result = if gives_check {
                    checkers == expected
                } else {
                    expected.is_empty()
                };
                if !result {
                    eprintln!(
                        "gives_check mismatch: gives_check={gives_check}, checkers={checkers:?}, actual={expected:?}"
                    );
                }
                result
            },
            "gives_check mismatch detected"
        );
        let is_check = !checkers.is_empty();
        // 4. 連続王手カウンタの更新
        if is_check {
            new_state.continuous_check[us.index()] = prev_continuous[us.index()] + 2;
        } else {
            new_state.continuous_check[us.index()] = 0;
        }
        // 受け手側は前の値をそのまま引き継ぐ（memcpyで自動的にコピーされる）
        // rshogi では partial_clone() で既にコピー済みなので、リセットしない

        // 5. 手番交代
        self.side_to_move = them;

        // 6. 王手情報の更新
        new_state.checkers = checkers;

        // 7. 千日手判定に使う手駒スナップショットを保存
        new_state.hand_snapshot = self.hand;
        new_state.material_value = Value::new(material_value);

        // 8. StateInfoの付け替え（previous をぶら下げる）
        new_state.last_move = m;
        self.push_state(new_state);

        // 9. 繰り返し情報の更新
        self.update_repetition_info();

        // 10. pin情報を差分更新（王との直線/斜め上の駒が動いた場合のみ再計算）
        {
            let occ_after = self.occupied();
            let changed_sqs: [Option<Square>; 2] = [moved_from, Some(moved_to)];

            for c in [Color::Black, Color::White] {
                let king_sq_prev = prev_king_sq[c.index()];
                let king_sq_now = self.king_square[c.index()];
                let king_moved = king_sq_prev != king_sq_now;

                let mut needs_recompute = king_moved;
                if !needs_recompute {
                    for sq in changed_sqs.iter().flatten().copied() {
                        if prev_blockers[c.index()].contains(sq)
                            || prev_pinners[c.index()].contains(sq)
                            || crate::bitboard::direct_of(king_sq_now, sq).is_some()
                        {
                            needs_recompute = true;
                            break;
                        }
                    }
                }

                if !needs_recompute {
                    let st = self.cur_state_mut();
                    st.blockers_for_king[c.index()] = prev_blockers[c.index()];
                    st.pinners[c.index()] = prev_pinners[c.index()];
                    continue;
                }

                let (blockers, pinners) =
                    self.compute_blockers_and_pinners(c, occ_after, Bitboard::EMPTY);
                let st = self.cur_state_mut();
                st.blockers_for_king[c.index()] = blockers;
                st.pinners[c.index()] = pinners;
            }
        }

        // 11. 王手マスの更新
        self.update_check_squares();

        #[cfg(debug_assertions)]
        if update_board_effects {
            self.debug_verify_board_effects();
        }

        dirty_piece
    }

    /// 指し手を戻す
    ///
    /// PASSの場合は undo_pass_move に委譲する。
    pub fn undo_move(&mut self, m: Move) {
        if m.is_pass() {
            return self.undo_pass_move();
        }

        // 1. 手番を戻す
        self.side_to_move = !self.side_to_move;
        self.game_ply -= 1;
        let us = self.side_to_move;
        let captured = self.cur_state().captured_piece;
        let prev_idx = self.cur_state().previous;
        assert_ne!(prev_idx, StateInfo::NO_PREVIOUS, "No previous state for undo");
        let update_board_effects = Self::should_update_board_effects();
        if update_board_effects {
            self.ensure_board_effects();
        } else {
            self.board_effects_dirty = true;
        }

        // 2. 駒の移動を戻す
        if m.is_drop() {
            let pt = m.drop_piece_type();
            let to = m.to();
            let moved_pc = self.piece_on(to);

            // PieceList: 盤上 → 手駒に戻す
            let piece_no = self.piece_list.piece_no_of_board(to);

            // 盤上から除去
            self.remove_piece_internal(to);
            // 手駒に戻す
            debug_assert!(us.index() < Color::NUM);
            // SAFETY: Color::index() は 0 or 1 で hand は [Hand; Color::NUM=2]。
            let hand_ref = unsafe { self.hand.get_unchecked_mut(us.index()) };
            *hand_ref = hand_ref.add(pt);

            let new_count = unsafe { self.hand.get_unchecked(us.index()) }.count(pt) as u8;
            let hand_bp = ExtBonaPiece::from_hand(us, pt, new_count);
            self.piece_list.put_piece_on_hand(piece_no, hand_bp);

            if update_board_effects {
                let occupied_after = self.occupied();
                rewind_by_dropping_piece(
                    &mut self.board_effects,
                    &mut self.long_effects,
                    occupied_after,
                    to,
                    moved_pc,
                );
            }
        } else {
            let from = m.from();
            let to = m.to();
            let moved_pc = self.piece_on(to);
            let original_pc = if m.is_promote() {
                moved_pc.unpromote()
            } else {
                moved_pc
            };

            // PieceList: 動いた駒を from に戻す
            let piece_no_moved = self.piece_list.piece_no_of_board(to);
            let from_bp = ExtBonaPiece::from_board(original_pc, from);

            if captured.is_some() {
                let cap_pt = captured.piece_type().unpromote();

                // PieceList: 手駒から取られた駒を盤上に復元
                // SAFETY: Color::index() は 0 or 1 で hand は [Hand; Color::NUM=2]。
                let hand_count = unsafe { self.hand.get_unchecked(us.index()) }.count(cap_pt) as u8;
                let hand_bp_fb =
                    crate::nnue::BonaPiece::from_hand_piece(Color::Black, us, cap_pt, hand_count);
                let piece_no_cap = self.piece_list.piece_no_of_hand(hand_bp_fb);
                let cap_board_bp = ExtBonaPiece::from_board(captured, to);

                // 駒を元の位置に戻す
                self.remove_piece_internal(to);
                self.put_piece_internal(captured, to);
                // 手駒から除去
                // SAFETY: Color::index() は 0 or 1 で hand は [Hand; Color::NUM=2]。
                let hand_ref = unsafe { self.hand.get_unchecked_mut(us.index()) };
                *hand_ref = hand_ref.sub(cap_pt);

                self.put_piece_internal(original_pc, from);

                // PieceList 更新
                self.piece_list.put_piece_on_board(piece_no_cap, cap_board_bp, to);
                self.piece_list.put_piece_on_board(piece_no_moved, from_bp, from);

                // 玉の移動を戻す
                if original_pc.piece_type() == PieceType::King {
                    self.king_square[us.index()] = from;
                }

                if update_board_effects {
                    let occupied_after = self.occupied();
                    rewind_by_capturing_piece(
                        &mut self.board_effects,
                        &mut self.long_effects,
                        occupied_after,
                        from,
                        to,
                        original_pc,
                        moved_pc,
                        captured,
                    );
                }
            } else {
                // 駒を元の位置に戻す
                self.remove_piece_internal(to);
                self.put_piece_internal(original_pc, from);

                // PieceList 更新
                self.piece_list.put_piece_on_board(piece_no_moved, from_bp, from);

                // 玉の移動を戻す
                if original_pc.piece_type() == PieceType::King {
                    self.king_square[us.index()] = from;
                }

                if update_board_effects {
                    let occupied_after = self.occupied();
                    rewind_by_no_capturing_piece(
                        &mut self.board_effects,
                        &mut self.long_effects,
                        occupied_after,
                        from,
                        to,
                        original_pc,
                        moved_pc,
                    );
                }
            }
        }

        // 3. StateInfoを戻す
        self.state_idx = prev_idx;

        #[cfg(debug_assertions)]
        if update_board_effects {
            self.debug_verify_board_effects();
        }
    }

    /// null moveを実行
    pub fn do_null_move(&mut self) {
        let noop = NoPrefetch;
        self.do_null_move_with_prefetch(&noop);
    }

    pub(crate) fn do_null_move_with_prefetch<P: TtPrefetch>(&mut self, prefetcher: &P) {
        let mut new_state = self.cur_state().partial_clone();

        new_state.board_key ^= zobrist_side();
        new_state.plies_from_null = 0;
        new_state.captured_piece = Piece::NONE;
        new_state.last_move = Move::NULL;
        new_state.hand_snapshot = self.hand;

        let next_side = !self.side_to_move;
        prefetcher.prefetch(new_state.key(), next_side);

        self.side_to_move = next_side;

        self.push_state(new_state);

        // null move後は王手されていないはず
        self.cur_state_mut().checkers = Bitboard::EMPTY;

        self.update_blockers_and_pinners();
        self.update_check_squares();
    }

    /// null moveを戻す
    pub fn undo_null_move(&mut self) {
        self.side_to_move = !self.side_to_move;
        let prev_idx = self.cur_state().previous;
        assert_ne!(prev_idx, StateInfo::NO_PREVIOUS, "No previous state for undo_null_move");
        self.state_idx = prev_idx;
    }

    /// パス手を実行
    ///
    /// 【重要】
    /// - checkers は正しく再計算する（相手が王手をかけたままパス可能なため）
    /// - ハッシュ更新は StateInfo 更新前に差分計算
    /// - assert! で release ビルドでも不正入力を検出
    pub fn do_pass_move(&mut self) -> DirtyPiece {
        // release ビルドでも検出
        assert!(self.can_pass(), "Cannot pass: rule disabled, in check, or no pass rights");

        let us = self.side_to_move;
        let them = !us;

        // 1. partial_clone で StateInfo をコピー
        let mut new_state = self.cur_state().partial_clone();

        // 2. パス権のZobrist差分を計算（変更前に取得）
        let old_black = new_state.get_pass_rights(Color::Black);
        let old_white = new_state.get_pass_rights(Color::White);

        // 3. パス権を消費（checked_sub で underflow 防止）
        let new_black = if us == Color::Black {
            old_black.checked_sub(1).expect("Black pass rights underflow")
        } else {
            old_black
        };
        let new_white = if us == Color::White {
            old_white.checked_sub(1).expect("White pass rights underflow")
        } else {
            old_white
        };
        new_state.set_pass_rights_internal(Color::Black, new_black);
        new_state.set_pass_rights_internal(Color::White, new_white);

        // 4. Zobristキー更新（手番 + パス権）
        new_state.board_key ^= zobrist_side();
        new_state.board_key ^= zobrist_pass_rights(old_black, old_white);
        new_state.board_key ^= zobrist_pass_rights(new_black, new_white);

        // 5. その他の StateInfo 更新
        new_state.captured_piece = Piece::NONE;
        new_state.last_move = Move::PASS;
        new_state.hand_snapshot = self.hand;
        // パスは合法手なので通常の手と同様にカウントを進める（千日手検出のため）
        // ※ do_null_move（探索用）とは異なり、0リセットしない
        new_state.plies_from_null += 1;

        // 6. 連続王手カウンタ: パスが王手を維持する場合はカウンタを更新
        // （自分が相手玉に攻撃している場合、パス後も相手は王手状態）
        let their_king = self.king_square(them);
        let gives_check = !self.attackers_to_c(their_king, us).is_empty();
        if gives_check {
            // 王手を維持するので、連続王手カウンタを更新（do_moveと同様）
            new_state.continuous_check[us.index()] += 2;
        } else {
            new_state.continuous_check[us.index()] = 0;
        }

        // 7. 手番交代
        self.side_to_move = them;
        self.game_ply += 1;

        // 8. push_state で StateInfo を積む
        self.push_state(new_state);

        // 9. 【重要】checkers を正しく計算
        // （相手が王手をかけたままパスした場合、こちらは王手状態になる）
        // Note: side_to_move は既に them に変更済み
        self.cur_state_mut().checkers =
            self.attackers_to_c(self.king_square(self.side_to_move), !self.side_to_move);

        // 10. blockers/pinners/check_squares を更新
        self.update_blockers_and_pinners();
        self.update_check_squares();

        // 11. 繰り返し情報を更新
        self.update_repetition_info();

        // PASSは盤面変化なしのため、DirtyPiece は空で返す
        DirtyPiece::new()
    }

    /// パス手を戻す
    pub fn undo_pass_move(&mut self) {
        self.side_to_move = !self.side_to_move;
        self.game_ply -= 1;

        let prev_idx = self.cur_state().previous;
        assert_ne!(prev_idx, StateInfo::NO_PREVIOUS, "No previous state for undo_pass_move");
        self.state_idx = prev_idx;
    }

    /// 繰り返し情報を更新（最大16手遡り）
    fn update_repetition_info(&mut self) {
        // 初期化
        let side = self.side_to_move;
        let (plies_from_null, board_key, hand_snapshot, prev_idx, cc_side, cc_opp) = {
            let st = self.cur_state();
            (
                st.plies_from_null,
                st.board_key,
                st.hand_snapshot,
                st.previous,
                st.continuous_check[side.index()],
                st.continuous_check[(!side).index()],
            )
        };

        let max_back = plies_from_null.min(16);
        let mut repetition = 0;
        let mut repetition_times = 0;
        let mut repetition_type = RepetitionState::None;

        if max_back >= 4 && prev_idx != StateInfo::NO_PREVIOUS {
            // 千日手は最短4手で成立するため4手前から比較開始
            let mut dist = 4;
            let mut st_idx = prev_idx;
            for _ in 0..3 {
                debug_assert!(st_idx < self.state_stack.len());
                // SAFETY: ループ不変条件: st_idx はループ先頭時点で常に有効なインデックス。
                //   - 1回目: prev_idx は関数先頭で NO_PREVIOUS チェック済み。
                //   - 2・3回目: 前の反復で NO_PREVIOUS なら break するため無効値では到達しない。
                //   push_state で設定された .previous は常に有効なインデックスか NO_PREVIOUS。
                st_idx = unsafe { self.state_stack.get_unchecked(st_idx) }.previous;
                if st_idx == StateInfo::NO_PREVIOUS {
                    break;
                }
            }

            while dist <= max_back && st_idx != StateInfo::NO_PREVIOUS {
                debug_assert!(st_idx < self.state_stack.len());
                // SAFETY: 同上。
                let stp = unsafe { self.state_stack.get_unchecked(st_idx) };
                if stp.board_key == board_key {
                    let prev_hand = stp.hand_snapshot[side.index()];
                    let cur_hand = hand_snapshot[side.index()];

                    if cur_hand == prev_hand {
                        let times = stp.repetition_times + 1;
                        repetition_times = times;
                        repetition = if times >= 3 { -dist } else { dist };

                        let mut rep_type = if dist <= cc_side {
                            RepetitionState::Lose
                        } else if dist <= cc_opp {
                            RepetitionState::Win
                        } else {
                            RepetitionState::Draw
                        };

                        if stp.repetition_times > 0 && stp.repetition_type != rep_type {
                            rep_type = RepetitionState::Draw;
                        }

                        repetition_type = rep_type;
                        break;
                    }

                    if cur_hand.is_superior_or_equal(prev_hand) {
                        repetition_type = RepetitionState::Superior;
                        repetition = dist;
                        break;
                    }

                    if prev_hand.is_superior_or_equal(cur_hand) {
                        repetition_type = RepetitionState::Inferior;
                        repetition = dist;
                        break;
                    }
                }

                let prev_same_side = stp.previous;
                if prev_same_side == StateInfo::NO_PREVIOUS {
                    break;
                }
                debug_assert!(prev_same_side < self.state_stack.len());
                // SAFETY: prev_same_side は .previous チェーンの有効なインデックス。
                st_idx = unsafe { self.state_stack.get_unchecked(prev_same_side) }.previous;
                dist += 2;
            }
        }

        let st = self.cur_state_mut();
        st.repetition = repetition;
        st.repetition_times = repetition_times;
        st.repetition_type = repetition_type;
    }

    /// 王手になるかどうか
    ///
    /// PASSの場合：自分が相手玉に王手をかけている状態なら true
    /// （パス後、相手が王手状態になるため）
    pub fn gives_check(&self, m: Move) -> bool {
        // PASS の場合：自分が相手玉に攻撃しているか
        if m.is_pass() {
            let them = !self.side_to_move;
            let their_king = self.king_square(them);
            return !self.attackers_to_c(their_king, self.side_to_move).is_empty();
        }

        let us = self.side_to_move;
        let to = m.to();

        if m.is_drop() {
            // 打ち駒の場合：打った駒が王手マスにあるか
            let pt = m.drop_piece_type();
            return self.check_squares(pt).contains(to);
        }

        let from = m.from();
        let pc = self.piece_on(from);
        let pt = pc.piece_type();

        // 直接王手：移動先が王手マスにあるか
        let moved_pt = if m.is_promote() {
            pt.promote().unwrap_or(pt)
        } else {
            pt
        };
        if self.check_squares(moved_pt).contains(to) {
            return true;
        }

        // 開き王手：fromがblockerで、fromが王との直線上から外れるか
        let them = !us;
        debug_assert!(them.index() < Color::NUM);
        // SAFETY: Color::index() は 0 or 1 で king_square は [Square; 2]。
        let ksq = unsafe { *self.king_square.get_unchecked(them.index()) };
        // blockers_for_king には敵駒も含まれるため、自駒でフィルタ
        let blockers = self.blockers_for_king(them) & self.pieces_c(us);

        if blockers.contains(from) {
            // fromが王との直線上にある場合、toも同じ直線上にないと開き王手
            // line_bb()の動的計算を避け、direct_of()による方向一致判定に置き換え
            let dir_from = crate::bitboard::direct_of(ksq, from);
            // blockerは必ず玉との直線上にある（blockers_for_kingの仕様保証）
            debug_assert!(
                dir_from.is_some(),
                "blocker at {from:?} must be on line with king at {ksq:?}"
            );
            let dir_to = crate::bitboard::direct_of(ksq, to);
            if dir_from != dir_to {
                return true;
            }
        }

        false
    }

    /// 1手詰めを検出（該当手があれば返す。なければ Move::NONE）
    pub fn mate_1ply(&mut self) -> Move {
        crate::mate::mate_1ply(self).unwrap_or(Move::NONE)
    }

    // =========================================================================
    // 入玉宣言勝ち（YaneuraOu DeclarationWin 準拠）
    // =========================================================================

    /// 敵陣のBitboard（先手なら1-3段目、後手なら7-9段目）
    pub(crate) fn enemy_field(us: Color) -> Bitboard {
        match us {
            Color::Black => RANK_BB[0] | RANK_BB[1] | RANK_BB[2],
            Color::White => RANK_BB[6] | RANK_BB[7] | RANK_BB[8],
        }
    }

    /// 入玉宣言勝ちの判定（YaneuraOu `Position::DeclarationWin()` 準拠）
    ///
    /// 条件を満たしていれば `Move::WIN`（24/27点法）または玉の移動手（トライルール）を返す。
    /// 条件を満たさなければ `Move::NONE` を返す。
    pub fn declaration_win(&self, rule: EnteringKingRule) -> Move {
        if rule == EnteringKingRule::None {
            return Move::NONE;
        }

        let us = self.side_to_move();

        // トライルール: 別ロジック
        if rule == EnteringKingRule::TryRule {
            return self.try_rule_move(us);
        }

        // --- 24/27 点法の宣言勝ち判定 ---

        let ksq = self.king_square(us);
        let ef = Self::enemy_field(us);

        // (b) 宣言側の玉が敵陣三段目以内に入っている
        if !ef.contains(ksq) {
            return Move::NONE;
        }

        // (e) 宣言側の玉に王手がかかっていない
        if self.in_check() {
            return Move::NONE;
        }

        // (d) 宣言側の敵陣三段目以内の駒は、玉を除いて10枚以上存在する
        let our_in_enemy = self.pieces_c(us) & ef;
        // our_in_enemy には玉も含まれるので 11枚以上必要
        if our_in_enemy.count() < 11 {
            return Move::NONE;
        }

        // (c) 駒点計算
        // 大駒（角・馬・飛・龍）= 5点、小駒 = 1点
        let big_set = PieceTypeSet::bishop_horse() | PieceTypeSet::rook_dragon();
        let big_in_enemy = (self.pieces_c_by_types(us, big_set) & ef).count();

        // 小駒1点、大駒5点、玉除く
        // = 敵陣の自駒数 + 敵陣の自駒の大駒×4 - 1(玉)
        let h = self.hand(us);
        let score = our_in_enemy.count() + big_in_enemy * 4 - 1
            + h.count(PieceType::Pawn)
            + h.count(PieceType::Lance)
            + h.count(PieceType::Knight)
            + h.count(PieceType::Silver)
            + h.count(PieceType::Gold)
            + (h.count(PieceType::Bishop) + h.count(PieceType::Rook)) * 5;

        // 必要点を計算（None / TryRule は上部で除外済み）
        let mut required = match rule {
            EnteringKingRule::Point24 | EnteringKingRule::Point24H => 31u32,
            EnteringKingRule::Point27 | EnteringKingRule::Point27H => match us {
                Color::Black => 28,
                Color::White => 27,
            },
            EnteringKingRule::None | EnteringKingRule::TryRule => unreachable!(),
        };

        // 駒落ち補正（_H バリアント）
        if matches!(rule, EnteringKingRule::Point24H | EnteringKingRule::Point27H) {
            let total = self.count_total_piece_points();
            // 56 - total が駒落ちの分。後手の必要点を減算（YO 準拠: 上手=後手）
            if total < 56 {
                let deficit = 56 - total;
                if us == Color::White {
                    required = required.saturating_sub(deficit);
                }
            }
        }

        if score >= required {
            Move::WIN
        } else {
            Move::NONE
        }
    }

    /// トライルール: 玉が敵の初期玉位置に移動できるか判定
    ///
    /// 玉が既にトライ升にいる場合は `Move::NONE` を返す（YO 準拠）。
    /// king_effect に自マスは含まれないため、隣接判定で除外される。
    /// この場合、前の手番でトライ升への移動が成立しておりサーバーが終局を判定するはず。
    fn try_rule_move(&self, us: Color) -> Move {
        let ksq = self.king_square(us);
        let try_sq = match us {
            // 先手: 敵(後手)の初期玉位置 = 5一
            Color::Black => Square::new(File::File5, Rank::Rank1),
            // 後手: 敵(先手)の初期玉位置 = 5九
            Color::White => Square::new(File::File5, Rank::Rank9),
        };

        // (1) 玉がトライ升に隣接しているか（ksq == try_sq の場合も false → YO 準拠）
        if !king_effect(ksq).contains(try_sq) {
            return Move::NONE;
        }

        // (2) トライ升に自駒がないか
        if self.pieces_c(us).contains(try_sq) {
            return Move::NONE;
        }

        // (3) トライ升に移動したとき相手に取られないか
        // 自玉を除いた occupied で判定（玉が移動するため）
        let occ_without_king = self.occupied() ^ Bitboard::from_square(ksq);
        let enemy_attackers =
            self.attackers_to_occ(try_sq, occ_without_king) & self.pieces_c(us.opponent());
        if !enemy_attackers.is_empty() {
            return Move::NONE;
        }

        // 玉の移動手を返す（成りなし）
        let king_piece = Piece::new(us, PieceType::King);
        Move::new_move(ksq, try_sq, false).with_piece(king_piece)
    }

    /// 全駒の合計点数（駒落ち判定用）
    ///
    /// YO 準拠: 盤上の全駒数(玉含む) + 大駒数×4 + 手駒の合計。
    /// 大駒 = 5点、その他(玉含む) = 1点。平手の場合は 56。
    fn count_total_piece_points(&self) -> u32 {
        let big_set = PieceTypeSet::bishop_horse() | PieceTypeSet::rook_dragon();

        // 盤上: YO の p1 + p2 * 4（玉も1点として計上）
        let all_count = self.occupied().count();
        let big_count = self.pieces_by_types(big_set).count();
        let board_score = all_count + big_count * 4;

        // 手駒
        let mut hand_score = 0u32;
        for c in [Color::Black, Color::White] {
            let h = self.hand(c);
            hand_score += h.count(PieceType::Pawn)
                + h.count(PieceType::Lance)
                + h.count(PieceType::Knight)
                + h.count(PieceType::Silver)
                + h.count(PieceType::Gold)
                + (h.count(PieceType::Bishop) + h.count(PieceType::Rook)) * 5;
        }

        board_score + hand_score
    }
}

impl Default for Position {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::{EnteringKingRule, File, Rank};

    #[test]
    fn test_position_new() {
        let pos = Position::new();
        assert_eq!(pos.side_to_move(), Color::Black);
        assert_eq!(pos.game_ply(), 0);
        assert!(pos.occupied().is_empty());
    }

    #[test]
    fn test_put_and_remove_piece() {
        let mut pos = Position::new();
        let sq = Square::new(File::File5, Rank::Rank5);

        pos.put_piece(Piece::B_PAWN, sq);
        assert_eq!(pos.piece_on(sq), Piece::B_PAWN);
        assert!(pos.pieces(Color::Black, PieceType::Pawn).contains(sq));

        pos.remove_piece(sq);
        assert_eq!(pos.piece_on(sq), Piece::NONE);
        assert!(!pos.pieces(Color::Black, PieceType::Pawn).contains(sq));
    }

    #[test]
    fn test_blockers_pinners_incremental_matches_full() {
        // 配置: 先手玉5九, 後手玉1一, 後手飛5六, 先手金5八（玉を遮る）, 先手桂1三（玉筋外）
        let mut pos = Position::new();
        let bk = Square::new(File::File5, Rank::Rank9);
        let wk = Square::new(File::File1, Rank::Rank1);
        let rook = Square::new(File::File5, Rank::Rank6);
        let blocker = Square::new(File::File5, Rank::Rank8);
        let knight = Square::new(File::File1, Rank::Rank3);

        pos.put_piece(Piece::B_KING, bk);
        pos.king_square[Color::Black.index()] = bk;
        pos.put_piece(Piece::W_KING, wk);
        pos.king_square[Color::White.index()] = wk;
        pos.put_piece(Piece::W_ROOK, rook);
        pos.put_piece(Piece::B_GOLD, blocker);
        pos.put_piece(Piece::B_KNIGHT, knight);
        pos.init_piece_list();

        pos.update_blockers_and_pinners();
        pos.update_check_squares();

        let prev_blockers = pos.blockers_for_king(Color::Black);
        let prev_pinners = pos.cur_state().pinners[Color::White.index()];

        // 玉筋とは無関係の桂を動かしてもblockers/pinnersは変わらない
        // 先手番で先手の桂を動かす（後手玉1一には王手にならない）
        let mv_offline = Move::new_move(knight, Square::new(File::File1, Rank::Rank2), false);
        let gives_check = pos.gives_check(mv_offline);
        pos.do_move(mv_offline, gives_check);
        assert_eq!(pos.blockers_for_king(Color::Black), prev_blockers);
        assert_eq!(pos.cur_state().pinners[Color::White.index()], prev_pinners);

        // 金を筋から外すとblockers/pinnersが更新される（再計算と一致）
        // 手番を戻して先手が金を動かす（王手ではない）
        pos.side_to_move = Color::Black;
        pos.update_check_squares();
        let mv_unblock = Move::new_move(blocker, Square::new(File::File6, Rank::Rank8), false);
        let gives_check = pos.gives_check(mv_unblock);
        pos.do_move(mv_unblock, gives_check);
        let (blockers_full, pinners_full) =
            pos.compute_blockers_and_pinners(Color::Black, pos.occupied(), Bitboard::EMPTY);
        assert_eq!(pos.blockers_for_king(Color::Black), blockers_full);
        assert_eq!(pos.cur_state().pinners[Color::White.index()], pinners_full);

        // 捕獲で遮断駒を除去した場合の開き王手も検出される
        // 先手の飛車 1一, 後手玉 1九, 先手金 1七（遮断駒）, 後手歩 2七 を1七の金で取って開き王手になるケース
        let mut pos = Position::new();
        let wk = Square::new(File::File1, Rank::Rank9);
        let br = Square::new(File::File1, Rank::Rank1);
        let b_blocker = Square::new(File::File1, Rank::Rank7);
        let w_target = Square::new(File::File2, Rank::Rank7);
        let bk = Square::new(File::File5, Rank::Rank9); // 先手玉はどこでもよい
        pos.put_piece(Piece::W_KING, wk);
        pos.king_square[Color::White.index()] = wk;
        pos.put_piece(Piece::B_KING, bk);
        pos.king_square[Color::Black.index()] = bk;
        pos.put_piece(Piece::B_ROOK, br);
        pos.put_piece(Piece::B_GOLD, b_blocker);
        pos.put_piece(Piece::W_PAWN, w_target);
        pos.init_piece_list();
        pos.side_to_move = Color::Black;
        pos.update_blockers_and_pinners();
        pos.update_check_squares();

        // 金で歩を取る（開き王手）
        let mv_capture = Move::new_move(b_blocker, w_target, false);
        // 開き王手になるため gives_check は true であるべき
        // blockers_for_king(White) に金(1七)が含まれていることを確認
        assert!(
            pos.blockers_for_king(Color::White).contains(b_blocker),
            "Gold at 1七 should be a blocker for White king"
        );
        let gives_check = pos.gives_check(mv_capture);
        assert!(gives_check, "Move should give check (discovered check)");
        pos.do_move(mv_capture, gives_check);
        // checkersに飛車が含まれていれば開き王手が検出されている
        assert!(pos.cur_state().checkers.contains(br));

        // 玉を動かした場合の blockers/pinners 再計算をテスト（別の局面で）
        // 先手玉5九、後手玉1一、後手飛5六（先手玉をpinする配置）
        let mut pos = Position::new();
        let bk = Square::new(File::File5, Rank::Rank9);
        let wk = Square::new(File::File1, Rank::Rank1);
        let wr = Square::new(File::File5, Rank::Rank6);
        pos.put_piece(Piece::B_KING, bk);
        pos.king_square[Color::Black.index()] = bk;
        pos.put_piece(Piece::W_KING, wk);
        pos.king_square[Color::White.index()] = wk;
        pos.put_piece(Piece::W_ROOK, wr);
        pos.init_piece_list();
        pos.side_to_move = Color::Black;
        pos.update_blockers_and_pinners();
        pos.update_check_squares();

        // 先手玉を横に動かす（後手玉への王手ではない）
        let king_from = bk;
        let king_to = Square::new(File::File6, Rank::Rank9);
        let king_move = Move::new_move(king_from, king_to, false);
        let gives_check = pos.gives_check(king_move);
        assert!(!gives_check, "King move should not give check");
        pos.do_move(king_move, gives_check);
        let (blockers_full, pinners_full) =
            pos.compute_blockers_and_pinners(Color::Black, pos.occupied(), Bitboard::EMPTY);
        assert_eq!(pos.blockers_for_king(Color::Black), blockers_full);
        assert_eq!(pos.cur_state().pinners[Color::White.index()], pinners_full);
    }

    #[test]
    fn test_pieces_by_type_set() {
        let mut pos = Position::new();
        let gold_sq = Square::new(File::File5, Rank::Rank5);
        let pro_sq = Square::new(File::File4, Rank::Rank4);
        let dragon_sq = Square::new(File::File9, Rank::Rank9);

        pos.put_piece(Piece::B_GOLD, gold_sq);
        pos.put_piece(Piece::B_PRO_PAWN, pro_sq);
        pos.put_piece(Piece::W_DRAGON, dragon_sq);

        let gold_like = pos.pieces_c_by_types(Color::Black, PieceTypeSet::golds());
        assert!(gold_like.contains(gold_sq));
        assert!(gold_like.contains(pro_sq));
        assert!(!gold_like.contains(dragon_sq));

        let sliders = pos.pieces_by_types(PieceTypeSet::rook_dragon());
        assert!(sliders.contains(dragon_sq));
        assert!(!sliders.contains(gold_sq));

        let all_black = pos.pieces_c_by_types(Color::Black, PieceTypeSet::ALL);
        assert_eq!(all_black.count(), 2);
    }

    #[test]
    fn test_pinned_pieces_excluding_removes_pinner_itself() {
        // 回帰テスト:
        // avoid升(移動元)がpinner候補そのものである場合、
        // occupiedだけでなくpinner候補集合からも除外される必要がある。
        let mut pos = Position::new();
        let ksq = Square::new(File::File5, Rank::Rank9);
        let pinner_sq = Square::new(File::File5, Rank::Rank1);
        let blocker_sq = Square::new(File::File5, Rank::Rank5);
        pos.put_piece(Piece::B_KING, ksq);
        pos.put_piece(Piece::W_ROOK, pinner_sq);
        pos.put_piece(Piece::B_GOLD, blocker_sq);
        pos.king_square[Color::Black.index()] = ksq;
        pos.king_square[Color::White.index()] = Square::new(File::File1, Rank::Rank1);

        let pinned_normal = pos.pinned_pieces_excluding(Color::Black, Square::SQ_11);
        assert!(pinned_normal.contains(blocker_sq));

        let pinned_without_pinner = pos.pinned_pieces_excluding(Color::Black, pinner_sq);
        assert!(pinned_without_pinner.is_empty(), "avoidがpinner自身のときpinは消える必要がある");
    }

    #[test]
    fn test_attackers_to_pawn() {
        let mut pos = Position::new();
        // 5五に先手歩
        let sq55 = Square::new(File::File5, Rank::Rank5);
        let sq54 = Square::new(File::File5, Rank::Rank4);
        pos.put_piece(Piece::B_PAWN, sq55);

        // 5四への利き
        let attackers = pos.attackers_to(sq54);
        assert!(attackers.contains(sq55));
    }

    #[test]
    fn test_do_move_drop() {
        let mut pos = Position::new();
        // 玉を配置
        let sq59 = Square::new(File::File5, Rank::Rank9);
        let sq51 = Square::new(File::File5, Rank::Rank1);
        pos.put_piece(Piece::B_KING, sq59);
        pos.put_piece(Piece::W_KING, sq51);
        pos.king_square[Color::Black.index()] = sq59;
        pos.king_square[Color::White.index()] = sq51;

        // 先手に歩を持たせる
        pos.hand[Color::Black.index()] = pos.hand[Color::Black.index()].add(PieceType::Pawn);
        pos.init_piece_list();

        // 5五歩打ち
        let to = Square::new(File::File5, Rank::Rank5);
        let m = Move::new_drop(PieceType::Pawn, to);

        pos.do_move(m, false);

        assert_eq!(pos.piece_on(to), Piece::B_PAWN);
        assert_eq!(pos.side_to_move(), Color::White);
        assert!(!pos.hand(Color::Black).has(PieceType::Pawn));

        pos.undo_move(m);

        assert_eq!(pos.piece_on(to), Piece::NONE);
        assert_eq!(pos.side_to_move(), Color::Black);
        assert!(pos.hand(Color::Black).has(PieceType::Pawn));
    }

    #[test]
    fn test_do_move_normal() {
        let mut pos = Position::new();
        // 7七に先手歩、玉を配置
        let sq77 = Square::new(File::File7, Rank::Rank7);
        let sq76 = Square::new(File::File7, Rank::Rank6);
        let sq59 = Square::new(File::File5, Rank::Rank9);
        let sq51 = Square::new(File::File5, Rank::Rank1);

        pos.put_piece(Piece::B_PAWN, sq77);
        pos.put_piece(Piece::B_KING, sq59);
        pos.put_piece(Piece::W_KING, sq51);
        pos.king_square[Color::Black.index()] = sq59;
        pos.king_square[Color::White.index()] = sq51;
        pos.init_piece_list();

        // 7六歩
        let m = Move::new_move(sq77, sq76, false);

        pos.do_move(m, false);

        assert_eq!(pos.piece_on(sq77), Piece::NONE);
        assert_eq!(pos.piece_on(sq76), Piece::B_PAWN);
        assert_eq!(pos.side_to_move(), Color::White);

        pos.undo_move(m);

        assert_eq!(pos.piece_on(sq77), Piece::B_PAWN);
        assert_eq!(pos.piece_on(sq76), Piece::NONE);
        assert_eq!(pos.side_to_move(), Color::Black);
    }

    #[test]
    fn test_do_move_capture() {
        let mut pos = Position::new();
        // 7六に先手歩、7五に後手歩、玉を配置
        let sq76 = Square::new(File::File7, Rank::Rank6);
        let sq75 = Square::new(File::File7, Rank::Rank5);
        let sq59 = Square::new(File::File5, Rank::Rank9);
        let sq51 = Square::new(File::File5, Rank::Rank1);

        pos.put_piece(Piece::B_PAWN, sq76);
        pos.put_piece(Piece::W_PAWN, sq75);
        pos.put_piece(Piece::B_KING, sq59);
        pos.put_piece(Piece::W_KING, sq51);
        pos.king_square[Color::Black.index()] = sq59;
        pos.king_square[Color::White.index()] = sq51;
        pos.init_piece_list();

        // 7五歩（取る）
        let m = Move::new_move(sq76, sq75, false);

        pos.do_move(m, false);

        assert_eq!(pos.piece_on(sq76), Piece::NONE);
        assert_eq!(pos.piece_on(sq75), Piece::B_PAWN);
        assert!(pos.hand(Color::Black).has(PieceType::Pawn));
        assert_eq!(pos.side_to_move(), Color::White);

        pos.undo_move(m);

        assert_eq!(pos.piece_on(sq76), Piece::B_PAWN);
        assert_eq!(pos.piece_on(sq75), Piece::W_PAWN);
        assert!(!pos.hand(Color::Black).has(PieceType::Pawn));
        assert_eq!(pos.side_to_move(), Color::Black);
    }

    #[test]
    fn test_do_move_promote() {
        let mut pos = Position::new();
        // 2三に先手歩、玉を配置
        let sq23 = Square::new(File::File2, Rank::Rank3);
        let sq22 = Square::new(File::File2, Rank::Rank2);
        let sq59 = Square::new(File::File5, Rank::Rank9);
        let sq51 = Square::new(File::File5, Rank::Rank1);

        pos.put_piece(Piece::B_PAWN, sq23);
        pos.put_piece(Piece::B_KING, sq59);
        pos.put_piece(Piece::W_KING, sq51);
        pos.king_square[Color::Black.index()] = sq59;
        pos.king_square[Color::White.index()] = sq51;
        pos.init_piece_list();

        // 2二歩成
        let m = Move::new_move(sq23, sq22, true);

        pos.do_move(m, false);

        assert_eq!(pos.piece_on(sq23), Piece::NONE);
        assert_eq!(pos.piece_on(sq22), Piece::B_PRO_PAWN);

        pos.undo_move(m);

        assert_eq!(pos.piece_on(sq23), Piece::B_PAWN);
        assert_eq!(pos.piece_on(sq22), Piece::NONE);
    }

    /// 自己対局中に発生した王手見落としによるpanicを再現し、checkersが正しく更新されることを確認する。
    #[test]
    fn test_checkers_matches_attackers_after_moves() {
        let mut pos = Position::new();
        pos.set_hirate();

        // byoyomi 3000ms の自己対局ログより抽出（最後の timeout は除外）。
        let moves = [
            "7g7f", "4a3b", "1g1f", "5a5b", "4g4f", "3c3d", "6g6f", "1c1d", "5i4h", "9c9d", "4h4g",
            "4c4d", "2h3h", "9a9c", "1i1g", "3a4b", "3h7h", "5c5d", "5g5f", "6c6d", "7h1h", "8b6b",
            "1h5h", "6d6e", "6f6e", "6b6e", "5h6h", "P*6g", "6h4h", "4d4e", "8h2b+", "3b2b",
            "B*7g", "4e4f",
        ];

        for (idx, mv_str) in moves.iter().enumerate() {
            let mv = Move::from_usi(mv_str).unwrap_or_else(|| panic!("invalid move: {mv_str}"));
            let gives_check = pos.gives_check(mv);
            pos.do_move(mv, gives_check);

            let king_sq = pos.king_square(pos.side_to_move());
            let expected_checkers = pos.attackers_to_c(king_sq, !pos.side_to_move());

            assert_eq!(
                pos.checkers(),
                expected_checkers,
                "checkers mismatch at ply {} after move {} in sfen {}",
                idx + 1,
                mv_str,
                pos.to_sfen()
            );
        }
    }

    #[test]
    fn test_do_move_sets_checkers_with_gives_check() {
        let mut pos = Position::new();
        // 玉と持ち駒だけの簡単な局面を作り、王手になる手を指す。
        let b_king = Square::new(File::File5, Rank::Rank9);
        let w_king = Square::new(File::File5, Rank::Rank1);
        pos.put_piece(Piece::B_KING, b_king);
        pos.put_piece(Piece::W_KING, w_king);
        pos.king_square[Color::Black.index()] = b_king;
        pos.king_square[Color::White.index()] = w_king;
        pos.hand[Color::Black.index()] = pos.hand[Color::Black.index()].add(PieceType::Gold);
        pos.init_piece_list();
        // check_squares の更新（gives_check() が正しく動作するために必要）
        pos.update_check_squares();

        let drop_sq = Square::from_usi("4a").unwrap();
        let mv = Move::new_drop(PieceType::Gold, drop_sq);

        // gives_check() が正しく王手を検出することを確認
        let gives_check = pos.gives_check(mv);
        assert!(gives_check, "gives_check should detect the check");

        // do_move に正しい gives_check を渡して、checkers が正しく設定されることを確認
        pos.do_move(mv, gives_check);
        let expected_checkers = pos.attackers_to_c(pos.king_square(Color::White), Color::Black);
        assert!(!expected_checkers.is_empty(), "drop should give check");
        assert_eq!(pos.checkers(), expected_checkers);
        assert_eq!(pos.state().continuous_check[Color::Black.index()], 2);
        assert_eq!(pos.state().continuous_check[Color::White.index()], 0);
        assert_eq!(pos.side_to_move(), Color::White);
    }

    /// パニック再現SFENで敵玉取りや自殺手が非合法になることを確認
    #[test]
    fn panic_position_disallows_king_capture() {
        let sfen = "ln2k1+L1+R/2s2s3/p1pl1p3/1+r2p1p1p/9/4B4/5PPPP/4Gg3/2+b2GKNL w S2NPgs7p 107";
        let mut pos = Position::new();
        pos.set_sfen(sfen).unwrap();

        // 白手番で「4h3i」（敵玉取り）は非合法
        let capture_king = Move::from_usi("4h3i").unwrap();
        assert!(!pos.is_legal(capture_king));

        // 黒手番で玉を3h→3iに動かす手（敵の利きに飛び込む）は非合法
        let mut pos_black = Position::new();
        pos_black.set_sfen(sfen).unwrap();
        pos_black.side_to_move = Color::Black;
        let b_king = pos_black.king_square(Color::Black);
        pos_black.remove_piece(b_king);
        let king_from = Square::from_usi("3h").unwrap();
        let king_to = Square::from_usi("3i").unwrap();
        pos_black.put_piece(Piece::B_KING, king_from);
        pos_black.king_square[Color::Black.index()] = king_from;
        pos_black.update_blockers_and_pinners();
        pos_black.update_check_squares();
        let king_move = Move::new_move(king_from, king_to, false);
        assert!(!pos_black.is_legal(king_move));
    }

    /// to_move が成れない駒（金）に成りフラグが立っている不正な指し手を弾くことを確認
    #[test]
    fn test_to_move_rejects_invalid_promote_flag_for_gold() {
        let mut pos = Position::new();
        let sq59 = Square::new(File::File5, Rank::Rank9);
        let sq51 = Square::new(File::File5, Rank::Rank1);
        let sq58 = Square::new(File::File5, Rank::Rank8);
        let sq57 = Square::new(File::File5, Rank::Rank7);

        pos.put_piece(Piece::B_KING, sq59);
        pos.put_piece(Piece::W_KING, sq51);
        pos.put_piece(Piece::B_GOLD, sq58);
        pos.king_square[Color::Black.index()] = sq59;
        pos.king_square[Color::White.index()] = sq51;

        // 金は成れないが、成りフラグを立てた不正な指し手を作成（ハッシュ衝突を模擬）
        let invalid_move = Move::new_move(sq58, sq57, true);
        assert!(invalid_move.is_promote(), "テスト用の指し手は成りフラグが立っている必要がある");

        // to_move は不正な成りフラグを持つ指し手を None で弾く
        assert_eq!(
            pos.to_move(invalid_move),
            None,
            "成れない駒（金）の成りフラグ付き指し手は弾かれるべき"
        );
    }

    /// to_move が成れない駒（玉）に成りフラグが立っている不正な指し手を弾くことを確認
    #[test]
    fn test_to_move_rejects_invalid_promote_flag_for_king() {
        let mut pos = Position::new();
        let sq59 = Square::new(File::File5, Rank::Rank9);
        let sq51 = Square::new(File::File5, Rank::Rank1);
        let sq58 = Square::new(File::File5, Rank::Rank8);

        pos.put_piece(Piece::B_KING, sq59);
        pos.put_piece(Piece::W_KING, sq51);
        pos.king_square[Color::Black.index()] = sq59;
        pos.king_square[Color::White.index()] = sq51;

        // 玉は成れないが、成りフラグを立てた不正な指し手を作成
        let invalid_move = Move::new_move(sq59, sq58, true);

        assert_eq!(
            pos.to_move(invalid_move),
            None,
            "成れない駒（玉）の成りフラグ付き指し手は弾かれるべき"
        );
    }

    /// to_move が既に成っている駒（と金）に成りフラグが立っている不正な指し手を弾くことを確認
    #[test]
    fn test_to_move_rejects_invalid_promote_flag_for_promoted_piece() {
        let mut pos = Position::new();
        let sq59 = Square::new(File::File5, Rank::Rank9);
        let sq51 = Square::new(File::File5, Rank::Rank1);
        let sq55 = Square::new(File::File5, Rank::Rank5);
        let sq54 = Square::new(File::File5, Rank::Rank4);

        pos.put_piece(Piece::B_KING, sq59);
        pos.put_piece(Piece::W_KING, sq51);
        pos.put_piece(Piece::B_PRO_PAWN, sq55); // と金
        pos.king_square[Color::Black.index()] = sq59;
        pos.king_square[Color::White.index()] = sq51;

        // と金は既に成っているので成れないが、成りフラグを立てた不正な指し手を作成
        let invalid_move = Move::new_move(sq55, sq54, true);

        assert_eq!(
            pos.to_move(invalid_move),
            None,
            "既に成っている駒（と金）の成りフラグ付き指し手は弾かれるべき"
        );
    }

    /// to_move が正常な成り（歩成）を受け入れることを確認
    #[test]
    fn test_to_move_accepts_valid_pawn_promotion() {
        let mut pos = Position::new();
        let sq59 = Square::new(File::File5, Rank::Rank9);
        let sq51 = Square::new(File::File5, Rank::Rank1);
        let sq23 = Square::new(File::File2, Rank::Rank3);
        let sq22 = Square::new(File::File2, Rank::Rank2);

        pos.put_piece(Piece::B_KING, sq59);
        pos.put_piece(Piece::W_KING, sq51);
        pos.put_piece(Piece::B_PAWN, sq23);
        pos.king_square[Color::Black.index()] = sq59;
        pos.king_square[Color::White.index()] = sq51;

        // 歩は成れるので、成りフラグを立てた正常な指し手
        let valid_move = Move::new_move(sq23, sq22, true);

        let result = pos.to_move(valid_move);
        assert!(result.is_some(), "成れる駒（歩）の成りは受け入れられるべき");

        // 返された指し手には駒情報（と金）が付加されている
        let mv = result.unwrap();
        assert_eq!(
            mv.moved_piece_after(),
            Piece::B_PRO_PAWN,
            "成りの場合、moved_piece_after はと金であるべき"
        );
    }

    /// to_move が正常な不成（歩不成）を受け入れることを確認
    #[test]
    fn test_to_move_accepts_valid_pawn_no_promotion() {
        let mut pos = Position::new();
        let sq59 = Square::new(File::File5, Rank::Rank9);
        let sq51 = Square::new(File::File5, Rank::Rank1);
        let sq24 = Square::new(File::File2, Rank::Rank4);
        let sq23 = Square::new(File::File2, Rank::Rank3);

        pos.put_piece(Piece::B_KING, sq59);
        pos.put_piece(Piece::W_KING, sq51);
        pos.put_piece(Piece::B_PAWN, sq24);
        pos.king_square[Color::Black.index()] = sq59;
        pos.king_square[Color::White.index()] = sq51;

        // 歩の不成
        let valid_move = Move::new_move(sq24, sq23, false);

        let result = pos.to_move(valid_move);
        assert!(result.is_some(), "不成の指し手は受け入れられるべき");

        // 返された指し手には駒情報（歩）が付加されている
        let mv = result.unwrap();
        assert_eq!(
            mv.moved_piece_after(),
            Piece::B_PAWN,
            "不成の場合、moved_piece_after は歩であるべき"
        );
    }

    /// 合成Bitboard（golds_bb, bishop_horse_bb, rook_dragon_bb）の整合性を確認
    #[test]
    fn test_composite_bitboard_consistency() {
        let mut pos = Position::new();
        pos.set_hirate();

        // golds_bbの整合性チェック
        let expected_golds = pos.pieces_pt(PieceType::Gold)
            | pos.pieces_pt(PieceType::ProPawn)
            | pos.pieces_pt(PieceType::ProLance)
            | pos.pieces_pt(PieceType::ProKnight)
            | pos.pieces_pt(PieceType::ProSilver);
        assert_eq!(pos.golds(), expected_golds, "golds_bb mismatch");

        // bishop_horse_bbの整合性チェック
        let expected_bh = pos.pieces_pt(PieceType::Bishop) | pos.pieces_pt(PieceType::Horse);
        assert_eq!(pos.bishop_horse(), expected_bh, "bishop_horse_bb mismatch");

        // rook_dragon_bbの整合性チェック
        let expected_rd = pos.pieces_pt(PieceType::Rook) | pos.pieces_pt(PieceType::Dragon);
        assert_eq!(pos.rook_dragon(), expected_rd, "rook_dragon_bb mismatch");

        // hdk_bbの整合性チェック
        let expected_hdk = pos.pieces_pt(PieceType::Horse)
            | pos.pieces_pt(PieceType::Dragon)
            | pos.pieces_pt(PieceType::King);
        assert_eq!(pos.hdk_bb, expected_hdk, "hdk_bb mismatch");
    }

    /// 指し手実行・取り消し後も合成Bitboardの整合性が維持されることを確認
    #[test]
    fn test_composite_bitboard_after_moves() {
        let mut pos = Position::new();
        pos.set_hirate();

        // 何手か指して整合性を確認（角成を含む）
        let moves = ["7g7f", "3c3d", "8h2b+", "3a2b"];
        for mv_str in moves {
            let mv = Move::from_usi(mv_str).unwrap();
            let gives_check = pos.gives_check(mv);
            pos.do_move(mv, gives_check);

            // 毎手後に整合性チェック
            let expected_golds = pos.pieces_pt(PieceType::Gold)
                | pos.pieces_pt(PieceType::ProPawn)
                | pos.pieces_pt(PieceType::ProLance)
                | pos.pieces_pt(PieceType::ProKnight)
                | pos.pieces_pt(PieceType::ProSilver);
            assert_eq!(pos.golds(), expected_golds, "golds_bb mismatch after {mv_str}");

            let expected_bh = pos.pieces_pt(PieceType::Bishop) | pos.pieces_pt(PieceType::Horse);
            assert_eq!(pos.bishop_horse(), expected_bh, "bishop_horse_bb mismatch after {mv_str}");

            let expected_rd = pos.pieces_pt(PieceType::Rook) | pos.pieces_pt(PieceType::Dragon);
            assert_eq!(pos.rook_dragon(), expected_rd, "rook_dragon_bb mismatch after {mv_str}");

            let expected_hdk = pos.pieces_pt(PieceType::Horse)
                | pos.pieces_pt(PieceType::Dragon)
                | pos.pieces_pt(PieceType::King);
            assert_eq!(pos.hdk_bb, expected_hdk, "hdk_bb mismatch after {mv_str}");
        }

        // undo_moveでも整合性維持を確認
        for mv_str in moves.iter().rev() {
            let mv = Move::from_usi(mv_str).unwrap();
            pos.undo_move(mv);

            let expected_golds = pos.pieces_pt(PieceType::Gold)
                | pos.pieces_pt(PieceType::ProPawn)
                | pos.pieces_pt(PieceType::ProLance)
                | pos.pieces_pt(PieceType::ProKnight)
                | pos.pieces_pt(PieceType::ProSilver);
            assert_eq!(pos.golds(), expected_golds, "golds_bb mismatch after undo {mv_str}");

            let expected_bh = pos.pieces_pt(PieceType::Bishop) | pos.pieces_pt(PieceType::Horse);
            assert_eq!(
                pos.bishop_horse(),
                expected_bh,
                "bishop_horse_bb mismatch after undo {mv_str}"
            );

            let expected_rd = pos.pieces_pt(PieceType::Rook) | pos.pieces_pt(PieceType::Dragon);
            assert_eq!(
                pos.rook_dragon(),
                expected_rd,
                "rook_dragon_bb mismatch after undo {mv_str}"
            );

            let expected_hdk = pos.pieces_pt(PieceType::Horse)
                | pos.pieces_pt(PieceType::Dragon)
                | pos.pieces_pt(PieceType::King);
            assert_eq!(pos.hdk_bb, expected_hdk, "hdk_bb mismatch after undo {mv_str}");
        }
    }

    /// 成り駒が golds_bb に含まれることを確認
    #[test]
    fn test_composite_bitboard_with_promotions() {
        let mut pos = Position::new();
        // 5段目に歩、玉を配置
        pos.set_sfen("4k4/9/9/9/4P4/9/9/9/4K4 b - 1").unwrap();

        let to = Square::from_usi("5d").unwrap();

        // 歩成でと金になる
        let mv = Move::from_usi("5e5d+").unwrap();
        let gives_check = pos.gives_check(mv);
        pos.do_move(mv, gives_check);

        // golds_bbにと金が含まれているはず
        assert!(pos.golds().contains(to), "と金がgolds_bbに含まれていない");

        pos.undo_move(mv);
        assert!(!pos.golds().contains(to), "undo後にと金がgolds_bbに残っている");
    }

    /// 飛車成で rook_dragon_bb の整合性を確認
    #[test]
    fn test_composite_bitboard_rook_promotion() {
        let mut pos = Position::new();
        // 飛車を3段目に配置
        pos.set_sfen("4k4/9/9/9/9/9/4R4/9/4K4 b - 1").unwrap();

        let to = Square::from_usi("5b").unwrap();

        // 飛車成で龍になる
        let mv = Move::from_usi("5g5b+").unwrap();
        let gives_check = pos.gives_check(mv);
        pos.do_move(mv, gives_check);

        // rook_dragon_bbに龍が含まれているはず
        assert!(pos.rook_dragon().contains(to), "龍がrook_dragon_bbに含まれていない");

        // 整合性チェック
        let expected_rd = pos.pieces_pt(PieceType::Rook) | pos.pieces_pt(PieceType::Dragon);
        assert_eq!(pos.rook_dragon(), expected_rd, "rook_dragon_bb mismatch");

        pos.undo_move(mv);
        assert!(!pos.rook_dragon().contains(to), "undo後に龍がrook_dragon_bbに残っている");
    }

    /// 香・桂・銀の成りで golds_bb の整合性を確認
    #[test]
    fn test_composite_bitboard_lance_knight_silver_promotions() {
        // 香成のテスト
        let mut pos = Position::new();
        pos.set_sfen("4k4/9/9/4L4/9/9/9/9/4K4 b - 1").unwrap();
        let mv = Move::from_usi("5d5c+").unwrap();
        let to = Square::from_usi("5c").unwrap();
        let gives_check = pos.gives_check(mv);
        pos.do_move(mv, gives_check);
        assert!(pos.golds().contains(to), "成香がgolds_bbに含まれていない");
        let expected_golds = pos.pieces_pt(PieceType::Gold)
            | pos.pieces_pt(PieceType::ProPawn)
            | pos.pieces_pt(PieceType::ProLance)
            | pos.pieces_pt(PieceType::ProKnight)
            | pos.pieces_pt(PieceType::ProSilver);
        assert_eq!(pos.golds(), expected_golds, "golds_bb mismatch after 香成");
        pos.undo_move(mv);

        // 桂成のテスト
        let mut pos = Position::new();
        pos.set_sfen("4k4/9/9/9/4N4/9/9/9/4K4 b - 1").unwrap();
        let mv = Move::from_usi("5e6c+").unwrap();
        let to = Square::from_usi("6c").unwrap();
        let gives_check = pos.gives_check(mv);
        pos.do_move(mv, gives_check);
        assert!(pos.golds().contains(to), "成桂がgolds_bbに含まれていない");
        let expected_golds = pos.pieces_pt(PieceType::Gold)
            | pos.pieces_pt(PieceType::ProPawn)
            | pos.pieces_pt(PieceType::ProLance)
            | pos.pieces_pt(PieceType::ProKnight)
            | pos.pieces_pt(PieceType::ProSilver);
        assert_eq!(pos.golds(), expected_golds, "golds_bb mismatch after 桂成");
        pos.undo_move(mv);

        // 銀成のテスト
        let mut pos = Position::new();
        pos.set_sfen("4k4/9/9/9/4S4/9/9/9/4K4 b - 1").unwrap();
        let mv = Move::from_usi("5e5d+").unwrap();
        let to = Square::from_usi("5d").unwrap();
        let gives_check = pos.gives_check(mv);
        pos.do_move(mv, gives_check);
        assert!(pos.golds().contains(to), "成銀がgolds_bbに含まれていない");
        let expected_golds = pos.pieces_pt(PieceType::Gold)
            | pos.pieces_pt(PieceType::ProPawn)
            | pos.pieces_pt(PieceType::ProLance)
            | pos.pieces_pt(PieceType::ProKnight)
            | pos.pieces_pt(PieceType::ProSilver);
        assert_eq!(pos.golds(), expected_golds, "golds_bb mismatch after 銀成");
        pos.undo_move(mv);
    }

    /// 駒を取りながら成る場合の整合性を確認
    #[test]
    fn test_composite_bitboard_capture_and_promote() {
        let mut pos = Position::new();
        // 相手の歩を取りながら成る局面
        pos.set_sfen("4k4/9/9/4p4/4P4/9/9/9/4K4 b - 1").unwrap();

        let mv = Move::from_usi("5e5d+").unwrap();
        let to = Square::from_usi("5d").unwrap();
        let gives_check = pos.gives_check(mv);
        pos.do_move(mv, gives_check);

        // golds_bbにと金が含まれているはず
        assert!(pos.golds().contains(to), "駒を取って成った後、と金がgolds_bbに含まれていない");

        // 整合性チェック
        let expected_golds = pos.pieces_pt(PieceType::Gold)
            | pos.pieces_pt(PieceType::ProPawn)
            | pos.pieces_pt(PieceType::ProLance)
            | pos.pieces_pt(PieceType::ProKnight)
            | pos.pieces_pt(PieceType::ProSilver);
        assert_eq!(pos.golds(), expected_golds, "golds_bb mismatch after capture and promote");

        pos.undo_move(mv);
        assert!(!pos.golds().contains(to), "undo後にと金がgolds_bbに残っている");
    }

    /// 角成で bishop_horse_bb の整合性を確認
    #[test]
    fn test_composite_bitboard_bishop_promotion() {
        let mut pos = Position::new();
        pos.set_sfen("4k4/9/9/9/9/9/9/4B4/4K4 b - 1").unwrap();

        let to = Square::from_usi("2b").unwrap();

        // 角成で馬になる
        let mv = Move::from_usi("5h2b+").unwrap();
        let gives_check = pos.gives_check(mv);
        pos.do_move(mv, gives_check);

        // bishop_horse_bbに馬が含まれているはず
        assert!(pos.bishop_horse().contains(to), "馬がbishop_horse_bbに含まれていない");

        // 整合性チェック
        let expected_bh = pos.pieces_pt(PieceType::Bishop) | pos.pieces_pt(PieceType::Horse);
        assert_eq!(pos.bishop_horse(), expected_bh, "bishop_horse_bb mismatch");

        pos.undo_move(mv);
        assert!(!pos.bishop_horse().contains(to), "undo後に馬がbishop_horse_bbに残っている");
    }

    // =========================================
    // パス権（Finite Pass Rights）テスト
    // =========================================

    #[test]
    fn test_pass_rights_enabled_default() {
        let mut pos = Position::new();
        pos.set_hirate();
        // デフォルトは無効
        assert!(!pos.is_pass_rights_enabled());
        assert_eq!(pos.pass_rights(Color::Black), 0);
        assert_eq!(pos.pass_rights(Color::White), 0);
    }

    #[test]
    fn test_set_startpos_with_pass_rights() {
        let mut pos = Position::new();
        pos.set_startpos_with_pass_rights(2, 2);

        assert!(pos.is_pass_rights_enabled());
        assert_eq!(pos.pass_rights(Color::Black), 2);
        assert_eq!(pos.pass_rights(Color::White), 2);
        assert!(pos.can_pass()); // 先手番、王手されていない、パス権あり
    }

    #[test]
    fn test_set_sfen_with_pass_rights() {
        let mut pos = Position::new();
        pos.set_sfen_with_pass_rights(
            "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1",
            3,
            5,
        )
        .unwrap();

        assert!(pos.is_pass_rights_enabled());
        assert_eq!(pos.pass_rights(Color::Black), 3);
        assert_eq!(pos.pass_rights(Color::White), 5);
    }

    #[test]
    fn test_can_pass_requires_enabled() {
        let mut pos = Position::new();
        pos.set_hirate();

        // 無効時は can_pass() = false
        assert!(!pos.is_pass_rights_enabled());
        assert!(!pos.can_pass());

        // 有効化してもパス権0なので can_pass() = false
        pos.set_pass_rights_enabled(true);
        assert!(pos.is_pass_rights_enabled());
        assert!(!pos.can_pass());
    }

    #[test]
    fn test_can_pass_requires_no_check() {
        // 王手状態でパスできないことを確認
        // 5a に後手玉、5b に先手金 → 後手玉に王手
        let sfen = "4k4/4G4/9/9/9/9/9/9/4K4 w - 1";
        let mut pos = Position::new();
        pos.set_sfen_with_pass_rights(sfen, 2, 2).unwrap();

        // 後手番で王手されている
        assert!(pos.in_check(), "White king at 5a should be in check from Gold at 5b");
        assert!(!pos.can_pass()); // 王手中はパス不可
    }

    #[test]
    fn test_do_pass_move_basic() {
        let mut pos = Position::new();
        pos.set_startpos_with_pass_rights(2, 2);

        let key_before = pos.state().key();
        let game_ply_before = pos.game_ply();

        // パス実行
        pos.do_pass_move();

        // 手番が変わる
        assert_eq!(pos.side_to_move(), Color::White);

        // パス権が減る
        assert_eq!(pos.pass_rights(Color::Black), 1);
        assert_eq!(pos.pass_rights(Color::White), 2);

        // ゲーム手数が増える（Position.game_ply）
        assert_eq!(pos.game_ply(), game_ply_before + 1);

        // ハッシュキーが変わる（手番とパス権の変化）
        assert_ne!(pos.state().key(), key_before);
    }

    #[test]
    fn test_undo_pass_move_restores_state() {
        let mut pos = Position::new();
        pos.set_startpos_with_pass_rights(2, 2);

        let key_before = pos.state().key();
        let side_before = pos.side_to_move();
        let black_rights_before = pos.pass_rights(Color::Black);
        let white_rights_before = pos.pass_rights(Color::White);
        let game_ply_before = pos.game_ply();

        pos.do_pass_move();
        pos.undo_pass_move();

        // 全ての状態が復元される
        assert_eq!(pos.side_to_move(), side_before);
        assert_eq!(pos.pass_rights(Color::Black), black_rights_before);
        assert_eq!(pos.pass_rights(Color::White), white_rights_before);
        assert_eq!(pos.game_ply(), game_ply_before);
        assert_eq!(pos.state().key(), key_before);
    }

    #[test]
    fn test_do_move_delegates_pass() {
        let mut pos = Position::new();
        pos.set_startpos_with_pass_rights(2, 2);

        // do_move(Move::PASS, ...) がdo_pass_move と同じ結果になることを確認
        let key_before = pos.state().key();
        pos.do_move(Move::PASS, false);

        assert_eq!(pos.side_to_move(), Color::White);
        assert_eq!(pos.pass_rights(Color::Black), 1);
        assert_ne!(pos.state().key(), key_before);
    }

    #[test]
    fn test_undo_move_delegates_pass() {
        let mut pos = Position::new();
        pos.set_startpos_with_pass_rights(2, 2);

        let key_before = pos.state().key();
        pos.do_move(Move::PASS, false);
        pos.undo_move(Move::PASS);

        assert_eq!(pos.side_to_move(), Color::Black);
        assert_eq!(pos.pass_rights(Color::Black), 2);
        assert_eq!(pos.state().key(), key_before);
    }

    #[test]
    fn test_pass_rights_hash_consistency() {
        // パス権の有無でハッシュが異なることを確認
        let mut pos_normal = Position::new();
        pos_normal.set_hirate();

        let mut pos_pass = Position::new();
        pos_pass.set_startpos_with_pass_rights(0, 0);

        // パス権(0,0) の場合は通常ルールとキー互換
        // Note: pass_rights_enabled フラグ自体はハッシュに影響しない
        assert_eq!(pos_normal.state().key(), pos_pass.state().key());

        // パス権を設定するとキーが変わる
        pos_pass.set_pass_rights_pair(2, 2);
        assert_ne!(pos_normal.state().key(), pos_pass.state().key());
    }

    #[test]
    fn test_set_pass_rights_enabled_normalizes_on_disable() {
        let mut pos = Position::new();
        pos.set_startpos_with_pass_rights(2, 3);

        assert_eq!(pos.pass_rights(Color::Black), 2);
        assert_eq!(pos.pass_rights(Color::White), 3);

        // 無効化すると (0,0) に正規化される
        pos.set_pass_rights_enabled(false);

        assert!(!pos.is_pass_rights_enabled());
        assert_eq!(pos.pass_rights(Color::Black), 0);
        assert_eq!(pos.pass_rights(Color::White), 0);
    }

    #[test]
    fn test_multiple_passes_decrement_correctly() {
        let mut pos = Position::new();
        pos.set_startpos_with_pass_rights(3, 2);

        // 先手パス → 後手パス → 先手パス
        pos.do_pass_move();
        assert_eq!(pos.pass_rights(Color::Black), 2);
        assert_eq!(pos.side_to_move(), Color::White);

        pos.do_pass_move();
        assert_eq!(pos.pass_rights(Color::White), 1);
        assert_eq!(pos.side_to_move(), Color::Black);

        pos.do_pass_move();
        assert_eq!(pos.pass_rights(Color::Black), 1);
        assert_eq!(pos.side_to_move(), Color::White);

        // 3回戻す
        pos.undo_pass_move();
        pos.undo_pass_move();
        pos.undo_pass_move();

        assert_eq!(pos.pass_rights(Color::Black), 3);
        assert_eq!(pos.pass_rights(Color::White), 2);
        assert_eq!(pos.side_to_move(), Color::Black);
    }

    #[test]
    fn test_pass_checkers_computed_correctly() {
        // パス後に相手の攻撃が自分の玉への王手になることを確認
        // 例: 先手が金を5七に置いて、後手玉が5一にいる状態
        // 先手パス → 後手番、後手玉への王手はない
        // 後手パス → 先手番、先手玉への王手はない
        let mut pos = Position::new();
        pos.set_startpos_with_pass_rights(2, 2);

        // 平手初期局面でパス → 王手なし
        pos.do_pass_move();
        assert!(!pos.in_check());

        pos.do_pass_move();
        assert!(!pos.in_check());
    }

    #[test]
    fn test_pass_while_giving_check() {
        // 相手に王手をかけている状態でパス可能
        // 先手が後手玉に王手 → 先手パス → 後手が王手状態になる
        // 5a: 後手玉, 5b: 先手金（後手玉に王手）, 5i: 先手玉
        let sfen = "4k4/4G4/9/9/9/9/9/9/4K4 b - 1";
        let mut pos = Position::new();
        pos.set_sfen_with_pass_rights(sfen, 2, 2).unwrap();

        // 先手番で、後手玉に王手をかけている状態
        assert_eq!(pos.side_to_move(), Color::Black);
        assert!(!pos.in_check()); // 先手は王手されていない
        assert!(pos.can_pass()); // 王手をかけていてもパス可能

        // 先手がパス
        pos.do_pass_move();

        // 後手番になり、後手は王手状態
        assert_eq!(pos.side_to_move(), Color::White);
        assert!(pos.in_check(), "White should be in check after Black's pass");

        // 後手はパスできない（王手中）
        assert!(!pos.can_pass());
    }

    #[test]
    fn test_set_pass_rights_idempotent() {
        // 同じ値で2回呼んでもkeyが変わらない（冪等性）
        let mut pos = Position::new();
        pos.set_startpos_with_pass_rights(2, 3);

        let key1 = pos.state().key();

        // 同じ値で再度設定
        pos.set_pass_rights_pair(2, 3);
        let key2 = pos.state().key();

        assert_eq!(key1, key2, "Setting same pass rights should not change key");

        // 異なる値に変更してから元に戻す
        pos.set_pass_rights_pair(5, 5);
        let key3 = pos.state().key();
        assert_ne!(key1, key3, "Different pass rights should change key");

        pos.set_pass_rights_pair(2, 3);
        let key4 = pos.state().key();
        assert_eq!(key1, key4, "Restoring original pass rights should restore key");
    }

    // =========================================
    // 入玉宣言勝ちのテスト
    // =========================================

    fn make_pos(sfen: &str) -> Position {
        let mut pos = Position::new();
        pos.set_sfen(sfen).unwrap();
        pos
    }

    #[test]
    fn test_declaration_win_none_rule() {
        let pos = make_pos("lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1");
        assert_eq!(pos.declaration_win(EnteringKingRule::None), Move::NONE);
    }

    #[test]
    fn test_declaration_win_startpos() {
        let pos = make_pos("lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1");
        assert_eq!(pos.declaration_win(EnteringKingRule::Point27), Move::NONE);
    }

    #[test]
    fn test_declaration_win_27point_success() {
        // 先手玉1一 + 敵陣に金2銀2歩6=10枚、王手なし
        // 盤上小駒10点 + 持駒 飛2(10点) + 角2(10点) = 30点 >= 28
        // 後手: 玉9九、敵陣外に香2桂2金2銀2歩3+手駒なし
        let sfen = "KGG6/SS7/PPPPPP3/9/9/9/2pppppp1/1ss1gg1nl/4k2nl b 2R2B3p 1";
        let pos = make_pos(sfen);
        let result = pos.declaration_win(EnteringKingRule::Point27);
        assert_eq!(result, Move::WIN, "先手28点以上で宣言勝ち");
    }

    #[test]
    fn test_declaration_win_king_not_in_enemy() {
        // 先手玉が自陣(9九)にいる → 宣言勝ち不可
        let pos = make_pos("lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1");
        assert_eq!(pos.declaration_win(EnteringKingRule::Point27), Move::NONE);
    }

    #[test]
    fn test_declaration_win_in_check() {
        // 先手玉1一が敵陣だが、後手の飛車9一で王手されている
        let sfen = "K7r/GG7/SSPPPPPP1/9/9/9/2pppppp1/1ss1gg1nl/4k2nl b 2B3p 1";
        let pos = make_pos(sfen);
        assert_eq!(pos.declaration_win(EnteringKingRule::Point27), Move::NONE);
    }

    #[test]
    fn test_declaration_win_insufficient_pieces() {
        // 先手玉が敵陣だが、敵陣の自駒が10枚未満(6枚のみ: K+GG+SS+P)
        let sfen = "KGGSS1rnl/P8/9/9/9/pp1pppppp/1ss1gg2l/1r5b1/4k2n1 b BNP 1";
        let pos = make_pos(sfen);
        assert_eq!(pos.declaration_win(EnteringKingRule::Point27), Move::NONE);
    }

    #[test]
    fn test_declaration_win_insufficient_points() {
        // 敵陣に10枚あるが、点数が足りない（小駒10枚=10点+持駒0点=10点 < 28）
        let sfen = "KGGSS4/PPPPPP3/PPPP5/9/9/pp1pppppp/1ss1gg1nl/1r5b1/4k2nl b - 1";
        let pos = make_pos(sfen);
        assert_eq!(pos.declaration_win(EnteringKingRule::Point27), Move::NONE);
    }

    #[test]
    fn test_count_total_piece_points_startpos() {
        let pos = make_pos("lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1");
        assert_eq!(pos.count_total_piece_points(), 56);
    }

    #[test]
    fn test_enemy_field() {
        let ef_black = Position::enemy_field(Color::Black);
        assert!(ef_black.contains(Square::new(File::File5, Rank::Rank1)));
        assert!(ef_black.contains(Square::new(File::File5, Rank::Rank3)));
        assert!(!ef_black.contains(Square::new(File::File5, Rank::Rank4)));

        let ef_white = Position::enemy_field(Color::White);
        assert!(ef_white.contains(Square::new(File::File5, Rank::Rank7)));
        assert!(ef_white.contains(Square::new(File::File5, Rank::Rank9)));
        assert!(!ef_white.contains(Square::new(File::File5, Rank::Rank6)));
    }

    // =========================================
    // トライルールのテスト
    // =========================================

    #[test]
    fn test_try_rule_success() {
        // 先手玉6一(File6,Rank1)がトライ升5一に隣接、5一が空で敵の利きもない
        let pos = make_pos("3K5/9/9/9/9/9/9/9/4k4 b 2r2b4g4s4n4l18p 1");
        let result = pos.declaration_win(EnteringKingRule::TryRule);
        assert!(result.is_normal(), "トライ成功時は玉の移動手を返す");
        assert_eq!(result.to(), Square::new(File::File5, Rank::Rank1));
    }

    #[test]
    fn test_try_rule_not_adjacent() {
        // 先手玉3一がトライ升5一に隣接していない（2マス離れている）
        let pos = make_pos("2K6/9/9/9/9/9/9/9/4k4 b 2r2b4g4s4n4l18p 1");
        assert_eq!(
            pos.declaration_win(EnteringKingRule::TryRule),
            Move::NONE,
            "トライ升に隣接していなければ NONE"
        );
    }

    #[test]
    fn test_try_rule_own_piece_on_target() {
        // 先手玉4一がトライ升5一に隣接しているが、5一に自駒（金）がある
        let pos = make_pos("3GK4/9/9/9/9/9/9/9/4k4 b 2r2b3g4s4n4l18p 1");
        assert_eq!(
            pos.declaration_win(EnteringKingRule::TryRule),
            Move::NONE,
            "トライ升に自駒があれば NONE"
        );
    }

    #[test]
    fn test_try_rule_enemy_attacks_target() {
        // 先手玉4一がトライ升5一に隣接、5一は空だが後手の飛車5九が利いている
        let pos = make_pos("4K4/9/9/9/9/9/9/9/4kr3 b 2b4g4s4n4l18p 1");
        assert_eq!(
            pos.declaration_win(EnteringKingRule::TryRule),
            Move::NONE,
            "トライ升に敵の利きがあれば NONE"
        );
    }
}
