use std::env;
use std::fmt::Write;
use std::fs;
use std::path::Path;

fn main() {
    println!("cargo:rerun-if-changed=build.rs");

    // OUT_DIR環境変数を取得
    let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set");
    let dest_path = Path::new(&out_dir);

    // 座標変換テーブル生成（タスク5.2で実装）
    generate_square_tables(dest_path);

    // 攻撃テーブル生成（P4フェーズ）
    generate_attack_tables(dest_path);

    // policyラベル変換テーブル生成
    generate_policy_label_tables(dest_path);
}

/// 座標変換テーブルを生成
fn generate_square_tables(out_dir: &Path) {
    let mut output = String::new();

    // ヘッダー部分
    output.push_str(
        "// Auto-generated by build.rs - DO NOT EDIT

",
    );
    // use文は不要（include!するファイルで既にインポートされている）

    // SQUARE_TO_FILEテーブル生成
    // Squareは 0..81 (SQ_11..SQ_99) + 81 (SQ_NONE)
    output.push_str(
        "/// マスから筋への変換テーブル (82要素: 0..81 + `SQ_NONE`)
",
    );
    output.push_str(
        "pub const SQUARE_TO_FILE: [File; 82] = [
",
    );

    // 0..81: 通常のマス (file = sq / 9, rank = sq % 9)
    // 将棋の座標系: file(筋)は1-9、rank(段)も1-9
    // Square = file * 9 + rank なので、file = sq / 9
    for sq in 0..81 {
        let file = sq / 9;
        if sq % 9 == 0 {
            output.push_str("    ");
        }
        let _ = write!(output, "File::new({file}), ");
        if sq % 9 == 8 {
            let _ = writeln!(output, "// SQ_{}1 - SQ_{}9", 9 - file, 9 - file);
        }
    }

    // 81: SQ_NONE用
    output.push_str(
        "    File::new(-1), // SQ_NONE
",
    );
    output.push_str(
        "];

",
    );

    // SQUARE_TO_RANKテーブル生成
    output.push_str(
        "/// マスから段への変換テーブル (82要素: 0..81 + `SQ_NONE`)
",
    );
    output.push_str(
        "pub const SQUARE_TO_RANK: [Rank; 82] = [
",
    );

    // 0..81: 通常のマス
    for sq in 0..81 {
        let rank = sq % 9;
        if sq % 9 == 0 {
            output.push_str("    ");
        }
        let _ = write!(output, "Rank::new({rank}), ");
        if sq % 9 == 8 {
            let _ = writeln!(output, "// SQ_{}1 - SQ_{}9", 9 - sq / 9, 9 - sq / 9);
        }
    }

    // 81: SQ_NONE用
    output.push_str(
        "    Rank::new(-1), // SQ_NONE
",
    );
    output.push_str(
        "];

",
    );

    // ファイルに書き込み
    let path = out_dir.join("square_tables.rs");
    fs::write(path, output).expect("Failed to write square_tables.rs");
}

/// 攻撃テーブルを生成
fn generate_attack_tables(out_dir: &Path) {
    let mut output = String::new();

    // ヘッダー
    output.push_str(
        "// Auto-generated attack tables - DO NOT EDIT\n\
\n\
use crate::board::{Bitboard, Bitboard256};\n\
\n",
    );

    // 非滑り駒の攻撃テーブル
    output.push_str(&generate_non_slider_attacks());

    // 滑り駒の攻撃テーブル
    output.push_str(&generate_slider_beams());
    output.push_str(&generate_qugiy_step_effects());
    output.push_str(&generate_qugiy_rook_masks());
    output.push_str(&generate_qugiy_bishop_masks());
    output.push_str(&generate_check_candidate_table());

    // ファイルに書き込み
    let path = out_dir.join("attack_tables.rs");
    fs::write(path, output).expect("Failed to write attack_tables.rs");
}

const POLICY_LABEL_SQUARES: usize = 81;
const POLICY_LABEL_CLASS_COUNT: usize = 27;
const POLICY_LABEL_COUNT: usize = POLICY_LABEL_SQUARES * POLICY_LABEL_CLASS_COUNT;
const COMPACT_POLICY_LABEL_COUNT: usize = 1496;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum PolicyPieceType {
    Pawn,
    Lance,
    Knight,
    Silver,
    Bishop,
    Rook,
    Gold,
    King,
    ProPawn,
    ProLance,
    ProKnight,
    ProSilver,
    Horse,
    Dragon,
}

impl PolicyPieceType {
    const ALL: [Self; 14] = [
        Self::Pawn,
        Self::Lance,
        Self::Knight,
        Self::Silver,
        Self::Bishop,
        Self::Rook,
        Self::Gold,
        Self::King,
        Self::ProPawn,
        Self::ProLance,
        Self::ProKnight,
        Self::ProSilver,
        Self::Horse,
        Self::Dragon,
    ];

    fn is_promotable(self) -> bool {
        matches!(
            self,
            Self::Pawn | Self::Lance | Self::Knight | Self::Silver | Self::Bishop | Self::Rook
        )
    }
}

