use std::sync::LazyLock;
use std::sync::atomic::{AtomicI32, AtomicU8, Ordering};

use crate::position::{BoardEffects, Position};
use crate::types::{Color, Piece, PieceType, Square, Value};

// =============================================================================
// パス権評価（Finite Pass Rights）
// =============================================================================

/// パス権評価の序盤値（デフォルト: 0cp - 評価関数に委ねる）
pub const DEFAULT_PASS_RIGHT_VALUE_EARLY: i32 = 0;
/// パス権評価の終盤値（デフォルト: 0cp - 評価関数に委ねる）
pub const DEFAULT_PASS_RIGHT_VALUE_LATE: i32 = 0;
/// 序盤の終わり（この手数まで EARLY_VALUE を使用）
const EARLY_PLY: u16 = 40;
/// 終盤の始まり（この手数以降 LATE_VALUE を使用）
const LATE_PLY: u16 = 120;

/// ランタイムで切り替え可能なパス権評価値（序盤）
static PASS_RIGHT_VALUE_EARLY: AtomicI32 = AtomicI32::new(DEFAULT_PASS_RIGHT_VALUE_EARLY);
/// ランタイムで切り替え可能なパス権評価値（終盤）
static PASS_RIGHT_VALUE_LATE: AtomicI32 = AtomicI32::new(DEFAULT_PASS_RIGHT_VALUE_LATE);

/// 現在のパス権評価値を取得（後方互換のため終盤値を返す）
pub fn get_pass_right_value() -> i32 {
    PASS_RIGHT_VALUE_LATE.load(Ordering::Relaxed)
}

/// パス権評価値を設定（序盤・終盤両方を同じ値に設定）
pub fn set_pass_right_value(value: i32) {
    PASS_RIGHT_VALUE_EARLY.store(value, Ordering::Relaxed);
    PASS_RIGHT_VALUE_LATE.store(value, Ordering::Relaxed);
}

/// パス権評価値を設定（序盤・終盤を個別に設定）
pub fn set_pass_right_value_phased(early: i32, late: i32) {
    PASS_RIGHT_VALUE_EARLY.store(early, Ordering::Relaxed);
    PASS_RIGHT_VALUE_LATE.store(late, Ordering::Relaxed);
}

/// 手数に応じたパス権評価値を計算（純粋関数）
///
/// グローバル変数を参照せず、引数のみで計算する。
/// テストやデバッグで使用可能。
#[inline]
fn compute_pass_right_value(ply: u16, early_value: i32, late_value: i32) -> i32 {
    if ply <= EARLY_PLY {
        early_value
    } else if ply >= LATE_PLY {
        late_value
    } else {
        // 線形補間
        let ratio = (ply - EARLY_PLY) as i32;
        let range = (LATE_PLY - EARLY_PLY) as i32;
        early_value + (late_value - early_value) * ratio / range
    }
}

/// 手数に応じたパス権評価値を計算（グローバル設定を使用）
#[inline]
fn pass_right_value_by_ply(ply: u16) -> i32 {
    let early_value = PASS_RIGHT_VALUE_EARLY.load(Ordering::Relaxed);
    let late_value = PASS_RIGHT_VALUE_LATE.load(Ordering::Relaxed);
    compute_pass_right_value(ply, early_value, late_value)
}

/// パス権の評価値を計算（手番側視点）
///
/// evaluate_material_with_pass() 実装
/// - `(自分のパス権 - 相手のパス権) * pass_right_value_by_ply(ply)`
///
/// 手数に応じてパス権の価値が変化する:
/// - 序盤(〜40手): 50cp（テンポが重要なため低い）
/// - 終盤(120手〜): 200cp（ツークツワンク回避のため高い）
/// - 中間: 線形補間
///
/// パス権ルールが無効な場合は 0 を返す。
#[inline]
pub fn evaluate_pass_rights(pos: &Position, ply: u16) -> Value {
    if !pos.is_pass_rights_enabled() {
        return Value::ZERO;
    }

    let us = pos.side_to_move();
    let them = !us;
    let our_rights = pos.pass_rights(us) as i32;
    let their_rights = pos.pass_rights(them) as i32;
    let value = pass_right_value_by_ply(ply);

    Value::new((our_rights - their_rights) * value)
}

