//! Runtime game crate: FPS player, rendering, and starter world.
#![allow(clippy::type_complexity)]
#![cfg_attr(
    feature = "hot-reload",
    allow(
        clippy::too_many_arguments,
        reason = "hot-lib-reloader wrappers preserve Bevy system signatures and macro spans"
    )
)]

pub mod animation;
pub mod audio;
pub mod editor_ext;
pub mod launch;
pub mod navigation;
mod scene_bootstrap;
mod schema_world_loader;

pub use scene_bootstrap::ProjectDefaultLevelRoot;

pub mod player {
    //! Player types re-exported from the stable sim crate.
    pub use sim::{CameraSensitivity, PlayerCamera};
}

pub mod rendering {
    //! Rendering helpers re-exported from the hot-reloadable implementation.
    pub use game_hot::{
        apply_camera_render_profile, apply_project_camera_fx, camera_auto_exposure_active,
        clear_viewport_camera_stack, effective_gi_path_for_camera, effective_viewport_gi_path,
        effective_viewport_render_stack, has_local_shadow_lights, hdr_enabled_profile,
        patch_camera_effects, resolve_viewport_camera_owner, strip_project_camera_fx,
        sync_optional_rendering_fx, sync_project_atmosphere, sync_viewport_camera_stack,
        world_has_local_shadow_lights, FullscreenEffectsPlugin, ProjectAtmosphere,
        SolariRaytracingSceneStats, SolariRenderingPlugin, ViewportCameraOwner, ViewportFxSnapshot,
        ViewportStackApply, HDR_ENV_VAR,
    };
}

use bevy::prelude::*;
use settings::ProjectSettingsPlugin;
use sim::SimPlugin;

pub use sim::{GameInputEnabled, GameInputFocused, SimEnabled};

/// When set, the in-process editor owns [`apply_project_camera_fx`] on a single camera.
#[derive(Resource, Debug, Clone, Copy, Default)]
pub struct GameRenderBootstrap {
    pub defer_player_camera_fx: bool,
}

/// Scene-startup controls for a host that embeds [`GamePlugin`].
#[derive(Resource, Debug, Clone, Copy, Default)]
pub struct GameSceneBootstrap {
    /// The host owns initial scene materialization instead of the standalone runtime.
    pub defer_default_level: bool,
}

#[cfg(not(feature = "hot-reload"))]
mod systems {
    pub use game_hot::{
        apply_ambient_light, apply_player_intent, grab_cursor, move_and_slide,
        setup_project_camera_effects, spawn_level, spawn_player, spawn_sun,
        sync_player_physics_position, sync_project_sun_visibility,
        sync_scene_directional_shadow_cascades, tag_player_camera, toggle_cursor_grab,
        update_camera_pitch, update_eye_height, update_local_player_intent,
    };
}

#[cfg(feature = "hot-reload")]
mod systems {
    pub use crate::hot::{
        apply_ambient_light, apply_player_intent, grab_cursor, move_and_slide,
        setup_project_camera_effects, spawn_level, spawn_player, spawn_sun,
        sync_player_physics_position, sync_project_sun_visibility,
        sync_scene_directional_shadow_cascades, tag_player_camera, toggle_cursor_grab,
        update_camera_pitch, update_eye_height, update_local_player_intent,
    };
}

#[cfg(feature = "hot-reload")]
#[hot_lib_reloader::hot_module(dylib = "game_hot", lib_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/debug"))]
mod hot {
    use avian3d::prelude::*;
    use bevy::input::mouse::AccumulatedMouseMotion;
    use bevy::light::atmosphere::ScatteringMedium;
    use bevy::light::CascadeShadowConfig;
    use bevy::prelude::*;
    use bevy::time::Fixed;
    use bevy::window::{CursorOptions, PrimaryWindow};
    use game_hot::{ProjectAtmosphere, SolariRaytracingSceneStats};
    use protocol::PlayerInputIntent;
    use settings::{
        ActiveCameraRenderProfile, ProjectRenderCamera, ProjectSettings, RenderingCapabilities,
        SimTuning,
    };
    use shared::{AuthoringComponentStates, InspectorOrder, LevelObject, LightDesc, ProjectSun};
    use sim::{
        CameraSensitivity, Crouching, GameInputEnabled, GameInputFocused, Grounded, JumpState,
        Player, PlayerCamera, PlayerVelocity, PlayerYawSensitivity,
    };

    hot_functions_from_file!("crates/game_hot/src/sim_systems.rs"); // move_and_slide, sync_player_physics_position, apply_player_intent
    hot_functions_from_file!("crates/game_hot/src/player/spawn.rs");
    hot_functions_from_file!("crates/game_hot/src/player/input.rs");
    hot_functions_from_file!("crates/game_hot/src/player/camera.rs");
    hot_functions_from_file!("crates/game_hot/src/rendering/camera_fx.rs");
    hot_functions_from_file!("crates/game_hot/src/world/mod.rs");
    hot_functions_from_file!("crates/game_hot/src/world/level.rs");
    hot_functions_from_file!("crates/game_hot/src/world/lighting.rs");

