//! Spawn helpers, bookmarks, hierarchy tree, and asset labeling.

use bevy::prelude::*;
use bevy_inspector_egui::bevy_inspector::hierarchy::SelectedEntities;
use shared::{
    AuthoringLightKind, LevelObject, LightDesc, MaterialDesc, PlayerSpawn, PrimitiveShape,
    ProjectSun,
};
use sim::Player;

use crate::assets::{snapshot_for_asset, EditorAsset, EditorAssetKind};
use crate::camera::EditorCamera;
use crate::gizmos::{EditorGizmoMode, EditorGizmoSpace};
use crate::history::{set_material_group_with_history, spawn_with_history, EditorEntitySnapshot};
use crate::play::default_player_spawn;
use crate::scene_io::SceneIo;
use crate::viewport::{recall_camera_bookmark, save_camera_bookmark, CameraBookmarks};

pub use super::play_controls::{toggle_play_mode, toggle_play_paused, toggle_possession};
pub use super::selection_ops::{
    delete_selection, duplicate_selection, entity_name, focus_editor_camera_on_selection,
    is_level_object, selected_level_entities,
};

pub fn set_gizmo_mode(world: &mut World, mode: EditorGizmoMode) {
    *world.resource_mut::<EditorGizmoMode>() = mode;
}

pub fn set_gizmo_space(world: &mut World, space: EditorGizmoSpace) {
    *world.resource_mut::<EditorGizmoSpace>() = space;
}

pub fn viewport_save_bookmark(world: &mut World) {
    let scene_key = world
        .resource::<SceneIo>()
        .active_path
        .as_ref()
        .map(|path| path.display().to_string())
        .unwrap_or_else(|| "__unsaved__".into());
    let Some(transform) = world
        .query_filtered::<&Transform, With<EditorCamera>>()
        .single(world)
        .ok()
        .copied()
    else {
        return;
    };
    let mut bookmarks = world.resource_mut::<CameraBookmarks>();
    save_camera_bookmark(&mut bookmarks, &scene_key, transform);
}

pub fn viewport_recall_bookmark(world: &mut World) {
    let scene_key = world
        .resource::<SceneIo>()
        .active_path
        .as_ref()
        .map(|path| path.display().to_string())
        .unwrap_or_else(|| "__unsaved__".into());
    let saved = {
        let bookmarks = world.resource::<CameraBookmarks>();
        match recall_camera_bookmark(bookmarks, &scene_key) {
            Some(transform) => transform,
            None => return,
        }
    };
    for mut transform in world
        .query_filtered::<&mut Transform, With<EditorCamera>>()
        .iter_mut(world)
    {
        *transform = saved;
    }
}

pub fn primitive_snapshot(name: &str, shape: PrimitiveShape, origin: Vec3) -> EditorEntitySnapshot {
    let asset = EditorAsset {
        label: name.to_string(),
        path: None,
        folder_path: crate::assets::BUILTINS_FOLDER.to_string(),
        kind: EditorAssetKind::Primitive(shape),
    };
    snapshot_for_asset(&asset, origin).expect("primitive assets are spawnable")
}

pub fn light_snapshot(name: &str, translation: Vec3) -> EditorEntitySnapshot {
    EditorEntitySnapshot {
        actor_id: None,
        actor_kind: shared::ActorKind::Light,
        actor_name: None,
        name: Some(name.to_string()),
        transform: Transform::from_translation(translation),
        primitive: None,
        brush: None,
        static_mesh_renderer: None,
        skinned_mesh_renderer: None,
        material: None,
        material_override: None,
        rigid_body: None,
        collider: None,
        physics: None,
        light: Some(LightDesc::default()),
        animation_controller: None,
        audio_source: None,
        audio_listener: None,
        player_spawn: false,
        model: None,
        prefab: None,
        prefab_instance: None,
        weapon_spawn: None,
        trigger_volume: None,
        post_process_volume: None,
        team_spawn: None,
        objective: None,

        navigation_bounds: None,

        navigation_obstacle: None,

        navigation_area: None,

        navigation_link: None,
        hierarchy_sibling_index: 0,
        editor_visibility: shared::EditorVisibility::default(),
        inspector_order: None,
        component_states: None,
        children: Vec::new(),
    }
}

pub fn ensure_player_spawn_for_edit(world: &mut World) -> Entity {
    if let Some(entity) = world
        .query_filtered::<Entity, With<PlayerSpawn>>()
        .iter(world)
        .next()
    {
        return entity;
    }

    let transform = world
        .query_filtered::<&Transform, With<Player>>()
        .iter(world)
        .next()
        .copied()
        .unwrap_or_else(default_player_spawn);

    spawn_with_history(
        world,
        EditorEntitySnapshot {
            actor_id: None,
            actor_kind: shared::ActorKind::PlayerSpawn,
            actor_name: None,
            name: Some("Player Start".to_string()),
            transform,
            primitive: None,
            brush: None,
            static_mesh_renderer: None,
            skinned_mesh_renderer: None,
            material: None,
            material_override: None,
            rigid_body: None,
            collider: None,
            physics: None,
            light: None,
            animation_controller: None,
            audio_source: None,
            audio_listener: None,
            player_spawn: true,
            model: None,
            prefab: None,
            prefab_instance: None,
            weapon_spawn: None,
            trigger_volume: None,
            post_process_volume: None,
            team_spawn: None,
            objective: None,

            navigation_bounds: None,

            navigation_obstacle: None,

            navigation_area: None,

            navigation_link: None,
            hierarchy_sibling_index: 0,
            editor_visibility: shared::EditorVisibility::default(),
            inspector_order: None,
            component_states: None,
            children: Vec::new(),
        },
    )
}