// =============================================================================
// パス手評価ボーナス
// =============================================================================

/// パス手の評価値ボーナス（デフォルト: 0）
///
/// 正の値を設定するとパス手が選ばれやすくなる。
/// USI オプション PassMoveBonus で調整可能。
/// 手数によるスケーリングは行わず、常に設定値の100%が適用される。
const DEFAULT_PASS_MOVE_BONUS: i32 = 0;

/// ランタイムで切り替え可能なパス手ボーナス
static PASS_MOVE_BONUS: AtomicI32 = AtomicI32::new(DEFAULT_PASS_MOVE_BONUS);

/// 現在のパス手ボーナス設定値を取得
#[inline]
pub fn get_pass_move_bonus() -> i32 {
    PASS_MOVE_BONUS.load(Ordering::Relaxed)
}

/// パス手ボーナスを設定
pub fn set_pass_move_bonus(value: i32) {
    PASS_MOVE_BONUS.store(value, Ordering::Relaxed);
}

/// パス手ボーナスを取得（スケーリングなし、常に100%）
///
/// 以前は手数によるスケーリング（序盤抑制）を行っていたが、
/// 実際の将棋では序盤〜中盤の手待ち局面でパスが有効であり、
/// 終盤は詰めろ等で逆にパスが不利なため、スケーリングを廃止。
#[inline]
pub fn get_scaled_pass_move_bonus(_ply: i32) -> i32 {
    PASS_MOVE_BONUS.load(Ordering::Relaxed)
}

/// Material評価の適用レベル（YaneuraOu MaterialLv に対応）
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MaterialLevel {
    Lv1,
    Lv2,
    Lv3,
    Lv4,
    Lv7,
    Lv8,
    Lv9,
}

impl MaterialLevel {
    /// レベル値から MaterialLevel を取得
    ///
    /// 注意: レベル5, 6は未実装（YaneuraOu互換性のため欠番）
    pub fn from_value(v: u8) -> Option<Self> {
        match v {
            1 => Some(MaterialLevel::Lv1),
            2 => Some(MaterialLevel::Lv2),
            3 => Some(MaterialLevel::Lv3),
            4 => Some(MaterialLevel::Lv4),
            7 => Some(MaterialLevel::Lv7),
            8 => Some(MaterialLevel::Lv8),
            9 => Some(MaterialLevel::Lv9),
            _ => None,
        }
    }

    /// レベル値を取得
    pub fn value(self) -> u8 {
        match self {
            MaterialLevel::Lv1 => 1,
            MaterialLevel::Lv2 => 2,
            MaterialLevel::Lv3 => 3,
            MaterialLevel::Lv4 => 4,
            MaterialLevel::Lv7 => 7,
            MaterialLevel::Lv8 => 8,
            MaterialLevel::Lv9 => 9,
        }
    }
}

/// デフォルトのMaterial評価レベル（YaneuraOu MaterialLv9 相当）
pub const DEFAULT_MATERIAL_LEVEL: MaterialLevel = MaterialLevel::Lv9;

/// ランタイムで切り替え可能なMaterial評価レベル
/// 値は MaterialLevel::value() の戻り値 (1, 2, 3, 4, 7, 8, 9)
///
/// 注意: Ordering::Relaxed を使用しているが、MaterialLevel は探索開始前
/// （USI isready / ベンチマーク開始時）に設定される想定のため問題ない。
/// 探索中に変更されることは想定していない。
static MATERIAL_LEVEL: AtomicU8 = AtomicU8::new(9);

/// 現在のMaterial評価レベルを取得
pub fn get_material_level() -> MaterialLevel {
    let v = MATERIAL_LEVEL.load(Ordering::Relaxed);
    debug_assert!(
        MaterialLevel::from_value(v).is_some(),
        "Invalid MaterialLevel value in AtomicU8: {v}"
    );
    MaterialLevel::from_value(v).unwrap_or(DEFAULT_MATERIAL_LEVEL)
}