    #[lib_change_subscription]
    pub fn subscribe() -> hot_lib_reloader::LibReloadObserver {}
}

#[cfg(feature = "hot-reload")]
pub fn hot_reload_subscribe() -> hot_lib_reloader::LibReloadObserver {
    hot::subscribe()
}

fn sim_enabled(enabled: Res<SimEnabled>) -> bool {
    enabled.0
}

fn game_input_enabled(enabled: Res<GameInputEnabled>) -> bool {
    enabled.0
}

fn register_level_bootstrap(app: &mut App, defer_default_level: bool) {
    if defer_default_level {
        // SceneIo owns editor startup and retains the generated arena as its
        // failure fallback until the authored document is materialized.
        app.add_systems(Startup, systems::spawn_level);
    } else {
        app.add_systems(Startup, scene_bootstrap::spawn_project_default_level);
    }
}

fn register_game_systems(
    app: &mut App,
    render_bootstrap: GameRenderBootstrap,
    scene_bootstrap: GameSceneBootstrap,
) {
    app.add_systems(
        FixedUpdate,
        (
            systems::apply_player_intent,
            systems::move_and_slide,
            systems::sync_player_physics_position,
        )
            .chain()
            .run_if(sim_enabled),
    )
    .add_systems(Startup, systems::spawn_player)
    .add_systems(
        Startup,
        systems::grab_cursor
            .after(systems::spawn_player)
            .run_if(game_input_enabled),
    )
    .add_systems(
        Update,
        (
            systems::toggle_cursor_grab,
            systems::update_local_player_intent,
            systems::update_camera_pitch,
            systems::update_eye_height,
        )
            .chain()
            .run_if(game_input_enabled),
    )
    .add_systems(
        Startup,
        (systems::apply_ambient_light, systems::spawn_sun).chain(),
    );

    register_level_bootstrap(app, scene_bootstrap.defer_default_level);
    app.add_systems(
        Update,
        (
            systems::sync_project_sun_visibility,
            systems::sync_scene_directional_shadow_cascades,
        ),
    );

    if !render_bootstrap.defer_player_camera_fx {
        app.add_systems(
            PostStartup,
            (
                systems::tag_player_camera,
                systems::setup_project_camera_effects,
            )
                .chain(),
        );
    }
}

/// Runtime game systems and content, without Bevy's platform/window plugins.
pub struct GamePlugin;

impl Plugin for GamePlugin {
    fn build(&self, app: &mut App) {
        let render_bootstrap = app
            .world()
            .get_resource::<GameRenderBootstrap>()
            .copied()
            .unwrap_or_default();
        let scene_bootstrap = app
            .world()
            .get_resource::<GameSceneBootstrap>()
            .copied()
            .unwrap_or_default();

        app.register_type::<GameInputEnabled>()
            .init_resource::<GameInputEnabled>()
            .init_resource::<GameInputFocused>()
            .add_plugins(schema_world_loader::SchemaWorldAssetPlugin)
            .add_plugins((ProjectSettingsPlugin, SimPlugin, bevy_ufbx::FbxPlugin))
            .add_plugins((audio::GameAudioPlugin, animation::GameAnimationPlugin))
            .add_plugins(game_hot::SolariRenderingPlugin)
            .add_plugins(game_hot::AutoExposureRenderingPlugin)
            .add_plugins(game_hot::FullscreenEffectsPlugin)
            .insert_resource(ClearColor(Color::srgb(0.0, 0.0, 0.0)));

        register_game_systems(app, render_bootstrap, scene_bootstrap);
    }
}

/// Called by the editor after swapping the hot dylib.
pub fn on_hot_reload(world: &mut World) {
    game_hot::on_hot_reload(world);
}

#[cfg(test)]
mod startup_tests {
    use super::*;
    use bevy::asset::AssetPlugin;
    use bevy::world_serialization::WorldSerializationPlugin;

    #[test]
    fn deferred_bootstrap_leaves_default_level_loading_to_the_host() {
        let mut app = App::new();
        app.add_plugins((
            MinimalPlugins,
            AssetPlugin::default(),
            WorldSerializationPlugin,
        ));
        app.insert_resource(settings::ProjectSettings::default());
        register_level_bootstrap(&mut app, true);

        app.update();

        let world = app.world_mut();
        let mut runtime_roots =
            world.query_filtered::<Entity, With<scene_bootstrap::ProjectDefaultLevelRoot>>();
        assert_eq!(runtime_roots.iter(world).count(), 0);
        let mut fallback = world.query_filtered::<Entity, With<shared::LevelObject>>();
        assert!(
            fallback.iter(world).next().is_some(),
            "the editor fallback arena remains available until SceneIo loads"
        );
    }
}
