/// Global renderer access via thread-local storage.
///
/// This module isolates the global singleton pattern used to give systems
/// and loaders access to the wgpu `Device` and `Queue` without threading
/// them through every call site. The long-term goal is to replace these
/// with explicit `RenderContext` parameters on systems that need GPU access.
use std::cell::RefCell;

use crate::debug::DebugMode;
use crate::entity::EntityHandle;

use super::{BillboardDrawCall, DrawCall, FontAtlas, Renderer, Spotlight};
use super::text_pipeline::TextVertex;

thread_local! {
    static GLOBAL_RENDERER: RefCell<Option<Renderer>> = RefCell::new(None);
}

fn with_ref<F, R>(f: F) -> R
where
    F: FnOnce(&Renderer) -> R,
{
    GLOBAL_RENDERER.with(|r| {
        let renderer = r.borrow();
        let renderer = renderer.as_ref().expect("Renderer not initialized");
        f(renderer)
    })
}

fn with_mut<F, R>(f: F) -> R
where
    F: FnOnce(&mut Renderer) -> R,
{
    GLOBAL_RENDERER.with(|r| {
        let mut renderer = r.borrow_mut();
        let renderer = renderer.as_mut().expect("Renderer not initialized");
        f(renderer)
    })
}

pub fn init(renderer: Renderer)
{
    GLOBAL_RENDERER.with(|r| *r.borrow_mut() = Some(renderer));
}

pub fn with_device<F, R>(f: F) -> R
where
    F: FnOnce(&wgpu::Device) -> R,
{
    with_ref(|r| f(&r.device))
}

pub fn with_queue<F, R>(f: F) -> R
where
    F: FnOnce(&wgpu::Queue) -> R,
{
    with_ref(|r| f(&r.queue))
}

pub fn with_surface_format<F, R>(f: F) -> R
where
    F: FnOnce(wgpu::TextureFormat) -> R,
{
    with_ref(|r| f(r.config.format))
}

pub fn aspect_ratio() -> f32
{
    with_ref(|r| r.aspect_ratio())
}

pub fn set_terrain_data()
{
    with_mut(|r| r.set_terrain_data());
}

pub fn init_snow_light_accumulation(terrain_min: glam::Vec2, terrain_max: glam::Vec2)
{
    with_mut(|r| r.init_snow_light_accumulation(terrain_min, terrain_max));
}

pub fn set_snow_depth(snow_depth_view: &wgpu::TextureView)
{
    with_mut(|r| r.set_snow_depth(snow_depth_view));
}

#[allow(dead_code)]
pub fn set_shadow_bias(bias: f32)
{
    with_mut(|r| r.shadow_bias = bias);
}

pub fn update_spotlights(spotlights: Vec<Spotlight>)
{
    with_mut(|r| r.spotlights = spotlights);
}

pub fn set_selected_entity(entity: Option<EntityHandle>)
{
    with_mut(|r| r.selected_entity = entity);
}

pub fn with_font_atlas<F, R>(f: F) -> R
where
    F: FnOnce(&FontAtlas) -> R,
{
    with_ref(|r| f(&r.font_atlas))
}

pub fn render(
    view: &glam::Mat4,
    projection: &glam::Mat4,
    camera_position: glam::Vec3,
    player_position: glam::Vec3,
    draw_calls: &[DrawCall],
    billboard_calls: &[BillboardDrawCall],
    text_vertices: &[TextVertex],
    time: f32,
    delta_time: f32,
    debug_mode: DebugMode,
) -> wgpu::SurfaceTexture
{
    with_mut(|r| {
        r.render(
            view,
            projection,
            camera_position,
            player_position,
            draw_calls,
            billboard_calls,
            text_vertices,
            time,
            delta_time,
            debug_mode,
        )
    })
}