fn generate_policy_label_tables(out_dir: &Path) {
    let compact_to_move_label = compute_policy_structural_labels();
    assert_eq!(
        compact_to_move_label.len(),
        COMPACT_POLICY_LABEL_COUNT,
        "unexpected compact policy label count"
    );

    let mut move_label_to_compact = vec![None; POLICY_LABEL_COUNT];
    for (compact, &move_label) in compact_to_move_label.iter().enumerate() {
        move_label_to_compact[usize::from(move_label)] =
            Some(u16::try_from(compact).expect("compact label index fits in u16"));
    }

    let mut output = String::new();
    output.push_str("// Auto-generated by build.rs - DO NOT EDIT\n\n");

    let _ = writeln!(
        output,
        "pub const COMPACT_TO_MOVE_LABEL: [u16; {COMPACT_POLICY_LABEL_COUNT}] = ["
    );
    for (idx, label) in compact_to_move_label.iter().enumerate() {
        if idx % 16 == 0 {
            output.push_str("    ");
        }
        let _ = write!(output, "{label}, ");
        if idx % 16 == 15 {
            output.push('\n');
        }
    }
    if compact_to_move_label.len() % 16 != 0 {
        output.push('\n');
    }
    output.push_str("];\n\n");

    let _ = writeln!(
        output,
        "pub const MOVE_LABEL_TO_COMPACT: [Option<u16>; {POLICY_LABEL_COUNT}] = ["
    );
    for (idx, label) in move_label_to_compact.iter().enumerate() {
        if idx % 12 == 0 {
            output.push_str("    ");
        }
        match label {
            Some(value) => {
                let _ = write!(output, "Some({value}), ");
            }
            None => output.push_str("None, "),
        }
        if idx % 12 == 11 {
            output.push('\n');
        }
    }
    if move_label_to_compact.len() % 12 != 0 {
        output.push('\n');
    }
    output.push_str("];\n");

    let path = out_dir.join("policy_label_tables.rs");
    fs::write(path, output).expect("Failed to write policy_label_tables.rs");
}

fn compute_policy_structural_labels() -> Vec<u16> {
    let mut valid = vec![false; POLICY_LABEL_COUNT];

    for piece_type in PolicyPieceType::ALL {
        for from in 0..POLICY_LABEL_SQUARES {
            add_piece_labels(piece_type, from, &mut valid);
        }
    }

    for to in 0..POLICY_LABEL_SQUARES {
        let (_, to_y) = policy_square_xy(to);
        if to_y != 0 {
            mark_label(20, to, &mut valid);
            mark_label(21, to, &mut valid);
        }
        if to_y > 1 {
            mark_label(22, to, &mut valid);
        }
        mark_label(23, to, &mut valid);
        mark_label(24, to, &mut valid);
        mark_label(25, to, &mut valid);
        mark_label(26, to, &mut valid);
    }

    valid
        .into_iter()
        .enumerate()
        .filter_map(|(idx, is_valid)| {
            is_valid.then_some(u16::try_from(idx).expect("label fits in u16"))
        })
        .collect()
}

fn add_piece_labels(piece_type: PolicyPieceType, from: usize, valid: &mut [bool]) {
    for &(dx, dy) in piece_step_offsets(piece_type) {
        let Some(to) = policy_offset_square(from, dx, dy) else {
            continue;
        };
        add_board_move_label(piece_type, from, to, valid);
    }

    for &(dx, dy) in piece_slide_offsets(piece_type) {
        let mut next = policy_offset_square(from, dx, dy);
        while let Some(to) = next {
            add_board_move_label(piece_type, from, to, valid);
            next = policy_offset_square(to, dx, dy);
        }
    }
}

fn piece_step_offsets(piece_type: PolicyPieceType) -> &'static [(i8, i8)] {
    const PAWN: &[(i8, i8)] = &[(0, -1)];
    const KNIGHT: &[(i8, i8)] = &[(-1, -2), (1, -2)];
    const SILVER: &[(i8, i8)] = &[(-1, -1), (0, -1), (1, -1), (-1, 1), (1, 1)];
    const GOLD_LIKE: &[(i8, i8)] = &[(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (0, 1)];
    const KING: &[(i8, i8)] =
        &[(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)];
    const HORSE_STEPS: &[(i8, i8)] = &[(-1, 0), (1, 0), (0, -1), (0, 1)];
    const DRAGON_STEPS: &[(i8, i8)] = &[(-1, -1), (1, -1), (-1, 1), (1, 1)];

    match piece_type {
        PolicyPieceType::Pawn => PAWN,
        PolicyPieceType::Knight => KNIGHT,
        PolicyPieceType::Silver => SILVER,
        PolicyPieceType::Gold
        | PolicyPieceType::ProPawn
        | PolicyPieceType::ProLance
        | PolicyPieceType::ProKnight
        | PolicyPieceType::ProSilver => GOLD_LIKE,
        PolicyPieceType::King => KING,
        PolicyPieceType::Horse => HORSE_STEPS,
        PolicyPieceType::Dragon => DRAGON_STEPS,
        PolicyPieceType::Lance | PolicyPieceType::Bishop | PolicyPieceType::Rook => &[],
    }
}