/// Material評価レベルを設定
pub fn set_material_level(level: MaterialLevel) {
    MATERIAL_LEVEL.store(level.value(), Ordering::Relaxed);
}

/// Material評価で盤面の利きを使うか
pub fn material_needs_board_effects() -> bool {
    matches!(
        get_material_level(),
        MaterialLevel::Lv3
            | MaterialLevel::Lv4
            | MaterialLevel::Lv7
            | MaterialLevel::Lv8
            | MaterialLevel::Lv9
    )
}

/// Apery(WCSC26)準拠の駒価値
pub(crate) fn base_piece_value(pt: PieceType) -> i32 {
    match pt {
        PieceType::Pawn => 90,
        PieceType::Lance => 315,
        PieceType::Knight => 405,
        PieceType::Silver => 495,
        PieceType::Bishop => 855,
        PieceType::Rook => 990,
        PieceType::Gold => 540,
        PieceType::King => 15000,
        PieceType::ProPawn => 540,
        PieceType::ProLance => 540,
        PieceType::ProKnight => 540,
        PieceType::ProSilver => 540,
        PieceType::Horse => 945,
        PieceType::Dragon => 1395,
    }
}

#[inline]
pub(crate) fn signed_piece_value(pc: Piece) -> i32 {
    if pc.is_none() {
        return 0;
    }
    let sign = if pc.color() == Color::Black { 1 } else { -1 };
    sign * base_piece_value(pc.piece_type())
}

#[inline]
pub(crate) fn hand_piece_value(color: Color, pt: PieceType) -> i32 {
    let sign = if color == Color::Black { 1 } else { -1 };
    sign * base_piece_value(pt)
}

/// material_valueをフル再計算（StateInfoの再初期化用）
pub fn compute_material_value(pos: &Position) -> Value {
    let mut score = 0i32;

    for sq in pos.occupied().iter() {
        score += signed_piece_value(pos.piece_on(sq));
    }

    for color in [Color::Black, Color::White] {
        let hand = pos.hand(color);
        for pt in PieceType::HAND_PIECES {
            score += hand.count(pt) as i32 * hand_piece_value(color, pt);
        }
    }

    Value::new(score)
}

#[inline]
fn dist(a: Square, b: Square) -> usize {
    let df = (a.file().index() as i32 - b.file().index() as i32).unsigned_abs() as usize;
    let dr = (a.rank().index() as i32 - b.rank().index() as i32).unsigned_abs() as usize;
    df.max(dr)
}

/// 距離に応じた利き評価値テーブルを生成（base * 1024 / (distance+1)）
const fn make_effect_values(base: i32) -> [i32; 9] {
    let mut arr = [0; 9];
    let mut i = 0;
    while i < 9 {
        arr[i] = base * 1024 / (i as i32 + 1);
        i += 1;
    }
    arr
}

const LV3_OUR_EFFECT_VALUE: [i32; 9] = make_effect_values(68);
const LV3_THEIR_EFFECT_VALUE: [i32; 9] = make_effect_values(96);
const LV4_OUR_EFFECT_VALUE: [i32; 9] = make_effect_values(85);
const LV4_THEIR_EFFECT_VALUE: [i32; 9] = make_effect_values(98);
const LV7_OUR_EFFECT_VALUE: [i32; 9] = make_effect_values(83);
const LV7_THEIR_EFFECT_VALUE: [i32; 9] = make_effect_values(92);

/// 利きの多重度に応じた評価値テーブル（LazyLockによる遅延初期化）
static MULTI_EFFECT_VALUE: LazyLock<[i32; 11]> = LazyLock::new(|| {
    let mut arr = [0i32; 11];
    // YaneuraOu の optimizer が出力した近似式
    // 6365 - pow(0.8525, m-1) * 5341 (m=1..10)
    // 利きが増えるほど逓減しつつ上限に漸近する特性を再現するための定数
    for (m, value) in arr.iter_mut().enumerate().skip(1) {
        *value = (6365.0 - 0.8525f64.powi((m as i32) - 1) * 5341.0) as i32;
    }
    arr
});

