use std::ops::{Deref, DerefMut};

use bevy_app::prelude::*;
use bevy_asset::AssetServer;
use bevy_ecs::{prelude::*, schedule::ScheduleBuildSettings};

use bevy_app::AppLabel;
use bevy_ecs::schedule::ScheduleLabel;
use bevy_utils::default;

use crate::{
    extract_plugin::{ExtractPlugin, ExtractSchedule},
    sync_world::despawn_temporary_render_entities,
    window::{
        ExtractedWindows, Surfaces, configure_surfaces, extract_windows, need_surface_configuration,
    },
};

pub mod prelude {
    pub use crate::window::{SurfaceData, Surfaces};
}

// The main plugin for the Vulkan renderer.
// This plugin is responsible for setting up the render app, and spawning the render thread.
// The render thread waits for the main app to send it render app, and then updates the render up before sending it back to the main app.
// Before the main app sends the render app, it runs the sync and extract schedules to update the entity mappings as well as extract any required entities and components from the main world to the render world.
// Then, the main world sends the render app to the render thread and starts to execute its schedules until the next frame, at which point it will wait for the render app to be sent back before running the sync and extract schedules again.
// The render thread will apply the extract commands in order to allow the main thread to get back to work sooner.

/// The main render schedule.
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct Render;

impl Render {
    /// Sets up the base structure of the rendering [`Schedule`].
    ///
    /// The sets defined in this enum are configured to run in order.
    pub fn base_schedule() -> Schedule {
        use RenderSystems::*;

        let mut schedule = Schedule::new(Self);

        schedule.configure_sets((ExtractCommands, Render, Cleanup, PostCleanup).chain());

        schedule.configure_sets((ExtractCommands, Prepare).chain());

        schedule
    }
}

/// The startup schedule of the [`RenderApp`]
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct RenderStartup;

#[derive(Debug, Default)]
pub struct RenderPlugin;

/// A label for the rendering sub-app.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
pub struct RenderApp;

/// The systems sets of the default [`App`] rendering schedule.
///
/// These can be useful for ordering, but you almost never want to add your systems to these sets.
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum RenderSystems {
    /// This is used for applying the commands from the [`ExtractSchedule`]
    ExtractCommands,
    /// Prepare render resources from extracted data for the GPU based on their sorted order.
    /// Create [`BindGroups`](render_resource::BindGroup) that depend on those data.
    Prepare,
    /// Actual rendering happens here.
    /// In most cases, only the render backend should insert resources here.
    Render,
    /// Cleanup render resources here.
    Cleanup,
    /// Final cleanup occurs: all entities will be despawned.
    ///
    /// Runs after [`Cleanup`](RenderSystems::Cleanup).
    PostCleanup,
}

mod extract_plugin;

fn mock_render_system() {
    eprintln!("This is a mock render system. It doesn't do anything.");
}

impl Plugin for RenderPlugin {
    fn build(&self, app: &mut bevy_app::App) {
        app.add_plugins(ExtractPlugin::default());

        let asset_server = app.world().resource::<AssetServer>().clone();
        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
            render_app.insert_resource(asset_server);
            render_app.init_schedule(RenderStartup);
            render_app.update_schedule = Some(Render.intern());
            render_app.add_systems(Render, mock_render_system.in_set(RenderSystems::Render));
        }
    }
}

fn init_vulkan(app: &mut App) -> Option<()> {
    let device = renderer::device::Device::new_from_default_desc(None, &[]).ok()?;

    let device = RenderDevice { device };
    app.insert_resource(device.clone());

    let render_app = app.sub_app_mut(RenderApp);
    render_app.insert_resource(device.clone());

    Some(())
}

/// A "scratch" world used to avoid allocating new worlds every frame when
/// swapping out the [`MainWorld`] for [`ExtractSchedule`].
#[derive(Resource, Default)]
struct ScratchMainWorld(World);

fn render_start() {
    #[cfg(feature = "trace")]
    tracing::event!(tracing::Level::DEBUG, "render_start");
}

fn render_startup() {
    #[cfg(feature = "trace")]
    tracing::event!(tracing::Level::DEBUG, "render_startup");
}

mod extract_param;
mod rendering;
mod sync_world;
mod window;

pub use rendering::RenderingPlugin;
pub use sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet, SyncWorldPlugin};

#[derive(Resource, Clone)]
pub struct RenderDevice {
    device: renderer::device::Device,
}

impl Deref for RenderDevice {
    type Target = renderer::device::Device;

    fn deref(&self) -> &Self::Target {
        &self.device
    }
}