fn piece_slide_offsets(piece_type: PolicyPieceType) -> &'static [(i8, i8)] {
    const LANCE: &[(i8, i8)] = &[(0, -1)];
    const BISHOP: &[(i8, i8)] = &[(-1, -1), (1, -1), (-1, 1), (1, 1)];
    const ROOK: &[(i8, i8)] = &[(-1, 0), (1, 0), (0, -1), (0, 1)];

    match piece_type {
        PolicyPieceType::Lance => LANCE,
        PolicyPieceType::Bishop | PolicyPieceType::Horse => BISHOP,
        PolicyPieceType::Rook | PolicyPieceType::Dragon => ROOK,
        PolicyPieceType::Pawn
        | PolicyPieceType::Knight
        | PolicyPieceType::Silver
        | PolicyPieceType::Gold
        | PolicyPieceType::King
        | PolicyPieceType::ProPawn
        | PolicyPieceType::ProLance
        | PolicyPieceType::ProKnight
        | PolicyPieceType::ProSilver => &[],
    }
}

fn add_board_move_label(piece_type: PolicyPieceType, from: usize, to: usize, valid: &mut [bool]) {
    let move_class = classify_move_label_class(from, to);

    if piece_type.is_promotable() {
        if is_mandatory_promotion(piece_type, to) {
            mark_label(move_class + 10, to, valid);
            return;
        }

        mark_label(move_class, to, valid);
        if is_promotion_move(from, to) {
            mark_label(move_class + 10, to, valid);
        }
        return;
    }

    mark_label(move_class, to, valid);
}

fn policy_offset_square(from: usize, dx: i8, dy: i8) -> Option<usize> {
    let (from_x, from_y) = policy_square_xy(from);
    let to_x = from_x + dx;
    let to_y = from_y + dy;
    if !(0..=8).contains(&to_x) || !(0..=8).contains(&to_y) {
        return None;
    }
    let to_x = usize::try_from(to_x).ok()?;
    let to_y = usize::try_from(to_y).ok()?;
    Some(to_x * 9 + to_y)
}

fn policy_square_xy(square: usize) -> (i8, i8) {
    let x = i8::try_from(square / 9).expect("file fits in i8");
    let y = i8::try_from(square % 9).expect("rank fits in i8");
    (x, y)
}

fn classify_move_label_class(from: usize, to: usize) -> usize {
    let (from_x, from_y) = policy_square_xy(from);
    let (to_x, to_y) = policy_square_xy(to);
    let dir_x = from_x - to_x;
    let dir_y = to_y - from_y;

    if dir_y < 0 && dir_x == 0 {
        0
    } else if dir_y == -2 && dir_x == -1 {
        8
    } else if dir_y == -2 && dir_x == 1 {
        9
    } else if dir_y < 0 && dir_x < 0 {
        1
    } else if dir_y < 0 && dir_x > 0 {
        2
    } else if dir_y == 0 && dir_x < 0 {
        3
    } else if dir_y == 0 && dir_x > 0 {
        4
    } else if dir_y > 0 && dir_x == 0 {
        5
    } else if dir_y > 0 && dir_x < 0 {
        6
    } else {
        7
    }
}

fn is_promotion_move(from: usize, to: usize) -> bool {
    is_promotion_zone_square(from) || is_promotion_zone_square(to)
}

fn is_promotion_zone_square(square: usize) -> bool {
    let (_, y) = policy_square_xy(square);
    y <= 2
}

fn is_mandatory_promotion(piece_type: PolicyPieceType, to: usize) -> bool {
    let (_, to_y) = policy_square_xy(to);
    match piece_type {
        PolicyPieceType::Pawn | PolicyPieceType::Lance => to_y == 0,
        PolicyPieceType::Knight => to_y <= 1,
        PolicyPieceType::Silver
        | PolicyPieceType::Bishop
        | PolicyPieceType::Rook
        | PolicyPieceType::Gold
        | PolicyPieceType::King
        | PolicyPieceType::ProPawn
        | PolicyPieceType::ProLance
        | PolicyPieceType::ProKnight
        | PolicyPieceType::ProSilver
        | PolicyPieceType::Horse
        | PolicyPieceType::Dragon => false,
    }
}

fn mark_label(class_idx: usize, to: usize, valid: &mut [bool]) {
    valid[class_idx * POLICY_LABEL_SQUARES + to] = true;
}

/// 王手候補テーブルを生成
fn generate_check_candidate_table() -> String {
    let mut output = String::new();

    output.push_str(
        "/// 王手候補テーブル \\[king_sq\\]\\[piece_index\\]\\[color\\]\n\
/// piece_index: 0=PAWN,1=LANCE,2=KNIGHT,3=SILVER,4=GOLD,5=BISHOP,6=ROOK(HORSE)\n\
#[allow(clippy::large_const_arrays)]\n\
pub const CHECK_CANDIDATE_BB: [[[Bitboard; 2]; 7]; 81] = [\n",
    );

    for ksq in 0..81 {
        output.push_str("    [\n");
        for pt_idx in 0..7 {
            output.push_str("        [\n");
            for us in 0..2 {
                let bb = check_candidate_bb(ksq, pt_idx, us);
                let _ = writeln!(output, "            Bitboard::from_packed_bits({bb}u128),");
            }
            output.push_str("        ],\n");
        }
        output.push_str("    ],\n");
    }

    output.push_str("];\n\n");
    output
}