struct Lv7Tables {
    our_effect_table: [[[i32; 3]; Square::NUM]; Square::NUM],
    their_effect_table: [[[i32; 3]; Square::NUM]; Square::NUM],
}

/// Lv7評価用のテーブル（LazyLockによる遅延初期化）
static LV7_TABLES: LazyLock<Lv7Tables> = LazyLock::new(|| {
    let mv = &*MULTI_EFFECT_VALUE;
    let mut our_effect_table = [[[0i32; 3]; Square::NUM]; Square::NUM];
    let mut their_effect_table = [[[0i32; 3]; Square::NUM]; Square::NUM];

    for king_sq in Square::all() {
        for sq in Square::all() {
            let d = dist(sq, king_sq);
            for m in 0..3 {
                our_effect_table[king_sq.index()][sq.index()][m] =
                    mv[m] * LV7_OUR_EFFECT_VALUE[d] / (1024 * 1024);
                their_effect_table[king_sq.index()][sq.index()][m] =
                    mv[m] * LV7_THEIR_EFFECT_VALUE[d] / (1024 * 1024);
            }
        }
    }

    Lv7Tables {
        our_effect_table,
        their_effect_table,
    }
});

// 自駒への味方/敵の利きが 0/1/2 のときの補正係数（MaterialLv7-9）
// 値は YaneuraOu から移植。後で 4096 で割って駒価値に掛ける。
const OUR_EFFECT_TO_OUR_PIECE: [i32; 3] = [0, 33, 43];
const THEIR_EFFECT_TO_OUR_PIECE: [i32; 3] = [0, 113, 122];

// 玉の位置ボーナス（先手視点の81升テーブル）
// インデックス: (8 - file) + rank * 9 （先手1段1筋が先頭、左から9筋→1筋の順）
// 高い値ほど玉にとって安全とみなす。後手は Inv(sq) でミラーし、符号を反転して用いる。
// 値は YaneuraOu MaterialLv8/9 から移植。
const KING_POS_BONUS: [i32; 81] = [
    875, 655, 830, 680, 770, 815, 720, 945, 755, 605, 455, 610, 595, 730, 610, 600, 590, 615, 565,
    640, 555, 525, 635, 565, 440, 600, 575, 520, 515, 580, 420, 640, 535, 565, 500, 510, 220, 355,
    240, 375, 340, 335, 305, 275, 320, 500, 530, 560, 445, 510, 395, 455, 490, 410, 345, 275, 250,
    355, 295, 280, 420, 235, 135, 335, 370, 385, 255, 295, 200, 265, 305, 305, 255, 225, 245, 295,
    200, 320, 275, 70, 200,
];

// 利き方向ごとの重み（direction_of の戻り値 0..9 に対応）
// 0:真上,1:右上上,2:右上,3:右右上,4:右,5:右右下,6:右下,7:右下下,8:真下,9:同じ升
const OUR_EFFECT_RATE: [i32; 10] = [1120, 1872, 112, 760, 744, 880, 1320, 600, 904, 1024];
const THEIR_EFFECT_RATE: [i32; 10] = [1056, 1714, 1688, 1208, 248, 240, 496, 816, 928, 1024];

fn king_pos_bonus(color: Color, sq: Square) -> i32 {
    // 後手側をミラーしてから参照する
    let target_sq = if color == Color::Black {
        sq
    } else {
        sq.inverse()
    };
    let idx = (8 - target_sq.file().index()) + target_sq.rank().index() * 9;
    let bonus = KING_POS_BONUS[idx];
    if color == Color::Black { bonus } else { -bonus }
}

