use super::{book_key_from_position, BookError, BookKey, BookMove, MemoryBook, StaticBook};
use crate::board::Position;
use crate::records::record::{GameRecord, NodeId};
use crate::types::Eval;
use crate::types::Move;

/// Bookの構築を支援するビルダー。
#[derive(Debug, Default)]
pub struct BookBuilder {
    book: MemoryBook,
}

impl BookBuilder {
    /// 空の `BookBuilder` を生成する。
    #[must_use]
    pub fn new() -> Self {
        Self { book: MemoryBook::new() }
    }

    /// 局面キーを指定して 1 手を追加する。
    ///
    /// 同一キーに複数の手を追加でき、[`build_static`](Self::build_static) 時にスコア降順でソートされる。
    pub fn insert(&mut self, key: BookKey, mv: Move, score: i16, depth: u16) {
        self.book.insert_move(key, BookMove::new(mv, score, depth));
    }

    /// [`Position`] から Zobrist キーを計算して 1 手を追加する。
    pub fn insert_for_position(&mut self, position: &Position, mv: Move, score: i16, depth: u16) {
        let key = book_key_from_position(position);
        self.insert(key, mv, score, depth);
    }

    /// [`GameRecord`] の全手順（変化手順を含む）を定跡として取り込む。
    ///
    /// 各手の評価値・探索深さは [`MoveRecord`](crate::records::record::MoveRecord) から取得し、
    /// 存在しない場合は `default_score` / `default_depth` を使用する。
    pub fn extend_from_game_record(
        &mut self,
        record: &GameRecord,
        default_score: i16,
        default_depth: u16,
    ) -> Result<(), BookError> {
        let mut position = Position::empty();
        position
            .set_sfen(record.init_position_sfen())
            .map_err(|err| BookError::InvalidData(format!("invalid init sfen: {err:?}")))?;

        let root = record.root_id();
        self.extend_from_node(record, root, &mut position, default_score, default_depth)
    }

    /// 内部の [`MemoryBook`] を消費して返す。
    #[must_use]
    pub fn into_memory(self) -> MemoryBook {
        self.book
    }

    /// [`StaticBook`] へ変換する。
    ///
    /// 各局面の候補手はスコア降順 → 探索深さ降順 → `Move` 昇順でソートされ、
    /// 決定的な順序で固定される。
    #[must_use]
    pub fn build_static(mut self) -> StaticBook {
        for moves in self.book.entries_mut().values_mut() {
            sort_moves(moves);
        }
        StaticBook::from_memory_book(&self.book)
    }

    fn extend_from_node(
        &mut self,
        record: &GameRecord,
        node_id: NodeId,
        position: &mut Position,
        default_score: i16,
        default_depth: u16,
    ) -> Result<(), BookError> {
        for child_id in record.children(node_id) {
            let node = record.node(*child_id);
            let Some(mv_record) = node.mv() else {
                // 終局特殊手ノードはbookの指し手系列に含めない。
                continue;
            };

            let mv = mv_record.mv();
            let mv32 = position.move32_from_move(mv);
            let score = mv_record.eval().map(Eval::raw).unwrap_or(default_score);
            let depth = mv_record.depth().unwrap_or(default_depth);

            self.insert(book_key_from_position(position), mv, score, depth);

            position.apply_move32(mv32);
            let result =
                self.extend_from_node(record, *child_id, position, default_score, default_depth);
            position
                .undo_move32(mv32)
                .map_err(|err| BookError::InvalidData(format!("undo failed: {err:?}")))?;
            result?;
        }
        Ok(())
    }
}

fn sort_moves(moves: &mut [BookMove]) {
    moves.sort_by(|lhs, rhs| {
        rhs.score()
            .cmp(&lhs.score())
            .then_with(|| rhs.depth().cmp(&lhs.depth()))
            .then_with(|| lhs.mv().raw().cmp(&rhs.mv().raw()))
    });
}
