//! Runtime rendering profile types (no egui).

use bevy::prelude::*;
use serde::{Deserialize, Serialize};

use crate::RenderingSettings;

/// Global illumination policy from project settings.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, Reflect)]
#[reflect(Default, Debug, PartialEq, Serialize, Deserialize)]
pub enum GiMode {
    #[default]
    Auto,
    Forward,
    Solari,
}

/// Resolved GI path for the active camera this frame.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Reflect)]
#[reflect(Default, Debug, PartialEq)]
pub enum GiPath {
    #[default]
    Forward,
    SolariDeferred,
}

/// Reason the active camera is rendering with a different path than requested.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
#[reflect(Debug, PartialEq)]
pub enum RenderFallbackReason {
    RtUnsupported,
}

/// Unified render-stack decision consumed by game, editor, and diagnostics.
#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
#[reflect(Resource, Debug, Clone, PartialEq)]
pub struct EffectiveRenderStack {
    pub requested_gi_path: GiPath,
    pub effective_gi_path: GiPath,
    pub fallback_reason: Option<RenderFallbackReason>,
    pub solari_ready: bool,
    pub local_shadow_lights: bool,
}

impl Default for EffectiveRenderStack {
    fn default() -> Self {
        Self {
            requested_gi_path: GiPath::Forward,
            effective_gi_path: GiPath::Forward,
            fallback_reason: None,
            solari_ready: false,
            local_shadow_lights: false,
        }
    }
}

/// Fixed EV100 on the camera, or Bevy histogram-based auto exposure.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, Reflect)]
#[reflect(Default, Debug, PartialEq, Serialize, Deserialize)]
pub enum ExposureMode {
    #[default]
    Manual,
    Auto,
}

/// GPU/runtime GI capabilities detected at startup and updated when path changes.
#[derive(Resource, Debug, Clone, Reflect)]
#[reflect(Resource, Debug, Clone)]
pub struct RenderingCapabilities {
    pub rt_supported: bool,
    pub auto_exposure_supported: bool,
    pub requested_gi_path: GiPath,
    pub effective_gi_path: GiPath,
    pub fallback_reason: Option<RenderFallbackReason>,
    /// Deprecated compatibility field. Prefer [`RenderingCapabilities::effective_gi_path`].
    pub active_gi_path: GiPath,
}

impl Default for RenderingCapabilities {
    fn default() -> Self {
        Self {
            rt_supported: false,
            auto_exposure_supported: false,
            requested_gi_path: GiPath::Forward,
            effective_gi_path: GiPath::Forward,
            fallback_reason: None,
            active_gi_path: GiPath::Forward,
        }
    }
}

/// Whether the resolved profile should drive [`bevy::post_process::auto_exposure::AutoExposure`].
pub fn effective_auto_exposure(
    profile: &ActiveCameraRenderProfile,
    caps: &RenderingCapabilities,
) -> bool {
    profile.exposure_mode == ExposureMode::Auto && profile.hdr && caps.auto_exposure_supported
}

/// Resolved post-process + GI stack for the active camera (read-only for UI).
#[derive(Resource, Debug, Clone, PartialEq, Reflect)]
#[reflect(Resource, Debug, Clone, PartialEq)]
pub struct ActiveCameraRenderProfile {
    pub gi_path: GiPath,
    pub hdr: bool,
    pub exposure_mode: ExposureMode,
    pub exposure_ev100: f32,
    pub auto_exposure_speed_brighten: f32,
    pub auto_exposure_speed_darken: f32,
    pub tonemapping_aces: bool,
    pub bloom: bool,
    pub taa: bool,
    pub ssao: bool,
    pub atmosphere: bool,
    pub fog_color: [f32; 3],
    pub fog_density: f32,
    pub fullscreen_effect: Option<String>,
}