fn direction_of(king: Square, sq: Square) -> usize {
    let mut df = sq.file().index() as i32 - king.file().index() as i32;
    let dr = sq.rank().index() as i32 - king.rank().index() as i32;

    if df > 0 {
        df = -df;
    }

    // 返り値の意味（YaneuraOuのバケット順）
    // 0: 真上, 1: 右上上, 2: 右上, 3: 右右上, 4: 右
    // 5: 右右下, 6: 右下, 7: 右下下, 8: 真下, 9: 同じ升
    if df == 0 && dr == 0 {
        return 9;
    }
    if df == 0 && dr < 0 {
        return 0;
    }
    if df > dr && dr < 0 {
        return 1;
    }
    if df == dr && dr < 0 {
        return 2;
    }
    if df < dr && dr < 0 {
        return 3;
    }
    if df < 0 && dr == 0 {
        return 4;
    }
    if df < -dr && dr > 0 {
        return 5;
    }
    if df == -dr && dr > 0 {
        return 6;
    }
    if df == 0 && dr > 0 {
        return 8;
    }
    if df > -dr && dr > 0 {
        return 7;
    }

    unreachable!("Unexpected direction calculation: df={df}, dr={dr}");
}

#[inline]
fn clamp_effect(count: u8, max: usize) -> usize {
    usize::min(count as usize, max)
}

fn eval_lv1(pos: &Position) -> i32 {
    pos.state().material_value.raw()
}

fn eval_lv2(pos: &Position) -> i32 {
    let mut score = pos.state().material_value.raw();
    for sq in pos.occupied().iter() {
        let pc = pos.piece_on(sq);
        score -= signed_piece_value(pc) * 104 / 1024;
    }
    score
}

fn eval_lv3(pos: &Position, effects: &BoardEffects) -> i32 {
    let mut score = pos.state().material_value.raw();
    let king_b = pos.king_square(Color::Black);
    let king_w = pos.king_square(Color::White);

    for sq in Square::all() {
        let e_b = effects.effect(Color::Black, sq) as i32;
        let e_w = effects.effect(Color::White, sq) as i32;

        let d_b = dist(sq, king_b);
        let d_w = dist(sq, king_w);

        let s_b = e_b * LV3_OUR_EFFECT_VALUE[d_b] / 1024 - e_w * LV3_THEIR_EFFECT_VALUE[d_b] / 1024;
        let s_w = e_w * LV3_OUR_EFFECT_VALUE[d_w] / 1024 - e_b * LV3_THEIR_EFFECT_VALUE[d_w] / 1024;

        score += s_b;
        score -= s_w;

        let pc = pos.piece_on(sq);
        if pc.is_some() {
            score -= signed_piece_value(pc) * 104 / 1024;
        }
    }

    score
}

fn eval_lv4(pos: &Position, effects: &BoardEffects) -> i32 {
    let mut score = pos.state().material_value.raw();
    let king_b = pos.king_square(Color::Black);
    let king_w = pos.king_square(Color::White);
    let mv = &*MULTI_EFFECT_VALUE;

    for sq in Square::all() {
        let e_b = clamp_effect(effects.effect(Color::Black, sq), 10);
        let e_w = clamp_effect(effects.effect(Color::White, sq), 10);

        let d_b = dist(sq, king_b);
        let d_w = dist(sq, king_w);

        let s_b = mv[e_b] * LV4_OUR_EFFECT_VALUE[d_b] / (1024 * 1024)
            - mv[e_w] * LV4_THEIR_EFFECT_VALUE[d_b] / (1024 * 1024);
        let s_w = mv[e_w] * LV4_OUR_EFFECT_VALUE[d_w] / (1024 * 1024)
            - mv[e_b] * LV4_THEIR_EFFECT_VALUE[d_w] / (1024 * 1024);

        score += s_b;
        score -= s_w;

        let pc = pos.piece_on(sq);
        if pc.is_some() {
            score -= signed_piece_value(pc) * 104 / 1024;
        }
    }

    score
}

fn eval_lv7(pos: &Position, effects: &BoardEffects) -> i32 {
    eval_lv7_like(pos, effects, false, false)
}