#[allow(clippy::too_many_lines)]
fn check_candidate_bb(ksq: i8, pt_idx: usize, us: usize) -> u128 {
    let them = us ^ 1;
    let enemy_field = enemy_field_mask(us);
    let enemy_gold = gold_effect(them, ksq) & enemy_field;
    let mut target = 0u128;

    match pt_idx {
        0 => {
            let mut bb = pawn_effect(them, ksq);
            while let Some(sq) = pop_lsb(&mut bb) {
                target |= pawn_effect(them, sq);
            }
            let mut bb = enemy_gold;
            while let Some(sq) = pop_lsb(&mut bb) {
                target |= pawn_effect(them, sq);
            }
            target &= !bit_of(ksq);
        }
        1 => {
            target = lance_step_effect(them, ksq);
            if (enemy_field & bit_of(ksq)) != 0 {
                let file = ksq / 9;
                let rank = ksq % 9;
                if file != 0 {
                    let sq = (file - 1) * 9 + rank;
                    target |= lance_step_effect(them, sq);
                }
                if file != 8 {
                    let sq = (file + 1) * 9 + rank;
                    target |= lance_step_effect(them, sq);
                }
            }
        }
        2 => {
            let mut bb = knight_effect(them, ksq) | enemy_gold;
            while let Some(sq) = pop_lsb(&mut bb) {
                target |= knight_effect(them, sq);
            }
            target &= !bit_of(ksq);
        }
        3 => {
            let mut bb = silver_effect(them, ksq);
            while let Some(sq) = pop_lsb(&mut bb) {
                target |= silver_effect(them, sq);
            }
            let mut bb = enemy_gold;
            while let Some(sq) = pop_lsb(&mut bb) {
                target |= silver_effect(them, sq);
            }
            let mut bb = gold_effect(them, ksq);
            while let Some(sq) = pop_lsb(&mut bb) {
                target |= silver_effect(them, sq) & enemy_field;
            }
            target &= !bit_of(ksq);
        }
        4 => {
            let mut bb = gold_effect(them, ksq);
            while let Some(sq) = pop_lsb(&mut bb) {
                target |= gold_effect(them, sq);
            }
            target &= !bit_of(ksq);
        }
        5 => {
            let mut bb = bishop_effect(ksq);
            while let Some(sq) = pop_lsb(&mut bb) {
                target |= bishop_effect(sq);
            }
            let mut bb = king_effect(ksq) & enemy_field;
            while let Some(sq) = pop_lsb(&mut bb) {
                target |= bishop_effect(sq);
            }
            let mut bb = king_effect(ksq);
            while let Some(sq) = pop_lsb(&mut bb) {
                target |= bishop_effect(sq) & enemy_field;
            }
            target &= !bit_of(ksq);
        }
        6 => {
            let mut bb = horse_effect(ksq);
            while let Some(sq) = pop_lsb(&mut bb) {
                target |= horse_effect(sq);
            }
            target &= !bit_of(ksq);
        }
        _ => {}
    }

    target
}

fn bit_of(sq: i8) -> u128 {
    let shift = u32::try_from(sq).expect("square index is non-negative");
    1u128 << shift
}

fn pop_lsb(bb: &mut u128) -> Option<i8> {
    if *bb == 0 {
        return None;
    }
    let idx = bb.trailing_zeros();
    *bb &= *bb - 1;
    Some(i8::try_from(idx).expect("square index fits in i8"))
}

fn enemy_field_mask(us: usize) -> u128 {
    let ranks: [i8; 3] = if us == 0 { [0, 1, 2] } else { [6, 7, 8] };
    let mut mask = 0u128;
    for rank in ranks {
        for file in 0..9 {
            let sq = file * 9 + rank;
            mask |= bit_of(sq);
        }
    }
    mask
}

fn pawn_effect(color: usize, sq: i8) -> u128 {
    generate_pawn_attack(sq, color)
}

fn knight_effect(color: usize, sq: i8) -> u128 {
    generate_knight_attack(sq, color)
}

fn silver_effect(color: usize, sq: i8) -> u128 {
    generate_silver_attack(sq, color)
}

fn gold_effect(color: usize, sq: i8) -> u128 {
    generate_gold_attack(sq, color)
}

fn king_effect(sq: i8) -> u128 {
    generate_king_attack(sq)
}

fn lance_step_effect(color: usize, sq: i8) -> u128 {
    let (black, white) = generate_lance_beams(sq);
    if color == 0 {
        black
    } else {
        white
    }
}

fn bishop_effect(sq: i8) -> u128 {
    let (ne, se, sw, nw) = generate_bishop_beams(sq);
    ne | se | sw | nw
}

fn horse_effect(sq: i8) -> u128 {
    bishop_effect(sq) | king_effect(sq)
}

