//! Prefab scene roots from [`PrefabRef`] / [`PrefabInstance`] authoring data.

use bevy::prelude::DynamicWorldRoot;
use bevy::prelude::*;
use bevy::world_serialization::{WorldInstanceReady, WorldInstanceSpawner};

use crate::{LevelObject, PrefabInstance, PrefabRef};

use crate::asset_server_path;

/// Runtime ownership for entities spawned from a linked prefab source.
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq)]
pub struct HydratedPrefabMember {
    pub instance_root: Entity,
}

/// Runtime marker that the linked prefab instance finished spawning.
#[derive(Component, Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct HydratedPrefabReady;

/// Runtime-only guard used by editor recovery when a prefab source is invalid.
#[derive(Component, Debug, Clone, PartialEq, Eq)]
pub struct PrefabHydrationBlocked {
    pub reason: String,
}

#[allow(clippy::type_complexity)]
pub fn hydrate_prefabs(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    prefabs: Query<
        (Entity, &PrefabRef),
        (
            With<LevelObject>,
            Without<PrefabInstance>,
            Or<(Added<PrefabRef>, Changed<PrefabRef>)>,
        ),
    >,
    prefab_instances: Query<
        (Entity, &PrefabInstance),
        (
            With<LevelObject>,
            Without<PrefabHydrationBlocked>,
            Or<(Added<PrefabInstance>, Changed<PrefabInstance>)>,
        ),
    >,
) {
    for (entity, prefab) in &prefabs {
        commands.entity(entity).insert(PrefabInstance::new(
            format!("legacy:{}", prefab.path),
            prefab.path.clone(),
        ));
    }

    for (entity, instance) in &prefab_instances {
        let path = asset_server_path(&instance.source_path);
        commands
            .entity(entity)
            .remove::<HydratedPrefabReady>()
            .insert((
                PrefabRef::new(instance.source_path.clone()),
                DynamicWorldRoot(asset_server.load(path)),
            ));
    }
}

pub fn tag_hydrated_prefab_members(
    ready: On<WorldInstanceReady>,
    prefab_roots: Query<(), With<PrefabInstance>>,
    instance_spawner: Res<WorldInstanceSpawner>,
    mut commands: Commands,
) {
    if prefab_roots.get(ready.entity).is_err() {
        return;
    }
    for member in instance_spawner.iter_instance_entities(ready.instance_id) {
        commands.entity(member).insert(HydratedPrefabMember {
            instance_root: ready.entity,
        });
    }
    commands.entity(ready.entity).insert(HydratedPrefabReady);
}

#[cfg(test)]
mod tests {
    use super::*;
    use bevy::asset::AssetPlugin;
    use bevy::world_serialization::{DynamicWorld, WorldSerializationPlugin};

    #[test]
    fn spawned_prefab_entities_receive_runtime_ownership() {
        let mut app = App::new();
        app.add_plugins((AssetPlugin::default(), WorldSerializationPlugin))
            .register_type::<crate::LevelObject>()
            .register_type::<crate::ActorId>()
            .add_observer(tag_hydrated_prefab_members);

        let mut source = World::new();
        source.spawn((crate::LevelObject, crate::ActorId::new("child")));
        let dynamic_world = DynamicWorld::from_world_with(
            &source,
            &app.world().resource::<AppTypeRegistry>().read(),
        );
        let handle = app
            .world_mut()
            .resource_mut::<Assets<DynamicWorld>>()
            .add(dynamic_world);
        let root = app
            .world_mut()
            .spawn((
                PrefabInstance::new("prefab", "assets/prefabs/test.scn.ron"),
                DynamicWorldRoot(handle),
            ))
            .id();

        app.update();

        let world = app.world_mut();
        let members: Vec<_> = world
            .query::<(Entity, &HydratedPrefabMember)>()
            .iter(world)
            .map(|(entity, owner)| (entity, owner.instance_root))
            .collect();
        assert_eq!(members.len(), 1);
        assert_eq!(members[0].1, root);
        assert!(world.get::<HydratedPrefabReady>(root).is_some());
    }
}