fn eval_lv8(pos: &Position, effects: &BoardEffects) -> i32 {
    eval_lv7_like(pos, effects, true, false)
}

fn eval_lv9(pos: &Position, effects: &BoardEffects) -> i32 {
    eval_lv7_like(pos, effects, true, true)
}

fn eval_lv7_like(
    pos: &Position,
    effects: &BoardEffects,
    use_king_bonus: bool,
    use_direction: bool,
) -> i32 {
    let mut score = pos.state().material_value.raw();
    let king_b = pos.king_square(Color::Black);
    let king_w = pos.king_square(Color::White);
    let inv_king_w = king_w.inverse();
    let tables = &*LV7_TABLES;

    for sq in Square::all() {
        let m1 = clamp_effect(effects.effect(Color::Black, sq), 2);
        let m2 = clamp_effect(effects.effect(Color::White, sq), 2);
        let pc = pos.piece_on(sq);
        let mut local = 0i32;

        if use_direction {
            let dir_b = direction_of(king_b, sq);
            let inv_sq = sq.inverse();
            let dir_w = direction_of(inv_king_w, inv_sq);

            local += tables.our_effect_table[king_b.index()][sq.index()][m1]
                * OUR_EFFECT_RATE[dir_b]
                / 1024;
            local -= tables.their_effect_table[king_b.index()][sq.index()][m2]
                * THEIR_EFFECT_RATE[dir_b]
                / 1024;
            local -= tables.our_effect_table[inv_king_w.index()][inv_sq.index()][m2]
                * OUR_EFFECT_RATE[dir_w]
                / 1024;
            local += tables.their_effect_table[inv_king_w.index()][inv_sq.index()][m1]
                * THEIR_EFFECT_RATE[dir_w]
                / 1024;
        } else {
            local += tables.our_effect_table[king_b.index()][sq.index()][m1];
            local -= tables.their_effect_table[king_b.index()][sq.index()][m2];

            let inv_sq = sq.inverse();
            local -= tables.our_effect_table[inv_king_w.index()][inv_sq.index()][m2];
            local += tables.their_effect_table[inv_king_w.index()][inv_sq.index()][m1];
        }

        // 玉の8近傍判定
        for color in [Color::Black, Color::White] {
            let king_sq = if color == Color::Black {
                king_b
            } else {
                king_w
            };
            if dist(sq, king_sq) == 1 {
                let effect_us = if color == Color::Black { m1 } else { m2 };
                let delta = if effect_us <= 1 {
                    if pc.is_none() || pc.color() != color {
                        11
                    } else {
                        -20
                    }
                } else if pc.is_none() || pc.color() != color {
                    0
                } else {
                    -11
                };
                local -= delta * if color == Color::Black { 1 } else { -1 };
            }
        }

        if pc.is_none() {
            // 何もない升
        } else if pc.piece_type() == PieceType::King {
            if use_king_bonus {
                local += king_pos_bonus(pc.color(), sq);
            }
        } else {
            let pv = signed_piece_value(pc);
            local -= pv * 104 / 1024;

            let effect_us = if pc.color() == Color::Black { m1 } else { m2 };
            let effect_them = if pc.color() == Color::Black { m2 } else { m1 };

            local += pv * OUR_EFFECT_TO_OUR_PIECE[effect_us] / 4096;
            local -= pv * THEIR_EFFECT_TO_OUR_PIECE[effect_them] / 4096;
        }

        score += local;
    }

    score
}