/// 非滑り駒（歩・桂・銀・金・玉）の攻撃テーブルを生成
fn generate_non_slider_attacks() -> String {
    let mut output = String::new();

    // 歩の攻撃テーブル
    output.push_str("/// 歩の攻撃パターン \\[sq\\]\\[color\\]\n");
    output.push_str("pub const PAWN_ATTACKS: [[Bitboard; 2]; 81] = [\n");
    for sq in 0..81 {
        output.push_str("    [\n");
        for color in 0..2 {
            let bb = generate_pawn_attack(sq, color);
            let _ = writeln!(output, "        Bitboard::from_packed_bits({bb}u128),");
        }
        output.push_str("    ],\n");
    }
    output.push_str("];\n\n");

    // 桂馬の攻撃テーブル
    output.push_str("/// 桂馬の攻撃パターン \\[sq\\]\\[color\\]\n");
    output.push_str("pub const KNIGHT_ATTACKS: [[Bitboard; 2]; 81] = [\n");
    for sq in 0..81 {
        output.push_str("    [\n");
        for color in 0..2 {
            let bb = generate_knight_attack(sq, color);
            let _ = writeln!(output, "        Bitboard::from_packed_bits({bb}u128),");
        }
        output.push_str("    ],\n");
    }
    output.push_str("];\n\n");

    // 銀の攻撃テーブル
    output.push_str("/// 銀の攻撃パターン \\[sq\\]\\[color\\]\n");
    output.push_str("pub const SILVER_ATTACKS: [[Bitboard; 2]; 81] = [\n");
    for sq in 0..81 {
        output.push_str("    [\n");
        for color in 0..2 {
            let bb = generate_silver_attack(sq, color);
            let _ = writeln!(output, "        Bitboard::from_packed_bits({bb}u128),");
        }
        output.push_str("    ],\n");
    }
    output.push_str("];\n\n");

    // 金の攻撃テーブル
    output.push_str("/// 金の攻撃パターン \\[sq\\]\\[color\\]\n");
    output.push_str("pub const GOLD_ATTACKS: [[Bitboard; 2]; 81] = [\n");
    for sq in 0..81 {
        output.push_str("    [\n");
        for color in 0..2 {
            let bb = generate_gold_attack(sq, color);
            let _ = writeln!(output, "        Bitboard::from_packed_bits({bb}u128),");
        }
        output.push_str("    ],\n");
    }
    output.push_str("];\n\n");

    // 玉の攻撃テーブル
    output.push_str("/// 玉の攻撃パターン \\[sq\\]\n");
    output.push_str("pub const KING_ATTACKS: [Bitboard; 81] = [\n");
    for sq in 0..81 {
        let bb = generate_king_attack(sq);
        let _ = writeln!(output, "    Bitboard::from_packed_bits({bb}u128),");
    }
    output.push_str("];\n\n");

    output
}

/// 歩の攻撃パターンを生成
fn generate_pawn_attack(sq: i8, color: usize) -> u128 {
    let file = sq / 9;
    let rank = sq % 9;

    // 先手(BLACK=0): 前方(-1)、後手(WHITE=1): 前方(+1)
    let forward_rank = if color == 0 { rank - 1 } else { rank + 1 };

    if !(0..9).contains(&forward_rank) {
        return 0; // 盤外
    }

    let target_sq = file * 9 + forward_rank;
    1u128 << target_sq
}

/// 桂馬の攻撃パターンを生成
fn generate_knight_attack(sq: i8, color: usize) -> u128 {
    let file = sq / 9;
    let rank = sq % 9;

    let mut attacks = 0u128;

    // 先手(BLACK=0): 2つ前、左右1マス
    // 後手(WHITE=1): 2つ後、左右1マス
    let forward_offset = if color == 0 { -2 } else { 2 };
    let target_rank = rank + forward_offset;

    if !(0..9).contains(&target_rank) {
        return 0;
    }

    // 左1マス
    if (0..9).contains(&(file + 1)) {
        let target_sq = (file + 1) * 9 + target_rank;
        attacks |= 1u128 << target_sq;
    }

    // 右1マス
    if (0..9).contains(&(file - 1)) {
        let target_sq = (file - 1) * 9 + target_rank;
        attacks |= 1u128 << target_sq;
    }

    attacks
}

/// 銀の攻撃パターンを生成
fn generate_silver_attack(sq: i8, color: usize) -> u128 {
    let file = sq / 9;
    let rank = sq % 9;

    let mut attacks = 0u128;

    // 先手(BLACK=0): 前方5マス（前1、斜め前2、斜め後2）
    // 後手(WHITE=1): 前方5マス（後1、斜め後2、斜め前2）
    let forward = if color == 0 { -1 } else { 1 };
    let backward = -forward;

    // 前方1マス
    if let Some(target_sq) = add_direction(file, rank, 0, forward) {
        attacks |= 1u128 << target_sq;
    }

    // 斜め前左
    if let Some(target_sq) = add_direction(file, rank, 1, forward) {
        attacks |= 1u128 << target_sq;
    }

    // 斜め前右
    if let Some(target_sq) = add_direction(file, rank, -1, forward) {
        attacks |= 1u128 << target_sq;
    }

    // 斜め後左
    if let Some(target_sq) = add_direction(file, rank, 1, backward) {
        attacks |= 1u128 << target_sq;
    }

    // 斜め後右
    if let Some(target_sq) = add_direction(file, rank, -1, backward) {
        attacks |= 1u128 << target_sq;
    }

    attacks
}

