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::Move16;

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

impl BookBuilder {
    #[must_use]
    pub fn new() -> Self {
        Self { book: MemoryBook::new() }
    }

    /// 1手を追加する。
    pub fn insert(&mut self, key: BookKey, move16: Move16, score: i16, depth: u16) {
        self.book.insert_move(key, BookMove::new(move16, score, depth));
    }

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

    /// GameRecordから手順を取り込む（変化手順も含む）。
    pub fn extend_from_game_record(
        &mut self,
        record: &GameRecord,
        default_score: i16,
        default_depth: u16,
    ) -> Result<(), BookError> {
        let mut position = Position::new();
        position
            .set(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へ変換する（手順は決定的な順序で固定）。
    #[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 mv_record = node
                .mv()
                .ok_or_else(|| BookError::InvalidData("missing move record".to_string()))?;

            let mv = mv_record.mv();
            let move16 = mv.to_move16();
            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), move16, score, depth);

            position.do_move(mv);
            let result =
                self.extend_from_node(record, *child_id, position, default_score, default_depth);
            position
                .undo_move(mv)
                .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.move16().0.cmp(&rhs.move16().0))
    });
}