/// Material評価（NNUE未初期化時のフォールバック）
///
/// NNUEが初期化されていない場合の代替評価関数として使用される。
/// MaterialLevelに応じた評価を実行する。
///
/// # パフォーマンス特性
///
/// - Level 1-2: 駒の価値のみ（高速）
/// - Level 3-4: 利きの計算を含む（中速）
/// - Level 7-9: より複雑な評価（低速だがNNUEより高速）
pub fn evaluate_material(pos: &Position) -> Value {
    let level = get_material_level();
    let raw = match level {
        MaterialLevel::Lv1 => eval_lv1(pos),
        MaterialLevel::Lv2 => eval_lv2(pos),
        MaterialLevel::Lv3 => {
            let effects = pos.board_effects();
            eval_lv3(pos, effects)
        }
        MaterialLevel::Lv4 => {
            let effects = pos.board_effects();
            eval_lv4(pos, effects)
        }
        MaterialLevel::Lv7 => {
            let effects = pos.board_effects();
            eval_lv7(pos, effects)
        }
        MaterialLevel::Lv8 => {
            let effects = pos.board_effects();
            eval_lv8(pos, effects)
        }
        MaterialLevel::Lv9 => {
            let effects = pos.board_effects();
            eval_lv9(pos, effects)
        }
    };

    // 手番側視点の評価値を返す
    // 注意: パス権評価は探索側で動的に追加される（キャッシュ互換性のため）
    if pos.side_to_move() == Color::Black {
        Value::new(raw)
    } else {
        Value::new(-raw)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::position::SFEN_HIRATE;

    #[test]
    fn test_material_eval_hirate() {
        let mut pos = Position::new();
        pos.set_sfen(SFEN_HIRATE).unwrap();

        let value = evaluate_material(&pos);

        // 初期局面はほぼ互角（MaterialLvにより0から僅かにずれる場合がある）
        assert!(value.raw().abs() < 200);
    }

    #[test]
    fn test_material_level_value_roundtrip() {
        let levels = [
            MaterialLevel::Lv1,
            MaterialLevel::Lv2,
            MaterialLevel::Lv3,
            MaterialLevel::Lv4,
            MaterialLevel::Lv7,
            MaterialLevel::Lv8,
            MaterialLevel::Lv9,
        ];

        for level in levels {
            let value = level.value();
            let restored = MaterialLevel::from_value(value).unwrap();
            assert_eq!(level, restored);
        }
    }

    #[test]
    fn test_material_level_invalid_values() {
        // 欠番と範囲外の値
        assert!(MaterialLevel::from_value(0).is_none());
        assert!(MaterialLevel::from_value(5).is_none()); // 欠番
        assert!(MaterialLevel::from_value(6).is_none()); // 欠番
        assert!(MaterialLevel::from_value(10).is_none());
    }

    #[test]
    fn test_get_set_material_level() {
        let original = get_material_level();

        set_material_level(MaterialLevel::Lv1);
        assert_eq!(get_material_level(), MaterialLevel::Lv1);

        set_material_level(MaterialLevel::Lv9);
        assert_eq!(get_material_level(), MaterialLevel::Lv9);

        // 元に戻す
        set_material_level(original);
    }

    // =========================================================================
    // 純粋関数のテスト（並列実行可能、グローバル変数を使用しない）
    // =========================================================================

    #[test]
    fn test_compute_pass_right_value_early() {
        const TEST_EARLY: i32 = 50;
        const TEST_LATE: i32 = 200;

        // 序盤（40手以下）は EARLY 値
        assert_eq!(compute_pass_right_value(0, TEST_EARLY, TEST_LATE), TEST_EARLY);
        assert_eq!(compute_pass_right_value(20, TEST_EARLY, TEST_LATE), TEST_EARLY);
        assert_eq!(compute_pass_right_value(40, TEST_EARLY, TEST_LATE), TEST_EARLY);
    }

    #[test]
    fn test_compute_pass_right_value_late() {
        const TEST_EARLY: i32 = 50;
        const TEST_LATE: i32 = 200;

        // 終盤（120手以上）は LATE 値
        assert_eq!(compute_pass_right_value(120, TEST_EARLY, TEST_LATE), TEST_LATE);
        assert_eq!(compute_pass_right_value(150, TEST_EARLY, TEST_LATE), TEST_LATE);
        assert_eq!(compute_pass_right_value(200, TEST_EARLY, TEST_LATE), TEST_LATE);
    }

    #[test]
    fn test_compute_pass_right_value_interpolation() {
        const TEST_EARLY: i32 = 50;
        const TEST_LATE: i32 = 200;

        // 中盤（40〜120手）は線形補間
        // 80手目は中間点: (50 + 200) / 2 = 125
        let mid_ply = (EARLY_PLY + LATE_PLY) / 2; // 80
        let expected_mid = (TEST_EARLY + TEST_LATE) / 2;
        assert_eq!(compute_pass_right_value(mid_ply, TEST_EARLY, TEST_LATE), expected_mid);

        // 補間値は EARLY と LATE の間
        let ply_60 = compute_pass_right_value(60, TEST_EARLY, TEST_LATE);
        assert!(ply_60 > TEST_EARLY);
        assert!(ply_60 < TEST_LATE);

        let ply_100 = compute_pass_right_value(100, TEST_EARLY, TEST_LATE);
        assert!(ply_100 > ply_60);
        assert!(ply_100 < TEST_LATE);
    }

    // =========================================================================
    // グローバル変数を使用するテスト（1つにまとめて競合を回避）
    // =========================================================================

    /// グローバル変数のセット/ゲットと統合テスト
    ///
    /// このテストはグローバル変数 PASS_RIGHT_VALUE_EARLY/LATE を変更するため、
    /// 他のテストとの競合を避けるために1つにまとめている。
    #[test]
    fn test_pass_right_value_global_and_evaluation() {
        use crate::types::Color;

        // 設定を保存
        let orig_early = PASS_RIGHT_VALUE_EARLY.load(std::sync::atomic::Ordering::Relaxed);
        let orig_late = PASS_RIGHT_VALUE_LATE.load(std::sync::atomic::Ordering::Relaxed);
        let orig_level = get_material_level();

        // --- Part 1: set_pass_right_value_phased のテスト ---
        set_pass_right_value_phased(50, 200);
        assert_eq!(pass_right_value_by_ply(0), 50, "Early value should be 50");
        assert_eq!(pass_right_value_by_ply(200), 200, "Late value should be 200");

        // --- Part 2: set_pass_right_value のテスト（両方を同じ値に設定）---
        set_pass_right_value(150);
        assert_eq!(pass_right_value_by_ply(0), 150, "Both should be 150");
        assert_eq!(pass_right_value_by_ply(200), 150, "Both should be 150");

        // --- Part 3: パス後の評価値テスト ---
        // 注意: evaluate_materialはパス権評価を含まない（キャッシュ互換性のため）
        // パス権評価は探索側で動的に追加される
        set_material_level(MaterialLevel::Lv9);
        set_pass_right_value_phased(200, 50); // Early=200, Late=50

        let mut pos = Position::new();
        pos.set_startpos_with_pass_rights(2, 2);

        let pass_eval1 = evaluate_pass_rights(&pos, pos.game_ply() as u16);
        assert_eq!(pass_eval1.raw(), 0, "Equal pass rights should give 0 eval");

        // After black passes
        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);

        let material_eval = evaluate_material(&pos);
        let pass_eval2 = evaluate_pass_rights(&pos, pos.game_ply() as u16);

        // 白視点: 白2 - 黒1 = +1 → +200cp
        assert_eq!(pass_eval2.raw(), 200, "White should have +200cp pass rights advantage");

        // evaluate_materialは駒価値のみを返す（初形なので0に近い）
        // パス権評価は別途探索側で追加される
        assert_eq!(
            material_eval.raw(),
            0,
            "Material eval should be 0 for starting position (pass rights added separately)"
        );

        // 合計評価値（探索で使う形式）を計算
        let combined_eval = material_eval + pass_eval2;
        // 白視点で+200cpなので、negamax（黒視点）では-200cp
        let negamax_combined = -combined_eval.raw();
        assert!(
            negamax_combined < 0,
            "Negamax combined score should be negative: got {negamax_combined}"
        );

        // 設定を復元
        set_pass_right_value_phased(orig_early, orig_late);
        set_material_level(orig_level);
    }
}