/// 金の攻撃パターンを生成
fn generate_gold_attack(sq: i8, color: usize) -> u128 {
    let file = sq / 9;
    let rank = sq % 9;

    let mut attacks = 0u128;

    // 基本の利き（先手基準）: 前後左右 + 斜め前2方向
    #[allow(clippy::cast_possible_wrap)]
    let black_offsets: &[(i8, i8)] = &[
        (0, -1),  // 前方
        (1, -1),  // 左斜め前
        (-1, -1), // 右斜め前
        (1, 0),   // 左
        (-1, 0),  // 右
        (0, 1),   // 後方
    ];

    let white_offsets: &[(i8, i8)] = &[
        (0, 1),  // 前方（後手基準）
        (-1, 1), // 左斜め前（後手視点）
        (1, 1),  // 右斜め前（後手視点）
        (-1, 0), // 左
        (1, 0),  // 右
        (0, -1), // 後方
    ];

    let offsets = if color == 0 { black_offsets } else { white_offsets };

    for &(df, dr) in offsets {
        if let Some(target_sq) = add_direction(file, rank, df, dr) {
            attacks |= 1u128 << target_sq;
        }
    }

    attacks
}

/// 玉の攻撃パターンを生成
fn generate_king_attack(sq: i8) -> u128 {
    let file = sq / 9;
    let rank = sq % 9;

    let mut attacks = 0u128;

    // 8方向すべて
    for df in -1..=1 {
        for dr in -1..=1 {
            if df == 0 && dr == 0 {
                continue; // 自分自身はスキップ
            }

            if let Some(target_sq) = add_direction(file, rank, df, dr) {
                attacks |= 1u128 << target_sq;
            }
        }
    }

    attacks
}

/// 方向を加算してSquareを取得（盤内チェック付き）
fn add_direction(file: i8, rank: i8, df: i8, dr: i8) -> Option<i8> {
    let new_file = file + df;
    let new_rank = rank + dr;

    if (0..9).contains(&new_file) && (0..9).contains(&new_rank) {
        Some(new_file * 9 + new_rank)
    } else {
        None
    }
}

/// 滑り駒（香・角・飛）のビームテーブルを生成
fn generate_slider_beams() -> String {
    let mut output = String::new();

    // LanceBeams構造体定義
    output.push_str(
        r"/// 香車のビーム構造
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LanceBeams {
    pub black: Bitboard,
    pub white: Bitboard,
}

",
    );

    // BishopBeams構造体定義
    output.push_str(
        r"/// 角のビーム構造
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BishopBeams {
    pub ne: Bitboard,
    pub se: Bitboard,
    pub sw: Bitboard,
    pub nw: Bitboard,
}

",
    );

    // RookBeams構造体定義
    output.push_str(
        r"/// 飛車のビーム構造
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RookBeams {
    pub n: Bitboard,
    pub e: Bitboard,
    pub s: Bitboard,
    pub w: Bitboard,
}

",
    );

    // 香車のビームテーブル
    output.push_str("/// 香車のビームテーブル \\[sq\\]\n");
    output.push_str("pub const LANCE_BEAMS: [LanceBeams; 81] = [\n");
    for sq in 0..81 {
        let (black_beam, white_beam) = generate_lance_beams(sq);
        let _ = writeln!(
            output,
            "    LanceBeams {{ black: Bitboard::from_packed_bits({black_beam}u128), white: Bitboard::from_packed_bits({white_beam}u128) }},"
        );
    }
    output.push_str("];\n\n");

    // 角のビームテーブル
    output.push_str("/// 角のビームテーブル \\[sq\\]\n");
    output.push_str("pub const BISHOP_BEAMS: [BishopBeams; 81] = [\n");
    for sq in 0..81 {
        let (ne, se, sw, nw) = generate_bishop_beams(sq);
        let _ = writeln!(
            output,
            "    BishopBeams {{ ne: Bitboard::from_packed_bits({ne}u128), se: Bitboard::from_packed_bits({se}u128), sw: Bitboard::from_packed_bits({sw}u128), nw: Bitboard::from_packed_bits({nw}u128) }},"
        );
    }
    output.push_str("];\n\n");

    // 飛車のビームテーブル
    output.push_str("/// 飛車のビームテーブル \\[sq\\]\n");
    output.push_str("pub const ROOK_BEAMS: [RookBeams; 81] = [\n");
    for sq in 0..81 {
        let (n, e, s, w) = generate_rook_beams(sq);
        let _ = writeln!(
            output,
            "    RookBeams {{ n: Bitboard::from_packed_bits({n}u128), e: Bitboard::from_packed_bits({e}u128), s: Bitboard::from_packed_bits({s}u128), w: Bitboard::from_packed_bits({w}u128) }},"
        );
    }
    output.push_str("];\n\n");

    output
}

/// Qugiyの方向利きステップテーブルを生成
fn generate_qugiy_step_effects() -> String {
    let mut output = String::new();

    output.push_str(
        "/// Qugiyの方向利きステップテーブル \\[dir\\]\\[sq\\]\n\
/// dir index: 0=RU, 1=R, 2=RD, 3=LU, 4=L, 5=LD\n\
pub const QUGIY_STEP_EFFECT: [[Bitboard; 81]; 6] = [\n",
    );

    // YaneuraOuのrayEffectに合わせ、RU/R/RD方向はbyte_reverseしたマスクを持つ。
    let directions: [(i8, i8); 6] = [
        (-1, -1), // RU
        (-1, 0),  // R
        (-1, 1),  // RD
        (1, -1),  // LU
        (1, 0),   // L
        (1, 1),   // LD
    ];

    let reverse_dirs = [true, true, true, false, false, false];

    for (dir_idx, (df, dr)) in directions.iter().enumerate() {
        output.push_str("    [\n");
        for sq in 0..81 {
            let mut mask = generate_ray_mask(sq, *df, *dr);
            if reverse_dirs[dir_idx] {
                mask = byte_reverse_u128(mask);
            }
            let _ = writeln!(output, "        Bitboard::from_packed_bits_unchecked({mask}u128),");
        }
        output.push_str("    ],\n");
    }

    output.push_str("];\n\n");
    output
}

