use std::sync::Arc;
use tracing::info;

use crate::arch::{
    infra_core::{env_core::EnvCore, env_mediator::EnvMediator},
    strategy_base::{
        handler::handler_core::BoardCastChannel,
        hlist_core::{HCons, HNil},
    },
    task_execution::task_general::TaskInfo,
    traits::strategy::Strategy,
};

/// Builder for an `extrema_infra` runtime.
///
/// Use this builder in the final binary to declare broadcast channels, runtime
/// tasks, and strategy modules. `with_strategy_module` keeps each strategy as
/// its concrete type by accumulating modules in a heterogeneous list, so a
/// process can compose different modules without boxing them behind a trait
/// object.
///
/// ```rust,no_run
/// use std::{sync::Arc, time::Duration};
///
/// use extrema_infra::prelude::*;
///
/// let task = AltTaskInfo {
///     alt_task_type: AltTaskType::TimeScheduler(Duration::from_secs(5)),
///     chunk: 1,
///     task_base_id: Some(1),
/// };
///
/// # #[derive(Clone)]
/// # struct MyStrategy;
/// # impl Strategy for MyStrategy { async fn initialize(&mut self) {} }
/// # impl CommandEmitter for MyStrategy {
/// #     fn command_init(&mut self, _: Arc<CommandRegistry>) {}
/// #     fn command_registry(&self) -> Arc<CommandRegistry> {
/// #         Arc::new(CommandRegistry::default())
/// #     }
/// # }
/// # impl EventHandler for MyStrategy {}
/// # let my_strategy = MyStrategy;
/// let env = EnvBuilder::new()
///     .with_board_cast_channel(BoardCastChannel::default_alt_event())
///     .with_board_cast_channel(BoardCastChannel::default_scheduler())
///     .with_task(TaskInfo::AltTask(Arc::new(task)))
///     .with_strategy_module(my_strategy)
///     .build();
/// ```
pub struct EnvBuilder<Strategies = HNil> {
    board_cast_channels: Vec<BoardCastChannel>,
    tasks: Vec<TaskInfo>,
    strategies: Strategies,
}

impl EnvBuilder<HNil> {
    /// Creates an empty runtime builder.
    pub fn new() -> Self {
        Self {
            board_cast_channels: Vec::new(),
            tasks: vec![],
            strategies: HNil,
        }
    }
}

impl Default for EnvBuilder<HNil> {
    fn default() -> Self {
        Self::new()
    }
}

impl<HeadList> EnvBuilder<HeadList> {
    /// Adds a broadcast channel if a channel of the same variant is not already
    /// present.
    ///
    /// Duplicate variants are skipped. For example, adding two trade channels
    /// still leaves one `Trade` broadcast channel in the runtime.
    pub fn with_board_cast_channel(mut self, channel: BoardCastChannel) -> Self {
        let channel_type_exists = self
            .board_cast_channels
            .iter()
            .any(|ch| std::mem::discriminant(ch) == std::mem::discriminant(&channel));

        if !channel_type_exists {
            info!("Adding board cast channel: {:?}", channel);
            self.board_cast_channels.push(channel);
        } else {
            info!("Skipped duplicate channel: {:?}", channel);
        }

        self
    }

    /// Adds one runtime task.
    pub fn with_task(mut self, task: TaskInfo) -> Self {
        info!("Adding task: {:?}", task);
        self.tasks.push(task);
        self
    }

    /// Adds several runtime tasks in order.
    pub fn with_tasks(mut self, tasks: Vec<TaskInfo>) -> Self {
        for task in tasks {
            info!("Adding task: {:?}", task);
            self.tasks.push(task);
        }
        self
    }

    /// Registers one strategy module.
    ///
    /// Calling this method repeatedly creates a static module chain. All modules
    /// receive the same broadcast events and may independently send commands to
    /// the tasks they care about.
    pub fn with_strategy_module<S>(self, strategy: S) -> EnvBuilder<HCons<S, HeadList>>
    where
        S: Strategy + Clone,
    {
        info!("Adding strategy: {}", strategy.strategy_name());
        EnvBuilder {
            strategies: HCons {
                head: strategy,
                tail: self.strategies,
            },
            board_cast_channels: self.board_cast_channels,
            tasks: self.tasks,
        }
    }
}

impl<Strategies> EnvBuilder<Strategies>
where
    Strategies: Strategy,
{
    /// Finalizes the builder into an executable environment.
    pub fn build(self) -> EnvMediator<Strategies> {
        EnvMediator {
            core: EnvCore {
                channel: Arc::new(self.board_cast_channels),
                strategy: self.strategies,
            },
            tasks: self.tasks,
        }
    }
}