/// Removes all authored [`LightDesc`] so project sun/ambient drive outdoor lighting.
pub fn reset_scene_lighting_to_project_defaults(world: &mut World) {
    let mut query = world.query_filtered::<Entity, With<LevelObject>>();
    let with_lights: Vec<Entity> = query
        .iter(world)
        .filter(|entity| world.get::<LightDesc>(*entity).is_some())
        .collect();
    for entity in with_lights {
        if let Ok(mut entity_mut) = world.get_entity_mut(entity) {
            entity_mut.remove::<LightDesc>();
        }
    }
    world
        .resource_mut::<crate::scene_io::SceneIo>()
        .mark_dirty();
}

/// Removes authored directional lights so [`ProjectSun`] drives outdoor lighting.
pub fn use_project_sun(world: &mut World) {
    let mut query = world.query_filtered::<(Entity, &LightDesc), With<LevelObject>>();
    let directionals: Vec<Entity> = query
        .iter(world)
        .filter(|(_, light)| matches!(light.kind, AuthoringLightKind::Directional))
        .map(|(e, _)| e)
        .collect();
    for entity in directionals {
        if let Ok(mut entity_mut) = world.get_entity_mut(entity) {
            entity_mut.remove::<LightDesc>();
        }
    }
    world
        .resource_mut::<crate::scene_io::SceneIo>()
        .mark_dirty();
}

pub fn create_scene_sun_override_from_project_settings(world: &mut World) -> Entity {
    let (transform, light) = {
        let sun_illuminance = world
            .resource::<settings::ProjectSettings>()
            .rendering
            .sun_illuminance;
        let transform = world
            .query_filtered::<&Transform, With<ProjectSun>>()
            .iter(world)
            .next()
            .copied()
            .unwrap_or_else(|| {
                Transform::from_xyz(0.0, 0.0, 0.0)
                    .looking_to(Vec3::new(-0.35, -0.85, -0.4), Vec3::Y)
            });
        let mut light = LightDesc::for_kind(AuthoringLightKind::Directional);
        light.intensity = sun_illuminance;
        (transform, light)
    };

    spawn_with_history(
        world,
        EditorEntitySnapshot {
            actor_id: None,
            actor_kind: shared::ActorKind::Light,
            actor_name: None,
            name: Some("Scene Sun".to_string()),
            transform,
            primitive: None,
            brush: None,
            static_mesh_renderer: None,
            skinned_mesh_renderer: None,
            material: None,
            material_override: None,
            rigid_body: None,
            collider: None,
            physics: None,
            light: Some(light),
            animation_controller: None,
            audio_source: None,
            audio_listener: None,
            player_spawn: false,
            model: None,
            prefab: None,
            prefab_instance: None,
            weapon_spawn: None,
            trigger_volume: None,
            post_process_volume: None,
            team_spawn: None,
            objective: None,

            navigation_bounds: None,

            navigation_obstacle: None,

            navigation_area: None,

            navigation_link: None,
            hierarchy_sibling_index: 0,
            editor_visibility: shared::EditorVisibility::default(),
            inspector_order: None,
            component_states: None,
            children: Vec::new(),
        },
    )
}

pub fn asset_label(asset: &EditorAsset) -> String {
    let prefix = match asset.kind {
        EditorAssetKind::Primitive(_) => "Primitive",
        EditorAssetKind::Light(_) => "Light",
        EditorAssetKind::Model => "Model",
        EditorAssetKind::Texture => "Texture",
        EditorAssetKind::Material => "Material",
        EditorAssetKind::AudioClip => "Audio Clip",
        EditorAssetKind::Level => "Level",
        EditorAssetKind::Prefab => "Prefab",
        EditorAssetKind::PostProcessVolume => "Post Process Volume",
        EditorAssetKind::PostProcessEffect => "Post FX",
        EditorAssetKind::RenderingProfile => "Rendering Profile",
        EditorAssetKind::ShaderSchema => "Shader Schema",
    };
    format!("{prefix}: {}", asset.label)
}

pub fn apply_texture_to_selection(
    world: &mut World,
    asset: &EditorAsset,
    selected: &SelectedEntities,
) -> Result<(), String> {
    let path = asset
        .path
        .clone()
        .ok_or_else(|| format!("Texture has no source path: {}", asset.label))?;
    let changes = selected_level_entities(world, selected)
        .into_iter()
        .map(|entity| {
            let mut material = world
                .get::<MaterialDesc>(entity)
                .cloned()
                .unwrap_or_default();
            material.base_color_texture = Some(path.clone());
            (entity, material)
        })
        .collect::<Vec<_>>();
    set_material_group_with_history(world, changes);
    Ok(())
}

pub fn apply_material_asset_to_selection(
    world: &mut World,
    asset: &EditorAsset,
    selected: &SelectedEntities,
) -> Result<(), String> {
    crate::assets::materials::apply_material_asset_to_selection(world, asset, selected)
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HierarchyNodeKind {
    Authored,
    Runtime,
    Generated,
}
