//! Regime classifier types: feature-index config and three-class enum.
//!
//! These types are used by the validation layer and offline analysis to classify
//! market states from feature vectors. The three-head `RegimeConditionalDQN`
//! routing logic was deleted in Phase 4; regime conditioning now lives exclusively
//! in the MoE gate inside `DQN`.

use std::sync::Arc;
use cudarc::driver::CudaStream;
use ml_core::cuda_autograd::GpuTensor;
use ml_core::MLError;
use serde::{Deserialize, Serialize};
use tracing::debug;

/// Dynamic regime classification parameters.
///
/// Feature indices and thresholds for CPU-side regime classification used by the
/// validation layer. The `DQN` MoE gate learns its own decomposition from the full
/// state vector — these fields are no longer consumed by the training path.
///
/// ## Default Feature Layout
///
/// OHLCV(0-4) + Technical(5-9) + PricePatterns(10-15) + Volume(16-21)
/// + Time(22-26) + Statistical(27-39) + Regime(40-41)
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct RegimeClassConfig {
    /// Feature index for ADX(14) in the market feature vector.
    pub adx_idx: usize,
    /// Feature index for CUSUM direction in the market feature vector.
    pub cusum_idx: usize,
    /// ADX threshold for trending classification (normalized 0-1).
    pub adx_threshold: f32,
    /// CUSUM direction threshold for volatile classification.
    pub cusum_threshold: f32,
}

impl Default for RegimeClassConfig {
    fn default() -> Self {
        Self {
            adx_idx: 40,
            cusum_idx: 41,
            adx_threshold: 0.25,
            cusum_threshold: 0.7,
        }
    }
}

/// Market regime types based on structural characteristics.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RegimeType {
    /// Strong directional trend (ADX > 25)
    Trending,
    /// Mean-reverting market (ADX <= 25, Entropy <= 0.7)
    Ranging,
    /// High volatility/uncertainty (ADX <= 25, Entropy > 0.7)
    Volatile,
}

impl RegimeType {
    /// Classify regime from market features (CPU path).
    ///
    /// Uses ADX and CUSUM direction from the state vector at positions specified
    /// by `cfg`. Returns `Ranging` when the feature vector is shorter than required.
    pub fn classify_from_features(features: &[f32], cfg: &RegimeClassConfig) -> Self {
        if features.len() <= cfg.cusum_idx {
            debug!(
                "Feature vector too small ({} elements), returning Ranging regime as default",
                features.len()
            );
            return Self::Ranging;
        }

        let adx = features.get(cfg.adx_idx).copied().unwrap_or(0.0);
        let cusum_direction = features.get(cfg.cusum_idx).copied().unwrap_or(0.0);

        if adx > cfg.adx_threshold {
            Self::Trending
        } else if cusum_direction.abs() > cfg.cusum_threshold {
            Self::Volatile
        } else {
            Self::Ranging
        }
    }

    /// Classify regime for a batch of states on GPU using tensor ops.
    ///
    /// Returns 3 binary mask tensors (trending, ranging, volatile) each of shape
    /// `[batch_size]`. Zero CPU roundtrip — all operations are GPU tensor ops.
    pub fn classify_regime_masks_gpu(
        states: &GpuTensor,
        cfg: &RegimeClassConfig,
        stream: &Arc<CudaStream>,
    ) -> Result<(GpuTensor, GpuTensor, GpuTensor), MLError> {
        let shape = states.shape();
        let batch_size = shape.first().copied().ok_or_else(|| {
            MLError::ModelError(
                "classify_regime_masks_gpu: states tensor has no dimensions".to_owned(),
            )
        })?;
        let state_dim = shape.get(1).copied().ok_or_else(|| {
            MLError::ModelError(
                "classify_regime_masks_gpu: states tensor needs 2 dimensions".to_owned(),
            )
        })?;

        let host = states.to_host(stream)?;

        let mut trending = Vec::with_capacity(batch_size);
        let mut ranging = Vec::with_capacity(batch_size);
        let mut volatile = Vec::with_capacity(batch_size);

        for i in 0..batch_size {
            let offset = i * state_dim;
            let row = host.get(offset..offset + state_dim).ok_or_else(|| {
                MLError::ModelError(format!(
                    "classify_regime_masks_gpu: row {i} out of bounds"
                ))
            })?;
            let regime = Self::classify_from_features(row, cfg);
            match regime {
                RegimeType::Trending => {
                    trending.push(1.0_f32);
                    ranging.push(0.0);
                    volatile.push(0.0);
                }
                RegimeType::Ranging => {
                    trending.push(0.0_f32);
                    ranging.push(1.0);
                    volatile.push(0.0);
                }
                RegimeType::Volatile => {
                    trending.push(0.0_f32);
                    ranging.push(0.0);
                    volatile.push(1.0);
                }
            }
        }

        let t = GpuTensor::from_host(&trending, vec![batch_size], stream)?;
        let r = GpuTensor::from_host(&ranging, vec![batch_size], stream)?;
        let v = GpuTensor::from_host(&volatile, vec![batch_size], stream)?;
        Ok((t, r, v))
    }
}