/// Qugiyの飛車マスクテーブルを生成
fn generate_qugiy_rook_masks() -> String {
    let mut output = String::new();

    output.push_str("/// Qugiyの飛車マスクテーブル \\[sq\\]\\[lo/hi\\]\n");
    output.push_str("pub const QUGIY_ROOK_MASK: [[Bitboard; 2]; 81] = [\n");

    for sq in 0..81 {
        let left = generate_ray_parts(sq, 1, 0);
        let right = generate_ray_parts(sq, -1, 0);
        let right_rev = (right.1.swap_bytes(), right.0.swap_bytes());
        let (hi, lo) = unpack_parts(right_rev, left);
        let hi = parts_to_raw_u128(hi);
        let lo = parts_to_raw_u128(lo);
        output.push_str("    [\n");
        let _ = writeln!(output, "        Bitboard::from_packed_bits_unchecked({lo}u128),");
        let _ = writeln!(output, "        Bitboard::from_packed_bits_unchecked({hi}u128),");
        output.push_str("    ],\n");
    }

    output.push_str("];\n\n");
    output
}

/// Qugiyの角マスクテーブルを生成
#[allow(clippy::similar_names)]
fn generate_qugiy_bishop_masks() -> String {
    let mut output = String::new();

    output.push_str("/// Qugiyの角マスクテーブル \\[sq\\]\\[part\\]\n");
    output.push_str("pub const QUGIY_BISHOP_MASK: [[Bitboard256; 2]; 81] = [\n");

    for sq in 0..81 {
        let lu = generate_ray_mask(sq, 1, -1);
        let ld = generate_ray_mask(sq, 1, 1);
        let ru = byte_reverse_u128(generate_ray_mask(sq, -1, -1));
        let rd = byte_reverse_u128(generate_ray_mask(sq, -1, 1));
        output.push_str("    [\n");
        for part in 0..2 {
            let lu_part = extract64(lu, part);
            let ru_part = extract64(ru, part);
            let ld_part = extract64(ld, part);
            let rd_part = extract64(rd, part);
            let _ = writeln!(
                output,
                "        Bitboard256::new({lu_part}, {ru_part}, {ld_part}, {rd_part}),"
            );
        }
        output.push_str("    ],\n");
    }

    output.push_str("];\n\n");
    output
}

fn generate_ray_mask(sq: i8, df: i8, dr: i8) -> u128 {
    let file = sq / 9;
    let rank = sq % 9;

    let mut mask = 0u128;
    let mut f = file + df;
    let mut r = rank + dr;
    while (0..9).contains(&f) && (0..9).contains(&r) {
        let target_sq = f * 9 + r;
        mask |= 1u128 << raw_bit_index(target_sq);
        f += df;
        r += dr;
    }
    mask
}

#[allow(clippy::cast_possible_truncation)]
const fn parts_to_raw_u128(parts: (u64, u64)) -> u128 {
    (parts.0 as u128) | ((parts.1 as u128) << 64)
}

fn square_parts(sq: i8) -> (u64, u64) {
    let file = sq / 9;
    let rank = sq % 9;
    if file < 7 {
        let shift = u32::try_from(file * 9 + rank).expect("square index fits in u32");
        (1u64 << shift, 0)
    } else {
        let shift = u32::try_from((file - 7) * 9 + rank).expect("square index fits in u32");
        (0, 1u64 << shift)
    }
}

fn generate_ray_parts(sq: i8, df: i8, dr: i8) -> (u64, u64) {
    let file = sq / 9;
    let rank = sq % 9;

    let mut p0 = 0u64;
    let mut p1 = 0u64;
    let mut f = file + df;
    let mut r = rank + dr;
    while (0..9).contains(&f) && (0..9).contains(&r) {
        let target_sq = f * 9 + r;
        let (b0, b1) = square_parts(target_sq);
        p0 |= b0;
        p1 |= b1;
        f += df;
        r += dr;
    }
    (p0, p1)
}

#[allow(clippy::cast_possible_truncation)]
const fn unpack_parts(hi_in: (u64, u64), lo_in: (u64, u64)) -> ((u64, u64), (u64, u64)) {
    let hi_out = (lo_in.1, hi_in.1);
    let lo_out = (lo_in.0, hi_in.0);
    (hi_out, lo_out)
}

#[allow(clippy::cast_possible_truncation)]
const fn extract64(packed_bits: u128, part: usize) -> u64 {
    if part == 0 {
        packed_bits as u64
    } else {
        (packed_bits >> 64) as u64
    }
}

#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
const fn raw_bit_index(sq: i8) -> u32 {
    if sq >= 63 {
        (sq as u32) + 1
    } else {
        sq as u32
    }
}

#[allow(clippy::cast_possible_truncation)]
const fn byte_reverse_u128(packed_bits: u128) -> u128 {
    let low = packed_bits as u64;
    let high = (packed_bits >> 64) as u64;
    ((low.swap_bytes() as u128) << 64) | (high.swap_bytes() as u128)
}