impl ActiveCameraRenderProfile {
    pub fn from_project(rendering: &RenderingSettings, gi_path: GiPath) -> Self {
        Self {
            gi_path,
            hdr: rendering.hdr,
            exposure_mode: rendering.exposure_mode,
            exposure_ev100: rendering.exposure_ev100,
            auto_exposure_speed_brighten: rendering.auto_exposure_speed_brighten,
            auto_exposure_speed_darken: rendering.auto_exposure_speed_darken,
            tonemapping_aces: rendering.tonemapping_aces,
            bloom: rendering.bloom,
            taa: rendering.taa,
            ssao: rendering.ssao,
            atmosphere: rendering.atmosphere,
            fog_color: rendering.fog_color,
            fog_density: rendering.fog_density,
            fullscreen_effect: None,
        }
    }
}

impl Default for ActiveCameraRenderProfile {
    fn default() -> Self {
        Self::from_project(&RenderingSettings::default(), GiPath::Forward)
    }
}

/// One contributing post-process volume for editor HUD (max 3 stored).
#[derive(Debug, Clone, Reflect)]
#[reflect(Debug, Clone)]
pub struct VolumeContribution {
    pub entity_bits: u64,
    pub weight: f32,
    pub label: String,
    pub override_count: u32,
}

/// Top volume contributors at the active camera position (editor HUD).
#[derive(Resource, Debug, Clone, Default, Reflect)]
#[reflect(Resource, Debug, Clone, Default)]
pub struct ActiveVolumeContribution {
    pub entries: Vec<VolumeContribution>,
}

impl ActiveVolumeContribution {
    pub const MAX_ENTRIES: usize = 3;

    pub fn set_entries(&mut self, entries: Vec<VolumeContribution>) {
        self.entries = entries;
        self.entries.truncate(Self::MAX_ENTRIES);
    }
}

/// Resolves the requested GI path from project mode and capabilities.
pub fn resolve_gi_path(gi_mode: GiMode, caps: &RenderingCapabilities) -> GiPath {
    match gi_mode {
        GiMode::Forward => GiPath::Forward,
        GiMode::Solari => {
            if caps.rt_supported {
                GiPath::SolariDeferred
            } else {
                GiPath::Forward
            }
        }
        GiMode::Auto => {
            if caps.rt_supported {
                GiPath::SolariDeferred
            } else {
                GiPath::Forward
            }
        }
    }
}

/// Resolves the effective camera GI path from project/volume intent and runtime state.
pub fn resolve_effective_render_stack(
    requested_gi_path: GiPath,
    caps: &RenderingCapabilities,
    solari_ready: bool,
    local_shadow_lights: bool,
) -> EffectiveRenderStack {
    let (effective_gi_path, fallback_reason) = match requested_gi_path {
        GiPath::Forward => (GiPath::Forward, None),
        GiPath::SolariDeferred if !caps.rt_supported => {
            (GiPath::Forward, Some(RenderFallbackReason::RtUnsupported))
        }
        GiPath::SolariDeferred => (GiPath::SolariDeferred, None),
    };

    EffectiveRenderStack {
        requested_gi_path,
        effective_gi_path,
        fallback_reason,
        solari_ready,
        local_shadow_lights,
    }
}

/// Mirrors the explicit stack decision into the capabilities resource for UI/readback.
pub fn write_effective_render_stack(caps: &mut RenderingCapabilities, stack: EffectiveRenderStack) {
    caps.requested_gi_path = stack.requested_gi_path;
    caps.effective_gi_path = stack.effective_gi_path;
    caps.fallback_reason = stack.fallback_reason;
    caps.active_gi_path = stack.effective_gi_path;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn effective_stack_uses_solari_when_ready() {
        let caps = RenderingCapabilities {
            rt_supported: true,
            ..Default::default()
        };

        let stack = resolve_effective_render_stack(GiPath::SolariDeferred, &caps, true, false);

        assert_eq!(stack.effective_gi_path, GiPath::SolariDeferred);
        assert_eq!(stack.fallback_reason, None);
    }

    #[test]
    fn effective_stack_keeps_solari_when_local_shadow_lights_exist() {
        let caps = RenderingCapabilities {
            rt_supported: true,
            ..Default::default()
        };

        let stack = resolve_effective_render_stack(GiPath::SolariDeferred, &caps, true, true);

        assert_eq!(stack.effective_gi_path, GiPath::SolariDeferred);
        assert_eq!(stack.fallback_reason, None);
        assert!(stack.local_shadow_lights);
    }
}
