//! Script bot — executes a deterministic per-turn action list defined in a scenario TOML.
//!
//! Each entry in the script specifies what the bot should do on a given turn number.
//! Any turn that has no script entry results in an immediate EndTurn.
//!
//! Supported script actions:
//! - `play_slots`: play the cheapest affordable card into each listed slot in order,
//!   skipping slots that are already occupied.
//!
//! The bot reads current board state from the engine each call, so it is fully
//! stateless and can be created fresh on every action (matching the Tauri game manager's
//! on-demand bot creation pattern).

use serde::{Deserialize, Serialize};

use crate::actions::Action;
use crate::bots::Bot;
use crate::core::engine::GameEngine;
use crate::core::types::Slot;
use crate::tensor::STATE_TENSOR_SIZE;

// ---------------------------------------------------------------------------
// Turn script data (shared with scenario TOML deserialization)
// ---------------------------------------------------------------------------

/// The action script for a single turn number.
///
/// When `opponent_bot = "script"` in a scenario TOML, one or more
/// `[[opponent_script]]` tables define what the bot does each turn.
///
/// Fields use snake_case aliases so the TOML can use `play_slots = [0, 2, 4]`
/// while the JSON DTO sent to the frontend uses camelCase (`playSlots`).
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TurnScript {
    /// The game turn number this script entry applies to (`state.current_turn`).
    pub turn: u32,

    /// Slots to play a card into, in order.
    /// The cheapest affordable card that targets the slot is chosen.
    /// Slots that are already occupied are skipped.
    #[serde(default, alias = "play_slots")]
    pub play_slots: Vec<u8>,
}

// ---------------------------------------------------------------------------
// ScriptBot
// ---------------------------------------------------------------------------

/// A bot that follows a deterministic per-turn script.
///
/// Designed for Test Lab scenarios that need predictable board states
/// (e.g. "opponent has a creature in slot 0 and slot 2, nothing else").
#[derive(Clone)]
pub struct ScriptBot {
    name: String,
    script: Vec<TurnScript>,
}

impl ScriptBot {
    pub fn new(script: Vec<TurnScript>) -> Self {
        Self {
            name: "ScriptBot".to_string(),
            script,
        }
    }
}

impl Bot for ScriptBot {
    fn name(&self) -> &str {
        &self.name
    }

    /// Fallback for callers that don't provide engine access.
    /// Always returns EndTurn — ScriptBot requires `select_action_with_engine`.
    fn select_action(
        &mut self,
        _state_tensor: &[f32; STATE_TENSOR_SIZE],
        _legal_mask: &[f32; Action::ACTION_SPACE_SIZE],
        _legal_actions: &[Action],
    ) -> Action {
        Action::EndTurn
    }

    /// Primary implementation — reads current turn and board state from the engine.
    fn select_action_with_engine(&mut self, engine: &GameEngine) -> Action {
        let current_turn = engine.state.current_turn as u32;
        let active_player = engine.state.active_player;
        let legal_actions = engine.get_legal_actions();

        if let Some(turn_script) = self.script.iter().find(|t| t.turn == current_turn) {
            // Try each scripted slot in order.
            for &slot_idx in &turn_script.play_slots {
                // Skip if something is already there from an earlier action this turn.
                if engine.state.get_creature(active_player, Slot(slot_idx)).is_some() {
                    continue;
                }
                // Find a legal PlayCard action that targets this slot.
                if let Some(&action) = legal_actions.iter().find(|a| {
                    matches!(a, Action::PlayCard { slot, .. } if slot.0 == slot_idx)
                }) {
                    return action;
                }
            }
        }

        // Nothing scripted for this turn (or all scripted actions already done): end turn.
        Action::EndTurn
    }

    fn requires_engine(&self) -> bool {
        true
    }

    fn reset(&mut self) {}

    fn clone_box(&self) -> Box<dyn Bot> {
        Box::new(self.clone())
    }
}