/// 香車のビームを生成（先手：北、後手：南）
fn generate_lance_beams(sq: i8) -> (u128, u128) {
    let file = sq / 9;
    let rank = sq % 9;

    let mut black_beam = 0u128; // 先手：北方向（rank減少）
    let mut white_beam = 0u128; // 後手：南方向（rank増加）

    // 先手（BLACK）：北方向（rank: current-1, current-2, ..., 0）
    for r in (0..rank).rev() {
        let target_sq = file * 9 + r;
        black_beam |= 1u128 << target_sq;
    }

    // 後手（WHITE）：南方向（rank: current+1, current+2, ..., 8）
    for r in (rank + 1)..9 {
        let target_sq = file * 9 + r;
        white_beam |= 1u128 << target_sq;
    }

    (black_beam, white_beam)
}

/// 角のビームを生成（4方向：北東、南東、南西、北西）
#[allow(clippy::similar_names)]
fn generate_bishop_beams(sq: i8) -> (u128, u128, u128, u128) {
    let file = sq / 9;
    let rank = sq % 9;

    let mut ne_beam = 0u128; // 北東：file+1, rank-1
    let mut se_beam = 0u128; // 南東：file+1, rank+1
    let mut sw_beam = 0u128; // 南西：file-1, rank+1
    let mut nw_beam = 0u128; // 北西：file-1, rank-1

    // 北東方向
    let mut f = file + 1;
    let mut r = rank - 1;
    while (0..9).contains(&f) && (0..9).contains(&r) {
        let target_sq = f * 9 + r;
        ne_beam |= 1u128 << target_sq;
        f += 1;
        r -= 1;
    }

    // 南東方向
    let mut f = file + 1;
    let mut r = rank + 1;
    while (0..9).contains(&f) && (0..9).contains(&r) {
        let target_sq = f * 9 + r;
        se_beam |= 1u128 << target_sq;
        f += 1;
        r += 1;
    }

    // 南西方向
    let mut f = file - 1;
    let mut r = rank + 1;
    while (0..9).contains(&f) && (0..9).contains(&r) {
        let target_sq = f * 9 + r;
        sw_beam |= 1u128 << target_sq;
        f -= 1;
        r += 1;
    }

    // 北西方向
    let mut f = file - 1;
    let mut r = rank - 1;
    while (0..9).contains(&f) && (0..9).contains(&r) {
        let target_sq = f * 9 + r;
        nw_beam |= 1u128 << target_sq;
        f -= 1;
        r -= 1;
    }

    (ne_beam, se_beam, sw_beam, nw_beam)
}

/// 飛車のビームを生成（4方向：北、東、南、西）
fn generate_rook_beams(sq: i8) -> (u128, u128, u128, u128) {
    let file = sq / 9;
    let rank = sq % 9;

    let mut n_beam = 0u128; // 北：rank-1, rank-2, ..., 0
    let mut e_beam = 0u128; // 東：file+1, file+2, ..., 8
    let mut s_beam = 0u128; // 南：rank+1, rank+2, ..., 8
    let mut w_beam = 0u128; // 西：file-1, file-2, ..., 0

    // 北方向
    for r in (0..rank).rev() {
        let target_sq = file * 9 + r;
        n_beam |= 1u128 << target_sq;
    }

    // 東方向
    for f in (file + 1)..9 {
        let target_sq = f * 9 + rank;
        e_beam |= 1u128 << target_sq;
    }

    // 南方向
    for r in (rank + 1)..9 {
        let target_sq = file * 9 + r;
        s_beam |= 1u128 << target_sq;
    }

    // 西方向
    for f in (0..file).rev() {
        let target_sq = f * 9 + rank;
        w_beam |= 1u128 << target_sq;
    }

    (n_beam, e_beam, s_beam, w_beam)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;

    #[test]
    fn test_generate_square_tables() {
        // テンポラリディレクトリにテーブルを生成
        let temp_dir = std::env::temp_dir().join("rshogi_build_test");
        fs::create_dir_all(&temp_dir).unwrap();

        // テーブル生成
        generate_square_tables(&temp_dir);

        // 生成されたファイルを読み込み
        let content = fs::read_to_string(temp_dir.join("square_tables.rs"))
            .expect("Failed to read generated file");

        // 基本的な検証
        assert!(content.contains("SQUARE_TO_FILE"));
        assert!(content.contains("SQUARE_TO_RANK"));
        assert!(content.contains("[File; 82]")); // 82要素 (0..81 + SQ_NONE)
        assert!(content.contains("[Rank; 82]")); // 82要素

        // SQ_11 (sq=0) は File::FILE_1 (file=0), Rank::RANK_1 (rank=0)
        assert!(content.contains("File(0),")); // 最初の要素
        assert!(content.contains("Rank(0),")); // 最初の要素

        // SQ_99 (sq=80) は File::FILE_9 (file=8), Rank::RANK_9 (rank=8)
        // sq=80 → file=80/9=8, rank=80%9=8

        // SQ_NONE用のエントリ
        assert!(content.contains("File(-1), // SQ_NONE"));
        assert!(content.contains("Rank(-1), // SQ_NONE"));

        // クリーンアップ
        let _ = fs::remove_dir_all(&temp_dir);
    }
}
