//! Prefab instance overrides — apply/revert per field group (v2).

use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};

use bevy::prelude::*;
use bevy::world_serialization::{
    DynamicWorld, DynamicWorldRoot, WorldInstance, WorldInstanceReady,
};
use bevy_egui::egui;
use egui_phosphor_icons::icons;
use scene::document::{SceneComponentBlob, SceneDocument, SceneEntityId, ScenePatch};
use shared::{
    decode_prefab_overrides, encode_prefab_overrides, ActorId, EditorVisibility,
    HydratedPrefabMember, HydratedPrefabReady, MaterialDesc, PrefabActorPath,
    PrefabComponentOverride, PrefabInstance, PrefabOverrides, PrefabPropertyOverride, PrefabRef,
    PrefabStructuralOverride,
};

use crate::asset_db::{ensure_asset_record, AssetRegistry};
use crate::history::{
    capture_prefab_edit_state, record_prefab_edit_state, record_prefab_source_apply,
};
use crate::ui::inspector::{
    apply_component_card_response, component_card, component_card_context, property_row,
    ComponentCardOptions,
};
use crate::ui::theme::{ERROR, SUCCESS, TEXT_MUTED, WARNING};

#[derive(Resource, Default)]
pub struct PrefabOverrideClipboard {
    pub property_path: String,
    pub value_ron: String,
}

#[derive(Resource, Default)]
pub struct PrefabHealthCache {
    entries: HashMap<Entity, (Instant, PrefabHealth)>,
}

pub fn prefab_source_revision(project_root: &Path, source_path: &str) -> Result<String, String> {
    scene::prefab_graph_revision(&project_root.join(source_path), project_root)
}

fn current_source_revision(world: &World, instance: &PrefabInstance) -> Option<String> {
    let root = world
        .get_resource::<crate::project_io::ProjectWorkspace>()
        .map(|workspace| PathBuf::from(&workspace.root))?;
    prefab_source_revision(&root, &instance.source_path).ok()
}

fn track_current_source_revision(
    world: &World,
    instance: &PrefabInstance,
    overrides: &mut PrefabOverrides,
) {
    overrides.source_revision = current_source_revision(world, instance);
}

#[derive(Debug, Clone)]
enum PrefabHealth {
    Ready,
    Loading,
    Untracked,
    ChangedBase,
    Conflict(Vec<String>),
    Broken(String),
    InvalidOverrides(String),
}

fn prefab_health(
    world: &mut World,
    entity: Entity,
    instance: &PrefabInstance,
    overrides: &PrefabOverrides,
) -> PrefabHealth {
    if let Some(health) = world
        .get_resource::<PrefabHealthCache>()
        .and_then(|cache| cache.entries.get(&entity))
        .filter(|(checked, _)| checked.elapsed() < Duration::from_millis(500))
        .map(|(_, health)| health.clone())
    {
        return health;
    }
    let health = compute_prefab_health(world, entity, instance, overrides);
    if let Some(mut cache) = world.get_resource_mut::<PrefabHealthCache>() {
        cache
            .entries
            .insert(entity, (Instant::now(), health.clone()));
    }
    health
}

fn compute_prefab_health(
    world: &World,
    entity: Entity,
    instance: &PrefabInstance,
    overrides: &PrefabOverrides,
) -> PrefabHealth {
    let Some(project_root) = world
        .get_resource::<crate::project_io::ProjectWorkspace>()
        .map(|workspace| PathBuf::from(&workspace.root))
    else {
        return PrefabHealth::Broken("Project workspace is unavailable".to_string());
    };
    if let Some(blocked) = world.get::<shared::PrefabHydrationBlocked>(entity) {
        return PrefabHealth::Broken(blocked.reason.clone());
    }
    if let Err(error) =
        scene::validate_prefab_graph(&project_root.join(&instance.source_path), &project_root)
    {
        return PrefabHealth::Broken(error);
    }
    if world.get::<HydratedPrefabReady>(entity).is_none() {
        return PrefabHealth::Loading;
    }
    let current = prefab_source_revision(&project_root, &instance.source_path).ok();
    let known_keys = prefab_legacy_target_keys(&project_root, instance);
    let mut stale: Vec<String> = overrides
        .child_visibility
        .keys()
        .filter(|key| !known_keys.contains(*key))
        .cloned()
        .collect();
    stale.extend(prefab_semantic_conflicts(
        &project_root,
        instance,
        overrides,
    ));
    stale.sort();
    stale.dedup();
    if !stale.is_empty() {
        return PrefabHealth::Conflict(stale);
    }
    match (&overrides.source_revision, current) {
        (None, _) => PrefabHealth::Untracked,
        (Some(recorded), Some(current)) if recorded != &current => PrefabHealth::ChangedBase,
        _ => PrefabHealth::Ready,
    }
}

fn prefab_legacy_target_keys(project_root: &Path, instance: &PrefabInstance) -> HashSet<String> {
    let direct = PrefabActorPath::direct("");
    source_document_for_target(project_root, instance, &direct)
        .map(|document| {
            document
                .entities
                .into_iter()
                .flat_map(|entity| [entity.actor_id, entity.name])
                .flatten()
                .collect()
        })
        .unwrap_or_default()
}

fn prefab_semantic_conflicts(
    project_root: &Path,
    instance: &PrefabInstance,
    overrides: &PrefabOverrides,
) -> Vec<String> {
    let mut conflicts = Vec::new();
    for property in &overrides.properties {
        let source = source_component_for_target(
            project_root,
            instance,
            &property.target,
            &property.component_type,
        )
        .and_then(|source| {
            apply_layer_prerequisites(
                source,
                overrides,
                &property.target,
                &property.component_type,
            )
        });
        let conflict = match source {
            Ok(Some(source)) if property.component_type == std::any::type_name::<Transform>() => {
                apply_transform_property(&source.ron, property).is_err()
            }
            Ok(Some(source))
                if property.component_type == std::any::type_name::<MaterialDesc>() =>
            {
                apply_material_property(&source.ron, property).is_err()
            }
            Ok(Some(_)) => false,
            _ => true,
        };
        if conflict {
            conflicts.push(property_conflict_key(property));
        }
    }
    for component in &overrides.components {
        let current = source_component_for_target(
            project_root,
            instance,
            &component.target,
            &component.component_type,
        )
        .ok()
        .flatten();
        let conflict = match (&component.base_component_ron, current) {
            (None, Some(_)) | (Some(_), None) => true,
            (Some(base), Some(current)) => {
                !component_values_equal(&component.component_type, &current.ron, base)
            }
            (None, None) => false,
        };
        if conflict {
            conflicts.push(component_conflict_key(component));
        }
    }
    for structural in &overrides.structural {
        let target = structural_target(structural);
        let document = source_document_for_target(project_root, instance, target);
        let source_entity = document.as_ref().ok().and_then(|document| {
            document.find_entity(&SceneEntityId::ActorId(target.actor_id.clone()))
        });
        let conflict = match structural {
            PrefabStructuralOverride::RemoveChild {
                base_subtree_revision,
                ..
            } => {
                source_entity.is_none()
                    || match prefab_source_revision(project_root, &instance.source_path) {
                        Ok(current) => current != *base_subtree_revision,
                        Err(_) => true,
                    }
            }
            PrefabStructuralOverride::ReparentChild {
                base_parent,
                new_parent,
                base_transform_ron,
                ..
            } => {
                let parent_changed = match source_parent_for_target(project_root, instance, target)
                {
                    Ok(current) => current != *base_parent,
                    Err(_) => true,
                };
                let transform_changed = base_transform_ron.as_ref().is_some_and(|base| {
                    source_component_for_target(
                        project_root,
                        instance,
                        target,
                        std::any::type_name::<Transform>(),
                    )
                    .ok()
                    .flatten()
                    .is_none_or(|current| {
                        !component_values_equal(
                            std::any::type_name::<Transform>(),
                            &current.ron,
                            base,
                        )
                    })
                });
                let destination_missing = new_parent.as_ref().is_some_and(|parent| {
                    parent.instance_chain != target.instance_chain
                        || source_parent_for_target(project_root, instance, parent).is_err()
                });
                parent_changed || transform_changed || destination_missing
            }
            PrefabStructuralOverride::AttachLocalChild { .. } => false,
        };
        if conflict {
            conflicts.push(structural_conflict_key(structural));
        }
    }
    conflicts
}

fn actor_path_key(target: &PrefabActorPath) -> String {
    if target.instance_chain.is_empty() {
        target.actor_id.clone()
    } else {
        format!("{}/{}", target.instance_chain.join("/"), target.actor_id)
    }
}

fn property_conflict_key(property: &PrefabPropertyOverride) -> String {
    format!(
        "{}.{}.{}",
        actor_path_key(&property.target),
        property.component_type,
        property.property_path
    )
}

fn component_conflict_key(component: &PrefabComponentOverride) -> String {
    format!(
        "{}.{}",
        actor_path_key(&component.target),
        component.component_type
    )
}

fn structural_conflict_key(structural: &PrefabStructuralOverride) -> String {
    format!(
        "{}.structure",
        actor_path_key(structural_target(structural))
    )
}

fn source_document_for_target(
    project_root: &Path,
    instance: &PrefabInstance,
    target: &PrefabActorPath,
) -> Result<SceneDocument, String> {
    let mut source_path = instance.source_path.clone();
    for anchor_actor_id in &target.instance_chain {
        let document = SceneDocument::from_ron_text(
            &std::fs::read_to_string(project_root.join(&source_path))
                .map_err(|error| error.to_string())?,
        )?;
        let component = document
            .find_entity(&SceneEntityId::ActorId(anchor_actor_id.clone()))
            .and_then(|entity| {
                entity.components.iter().find(|component| {
                    component.type_name == std::any::type_name::<PrefabInstance>()
                })
            })
            .ok_or_else(|| format!("nested prefab anchor `{anchor_actor_id}` is missing"))?;
        let nested: PrefabInstance =
            ron::from_str(&component.ron).map_err(|error| error.to_string())?;
        source_path = nested.source_path;
    }
    SceneDocument::from_ron_text(
        &std::fs::read_to_string(project_root.join(source_path))
            .map_err(|error| error.to_string())?,
    )
}

fn source_component_for_target(
    project_root: &Path,
    instance: &PrefabInstance,
    target: &PrefabActorPath,
    component_type: &str,
) -> Result<Option<SceneComponentBlob>, String> {
    let mut source_path = instance.source_path.clone();
    let mut remaining_target = target.clone();
    let mut layers = Vec::new();
    for anchor_actor_id in &target.instance_chain {
        let document = SceneDocument::from_ron_text(
            &std::fs::read_to_string(project_root.join(&source_path))
                .map_err(|error| error.to_string())?,
        )?;
        let nested_component = document
            .find_entity(&SceneEntityId::ActorId(anchor_actor_id.clone()))
            .and_then(|entity| {
                entity.components.iter().find(|component| {
                    component.type_name == std::any::type_name::<PrefabInstance>()
                })
            })
            .ok_or_else(|| format!("nested prefab anchor `{anchor_actor_id}` is missing"))?;
        let nested: PrefabInstance =
            ron::from_str(&nested_component.ron).map_err(|error| error.to_string())?;
        remaining_target.instance_chain.remove(0);
        layers.push((decode_prefab_overrides(&nested)?, remaining_target.clone()));
        source_path = nested.source_path;
    }
    let document = SceneDocument::from_ron_text(
        &std::fs::read_to_string(project_root.join(source_path))
            .map_err(|error| error.to_string())?,
    )?;
    let mut current = document
        .find_entity(&SceneEntityId::ActorId(target.actor_id.clone()))
        .and_then(|entity| {
            entity
                .components
                .iter()
                .find(|component| component.type_name == component_type)
        })
        .cloned();
    for (overrides, layer_target) in layers.into_iter().rev() {
        current = apply_layer_to_component(current, &overrides, &layer_target, component_type)?;
    }
    Ok(current)
}

fn source_parent_for_target(
    project_root: &Path,
    instance: &PrefabInstance,
    target: &PrefabActorPath,
) -> Result<Option<PrefabActorPath>, String> {
    let mut source_path = instance.source_path.clone();
    let mut remaining_target = target.clone();
    let mut layers = Vec::new();
    for anchor_actor_id in &target.instance_chain {
        let document = SceneDocument::from_ron_text(
            &std::fs::read_to_string(project_root.join(&source_path))
                .map_err(|error| error.to_string())?,
        )?;
        let component = document
            .find_entity(&SceneEntityId::ActorId(anchor_actor_id.clone()))
            .and_then(|entity| {
                entity.components.iter().find(|component| {
                    component.type_name == std::any::type_name::<PrefabInstance>()
                })
            })
            .ok_or_else(|| format!("nested prefab anchor `{anchor_actor_id}` is missing"))?;
        let nested: PrefabInstance =
            ron::from_str(&component.ron).map_err(|error| error.to_string())?;
        remaining_target.instance_chain.remove(0);
        layers.push((decode_prefab_overrides(&nested)?, remaining_target.clone()));
        source_path = nested.source_path;
    }
    let document = SceneDocument::from_ron_text(
        &std::fs::read_to_string(project_root.join(source_path))
            .map_err(|error| error.to_string())?,
    )?;
    let mut parent_actor_id = document
        .find_entity(&SceneEntityId::ActorId(target.actor_id.clone()))
        .ok_or_else(|| format!("source actor `{}` is missing", actor_path_key(target)))?
        .parent_actor_id
        .clone();
    for (overrides, layer_target) in layers.into_iter().rev() {
        for structural in &overrides.structural {
            match structural {
                PrefabStructuralOverride::RemoveChild { target, .. } if target == &layer_target => {
                    return Err(format!(
                        "source actor `{}` was removed by a nested layer",
                        actor_path_key(target)
                    ));
                }
                PrefabStructuralOverride::ReparentChild {
                    target,
                    base_parent,
                    new_parent,
                    ..
                } if target == &layer_target => {
                    let expected = base_parent.as_ref().map(|parent| parent.actor_id.as_str());
                    if parent_actor_id.as_deref() != expected {
                        return Err(format!(
                            "nested parent override for `{}` conflicts with its source",
                            actor_path_key(target)
                        ));
                    }
                    parent_actor_id = new_parent.as_ref().map(|parent| parent.actor_id.clone());
                }
                _ => {}
            }
        }
    }
    Ok(parent_actor_id.map(|actor_id| PrefabActorPath {
        instance_chain: target.instance_chain.clone(),
        actor_id,
    }))
}

fn apply_layer_to_component(
    current: Option<SceneComponentBlob>,
    overrides: &PrefabOverrides,
    target: &PrefabActorPath,
    component_type: &str,
) -> Result<Option<SceneComponentBlob>, String> {
    let mut current = apply_layer_prerequisites(current, overrides, target, component_type)?;
    for property in &overrides.properties {
        if property.target != *target || property.component_type != component_type {
            continue;
        }
        let source = current.as_ref().ok_or_else(|| {
            format!(
                "nested property `{}` target is missing component `{component_type}`",
                property.property_path
            )
        })?;
        let ron = if component_type == std::any::type_name::<Transform>() {
            apply_transform_property(&source.ron, property)?
        } else if component_type == std::any::type_name::<MaterialDesc>() {
            apply_material_property(&source.ron, property)?
        } else {
            return Err(format!(
                "nested property resolution does not support component `{component_type}`"
            ));
        };
        current = Some(SceneComponentBlob::from_ron(component_type, ron)?);
    }
    if overrides.structural.iter().any(|structural| {
        matches!(structural, PrefabStructuralOverride::RemoveChild { target: candidate, .. } if candidate == target)
    }) {
        return Ok(None);
    }
    Ok(current)
}

fn apply_layer_prerequisites(
    mut current: Option<SceneComponentBlob>,
    overrides: &PrefabOverrides,
    target: &PrefabActorPath,
    component_type: &str,
) -> Result<Option<SceneComponentBlob>, String> {
    if component_type == std::any::type_name::<Transform>() {
        for structural in &overrides.structural {
            if let PrefabStructuralOverride::ReparentChild {
                target: candidate,
                base_transform_ron,
                local_transform_ron,
                ..
            } = structural
            {
                if candidate != target {
                    continue;
                }
                if let Some(base) = base_transform_ron {
                    let current_ron = current.as_ref().map(|component| component.ron.as_str());
                    if current_ron
                        .is_none_or(|value| !component_values_equal(component_type, value, base))
                    {
                        return Err(format!(
                            "nested reparent transform for `{}` conflicts with its source",
                            actor_path_key(target)
                        ));
                    }
                }
                if let Some(value) = local_transform_ron {
                    current = Some(SceneComponentBlob::from_ron(component_type, value.clone())?);
                }
            }
        }
    }
    for component in &overrides.components {
        if component.target != *target || component.component_type != component_type {
            continue;
        }
        let current_ron = current.as_ref().map(|value| value.ron.as_str());
        let base_matches = match (&component.base_component_ron, current_ron) {
            (None, None) => true,
            (Some(base), Some(current)) => component_values_equal(component_type, current, base),
            _ => false,
        };
        if !base_matches {
            return Err(format!(
                "nested component `{component_type}` for `{}` conflicts with its source",
                actor_path_key(target)
            ));
        }
        current = component
            .value_component_ron
            .as_ref()
            .map(|value| SceneComponentBlob::from_ron(component_type, value.clone()))
            .transpose()?;
    }
    Ok(current)
}

fn source_target_exists(
    project_root: &Path,
    instance: &PrefabInstance,
    target: &PrefabActorPath,
) -> bool {
    source_document_for_target(project_root, instance, target)
        .ok()
        .and_then(|document| {
            document
                .find_entity(&SceneEntityId::ActorId(target.actor_id.clone()))
                .map(|_| ())
        })
        .is_some()
}

fn accept_current_source(world: &mut World, entity: Entity) {
    let old = capture_prefab_edit_state(world, entity);
    let Some(instance) = world.get::<PrefabInstance>(entity).cloned() else {
        return;
    };
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return;
    };
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, entity, &overrides);
    record_prefab_edit_state(world, entity, old);
}

fn keep_prefab_overrides(world: &mut World, entity: Entity) {
    let old = capture_prefab_edit_state(world, entity);
    let Some(instance) = world.get::<PrefabInstance>(entity).cloned() else {
        return;
    };
    let Some(project_root) = world
        .get_resource::<crate::project_io::ProjectWorkspace>()
        .map(|workspace| PathBuf::from(&workspace.root))
    else {
        return;
    };
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return;
    };
    let prerequisite_layer = overrides.clone();
    for property in &mut overrides.properties {
        if let Ok(Some(source)) = source_component_for_target(
            &project_root,
            &instance,
            &property.target,
            &property.component_type,
        )
        .and_then(|source| {
            apply_layer_prerequisites(
                source,
                &prerequisite_layer,
                &property.target,
                &property.component_type,
            )
        }) {
            property.base_component_ron = source.ron;
        }
    }
    for component in &mut overrides.components {
        component.base_component_ron = source_component_for_target(
            &project_root,
            &instance,
            &component.target,
            &component.component_type,
        )
        .ok()
        .flatten()
        .map(|source| source.ron);
    }
    for structural in &mut overrides.structural {
        match structural {
            PrefabStructuralOverride::RemoveChild {
                base_subtree_revision,
                ..
            } => {
                *base_subtree_revision =
                    prefab_source_revision(&project_root, &instance.source_path)
                        .unwrap_or_default();
            }
            PrefabStructuralOverride::ReparentChild {
                target,
                base_parent,
                base_transform_ron,
                ..
            } => {
                if let Ok(parent) = source_parent_for_target(&project_root, &instance, target) {
                    *base_parent = parent;
                }
                *base_transform_ron = source_component_for_target(
                    &project_root,
                    &instance,
                    target,
                    std::any::type_name::<Transform>(),
                )
                .ok()
                .flatten()
                .map(|component| component.ron);
            }
            PrefabStructuralOverride::AttachLocalChild { .. } => {}
        }
    }
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, entity, &overrides);
    record_prefab_edit_state(world, entity, old);
}

fn take_source_for_conflicts(world: &mut World, entity: Entity) {
    let old = capture_prefab_edit_state(world, entity);
    let Some(instance) = world.get::<PrefabInstance>(entity).cloned() else {
        return;
    };
    let Some(project_root) = world
        .get_resource::<crate::project_io::ProjectWorkspace>()
        .map(|workspace| PathBuf::from(&workspace.root))
    else {
        return;
    };
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return;
    };
    let conflicts: HashSet<_> = prefab_semantic_conflicts(&project_root, &instance, &overrides)
        .into_iter()
        .collect();
    overrides
        .properties
        .retain(|property| !conflicts.contains(&property_conflict_key(property)));
    overrides
        .components
        .retain(|component| !conflicts.contains(&component_conflict_key(component)));
    overrides
        .structural
        .retain(|structural| !conflicts.contains(&structural_conflict_key(structural)));
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, entity, &overrides);
    record_prefab_edit_state(world, entity, old);
}

pub fn revert_prefab_instance_overrides(world: &mut World, entity: Entity) {
    let old = capture_prefab_edit_state(world, entity);
    let Some(instance) = world.get::<PrefabInstance>(entity).cloned() else {
        return;
    };
    let mut overrides = PrefabOverrides::default();
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, entity, &overrides);
    record_prefab_edit_state(world, entity, old);
}

fn remove_stale_visibility_overrides(world: &mut World, entity: Entity) {
    let old = capture_prefab_edit_state(world, entity);
    let Some(instance) = world.get::<PrefabInstance>(entity).cloned() else {
        return;
    };
    let Some(project_root) = world
        .get_resource::<crate::project_io::ProjectWorkspace>()
        .map(|workspace| PathBuf::from(&workspace.root))
    else {
        return;
    };
    let known_keys = prefab_legacy_target_keys(&project_root, &instance);
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return;
    };
    overrides
        .child_visibility
        .retain(|key, _| known_keys.contains(key));
    overrides
        .properties
        .retain(|property| source_target_exists(&project_root, &instance, &property.target));
    overrides
        .components
        .retain(|component| source_target_exists(&project_root, &instance, &component.target));
    overrides.structural.retain(|structural| match structural {
        PrefabStructuralOverride::RemoveChild { target, .. }
        | PrefabStructuralOverride::ReparentChild { target, .. } => {
            source_target_exists(&project_root, &instance, target)
        }
        PrefabStructuralOverride::AttachLocalChild { parent, .. } => {
            source_target_exists(&project_root, &instance, parent)
        }
    });
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, entity, &overrides);
    record_prefab_edit_state(world, entity, old);
}

fn discard_invalid_overrides(world: &mut World, entity: Entity) {
    let old = capture_prefab_edit_state(world, entity);
    let Some(mut instance) = world.get::<PrefabInstance>(entity).cloned() else {
        return;
    };
    instance.overrides_ron = None;
    world.entity_mut(entity).insert(instance);
    record_prefab_edit_state(world, entity, old);
}

fn relink_prefab_source(world: &mut World, entity: Entity) {
    let Some(project_root) = world
        .get_resource::<crate::project_io::ProjectWorkspace>()
        .map(|workspace| PathBuf::from(&workspace.root))
    else {
        return;
    };
    let Some(path) = rfd::FileDialog::new()
        .set_directory(project_root.join("assets/prefabs"))
        .add_filter("Bevy prefab", &["scn.ron", "ron"])
        .pick_file()
    else {
        return;
    };
    let Ok(path) = path.canonicalize() else {
        return;
    };
    let Ok(root) = project_root.canonicalize() else {
        return;
    };
    let Ok(relative) = path.strip_prefix(&root) else {
        world.resource_mut::<crate::scene_io::SceneIo>().status =
            "Prefab source must remain inside the project".into();
        return;
    };
    let source_path = relative.to_string_lossy().replace('\\', "/");
    if let Err(error) = scene::validate_prefab_graph(&path, &root) {
        world.resource_mut::<crate::scene_io::SceneIo>().status =
            format!("Prefab relink failed: {error}");
        return;
    }
    let old = capture_prefab_edit_state(world, entity);
    let Some(mut instance) = world.get::<PrefabInstance>(entity).cloned() else {
        return;
    };
    instance.source_path = source_path.clone();
    let label = path
        .file_stem()
        .and_then(|value| value.to_str())
        .unwrap_or("Prefab")
        .to_string();
    let registration = world
        .get_resource_mut::<AssetRegistry>()
        .ok_or_else(|| "asset registry is unavailable".to_string())
        .and_then(|mut registry| {
            ensure_asset_record(&mut registry, source_path.clone(), label, "Prefab")
        });
    let record = match registration {
        Ok(record) => record,
        Err(error) => {
            world.resource_mut::<crate::scene_io::SceneIo>().status =
                format!("Prefab relink failed: could not register source: {error}");
            return;
        }
    };
    instance.asset_id = record.id.as_string();
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return;
    };
    track_current_source_revision(world, &instance, &mut overrides);
    instance.overrides_ron = encode_prefab_overrides(&overrides).ok().flatten();
    world
        .entity_mut(entity)
        .remove::<shared::PrefabHydrationBlocked>()
        .insert((instance, PrefabRef::new(source_path)));
    record_prefab_edit_state(world, entity, old);
    world.resource_mut::<crate::scene_io::SceneIo>().status = "Prefab source relinked".into();
}

pub fn read_overrides(instance: &PrefabInstance) -> PrefabOverrides {
    try_read_overrides(instance).unwrap_or_default()
}

pub fn try_read_overrides(instance: &PrefabInstance) -> Result<PrefabOverrides, String> {
    decode_prefab_overrides(instance)
}

fn read_overrides_for_edit(
    world: &mut World,
    instance: &PrefabInstance,
) -> Option<PrefabOverrides> {
    match try_read_overrides(instance) {
        Ok(overrides) => Some(overrides),
        Err(error) => {
            world.resource_mut::<crate::scene_io::SceneIo>().status =
                format!("Prefab edit blocked: {error}. Use Discard invalid data to recover.");
            None
        }
    }
}

pub fn new_tracked_prefab_instance(
    world: &World,
    asset_id: impl Into<String>,
    source_path: impl Into<String>,
) -> PrefabInstance {
    let mut instance = PrefabInstance::new(asset_id, source_path);
    let mut overrides = PrefabOverrides::default();
    track_current_source_revision(world, &instance, &mut overrides);
    if overrides != PrefabOverrides::default() {
        instance.overrides_ron = encode_prefab_overrides(&overrides).ok().flatten();
    }
    instance
}

pub fn write_overrides(world: &mut World, entity: Entity, overrides: &PrefabOverrides) {
    let Some(mut instance) = world.get::<PrefabInstance>(entity).cloned() else {
        return;
    };
    instance.overrides_ron = encode_prefab_overrides(overrides).unwrap_or_default();
    if let Ok(mut entity_mut) = world.get_entity_mut(entity) {
        entity_mut.insert(instance);
    }
}

pub fn set_child_visibility_override(
    world: &mut World,
    entity: Entity,
    child: Entity,
    visible: bool,
) {
    let Some((instance_root, target)) = prefab_member_override_target(world, child) else {
        world.resource_mut::<crate::scene_io::SceneIo>().status =
            "Cannot store prefab visibility: target has no stable prefab path".into();
        return;
    };
    if instance_root != entity {
        return;
    }
    let Some(instance) = world.get::<PrefabInstance>(instance_root).cloned() else {
        return;
    };
    let component_type = std::any::type_name::<EditorVisibility>().to_string();
    let value = EditorVisibility { visible };
    let value_component_ron = match ron::to_string(&value) {
        Ok(value) => value,
        Err(_) => return,
    };
    let source_base = world
        .get_resource::<crate::project_io::ProjectWorkspace>()
        .map(|workspace| PathBuf::from(&workspace.root))
        .and_then(|project_root| {
            source_component_for_target(&project_root, &instance, &target, &component_type)
                .ok()
                .flatten()
                .map(|component| component.ron)
        });

    let old = capture_prefab_edit_state(world, instance_root);
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return;
    };
    overrides.child_visibility.remove(&target.actor_id);
    let base_component_ron = overrides
        .components
        .iter()
        .find(|candidate| candidate.target == target && candidate.component_type == component_type)
        .and_then(|candidate| candidate.base_component_ron.clone())
        .or(source_base);
    overrides.components.retain(|candidate| {
        !(candidate.target == target && candidate.component_type == component_type)
    });
    overrides.components.push(PrefabComponentOverride {
        target,
        component_type,
        base_component_ron,
        value_component_ron: Some(value_component_ron),
    });
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, instance_root, &overrides);
    world.entity_mut(child).insert(value);
    record_prefab_edit_state(world, instance_root, old);
}

pub fn prefab_member_override_target(
    world: &World,
    member: Entity,
) -> Option<(Entity, PrefabActorPath)> {
    let actor_id = world.get::<ActorId>(member)?.0.clone();
    let mut instance_root = world.get::<HydratedPrefabMember>(member)?.instance_root;
    let mut instance_chain = Vec::new();
    let mut seen = HashSet::new();
    while seen.insert(instance_root) {
        let Some(owner) = world.get::<HydratedPrefabMember>(instance_root) else {
            break;
        };
        instance_chain.insert(0, world.get::<ActorId>(instance_root)?.0.clone());
        instance_root = owner.instance_root;
    }
    world.get::<PrefabInstance>(instance_root)?;
    Some((
        instance_root,
        PrefabActorPath {
            instance_chain,
            actor_id,
        },
    ))
}

pub fn set_prefab_material_property_override(
    world: &mut World,
    member: Entity,
    property_path: &str,
    value: MaterialDesc,
) {
    record_prefab_property_override(world, member, property_path, value);
}

pub fn set_prefab_transform_property_override(
    world: &mut World,
    member: Entity,
    property_path: &str,
    value: Transform,
) {
    record_prefab_property_override(world, member, property_path, value);
}

fn record_prefab_property_override<T>(
    world: &mut World,
    member: Entity,
    property_path: &str,
    value: T,
) where
    T: Component + Clone + serde::Serialize,
{
    let Some((instance_root, target)) = prefab_member_override_target(world, member) else {
        return;
    };
    let Some(instance) = world.get::<PrefabInstance>(instance_root).cloned() else {
        return;
    };
    let Some(current) = world.get::<T>(member).cloned() else {
        return;
    };
    let component_type = std::any::type_name::<T>().to_string();
    let current_ron = match ron::to_string(&current) {
        Ok(value) => value,
        Err(error) => {
            world.resource_mut::<crate::scene_io::SceneIo>().status =
                format!("Could not capture prefab property: {error}");
            return;
        }
    };
    let value_ron = match ron::to_string(&value) {
        Ok(value) => value,
        Err(error) => {
            world.resource_mut::<crate::scene_io::SceneIo>().status =
                format!("Could not encode prefab property: {error}");
            return;
        }
    };

    let old = capture_prefab_edit_state(world, instance_root);
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return;
    };
    let base_component_ron = overrides
        .properties
        .iter()
        .find(|candidate| candidate.target == target && candidate.component_type == component_type)
        .map(|candidate| candidate.base_component_ron.clone())
        .unwrap_or(current_ron);
    overrides.properties.retain(|candidate| {
        !(candidate.target == target
            && candidate.component_type == component_type
            && candidate.property_path == property_path)
    });
    overrides.properties.push(PrefabPropertyOverride {
        target,
        component_type,
        property_path: property_path.to_string(),
        base_component_ron,
        value_component_ron: value_ron,
    });
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, instance_root, &overrides);
    world.entity_mut(member).insert(value);
    record_prefab_edit_state(world, instance_root, old);
}

pub fn remove_prefab_component_override<T>(world: &mut World, member: Entity)
where
    T: Component + Clone + serde::Serialize,
{
    let Some((instance_root, target)) = prefab_member_override_target(world, member) else {
        return;
    };
    let Some(instance) = world.get::<PrefabInstance>(instance_root).cloned() else {
        return;
    };
    let Some(current) = world.get::<T>(member).cloned() else {
        return;
    };
    let Ok(current_component_ron) = ron::to_string(&current) else {
        return;
    };
    let component_type = std::any::type_name::<T>().to_string();
    let old = capture_prefab_edit_state(world, instance_root);
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return;
    };
    let existing_component = overrides
        .components
        .iter()
        .find(|candidate| candidate.target == target && candidate.component_type == component_type);
    let cancels_local_add = existing_component.is_some_and(|component| {
        component.base_component_ron.is_none() && component.value_component_ron.is_some()
    });
    let base_component_ron = existing_component
        .and_then(|component| component.base_component_ron.clone())
        .or_else(|| {
            overrides
                .properties
                .iter()
                .find(|candidate| {
                    candidate.target == target && candidate.component_type == component_type
                })
                .map(|property| property.base_component_ron.clone())
        })
        .or_else(|| {
            world
                .get_resource::<crate::project_io::ProjectWorkspace>()
                .map(|workspace| PathBuf::from(&workspace.root))
                .and_then(|project_root| {
                    source_component_for_target(&project_root, &instance, &target, &component_type)
                        .ok()
                        .flatten()
                        .map(|component| component.ron)
                })
        })
        .or(Some(current_component_ron));
    overrides.properties.retain(|candidate| {
        !(candidate.target == target && candidate.component_type == component_type)
    });
    overrides.components.retain(|candidate| {
        !(candidate.target == target && candidate.component_type == component_type)
    });
    if !cancels_local_add {
        overrides.components.push(PrefabComponentOverride {
            target,
            component_type,
            base_component_ron,
            value_component_ron: None,
        });
    }
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, instance_root, &overrides);
    world.entity_mut(member).remove::<T>();
    record_prefab_edit_state(world, instance_root, old);
}

pub fn set_prefab_component_override<T>(world: &mut World, member: Entity, value: T)
where
    T: Component + Clone + serde::Serialize,
{
    let Some((instance_root, target)) = prefab_member_override_target(world, member) else {
        return;
    };
    let Some(instance) = world.get::<PrefabInstance>(instance_root).cloned() else {
        return;
    };
    let value_component_ron = match ron::to_string(&value) {
        Ok(value) => value,
        Err(_) => return,
    };
    let component_type = std::any::type_name::<T>().to_string();
    let base_component_ron = world
        .get::<T>(member)
        .and_then(|current| ron::to_string(current).ok());
    let old = capture_prefab_edit_state(world, instance_root);
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return;
    };
    let base_component_ron = overrides
        .components
        .iter()
        .find(|candidate| candidate.target == target && candidate.component_type == component_type)
        .and_then(|candidate| candidate.base_component_ron.clone())
        .or(base_component_ron);
    overrides.components.retain(|candidate| {
        !(candidate.target == target && candidate.component_type == component_type)
    });
    overrides.components.push(PrefabComponentOverride {
        target,
        component_type,
        base_component_ron,
        value_component_ron: Some(value_component_ron),
    });
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, instance_root, &overrides);
    world.entity_mut(member).insert(value);
    record_prefab_edit_state(world, instance_root, old);
}

pub fn revert_prefab_override_scope(
    world: &mut World,
    member: Entity,
    component_type: Option<&str>,
    property_path: Option<&str>,
) {
    let Some((instance_root, target)) = prefab_member_override_target(world, member) else {
        return;
    };
    revert_prefab_target_scope(world, instance_root, &target, component_type, property_path);
}

fn revert_prefab_target_scope(
    world: &mut World,
    instance_root: Entity,
    target: &PrefabActorPath,
    component_type: Option<&str>,
    property_path: Option<&str>,
) {
    let Some(instance) = world.get::<PrefabInstance>(instance_root).cloned() else {
        return;
    };
    let old = capture_prefab_edit_state(world, instance_root);
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return;
    };
    overrides.properties.retain(|candidate| {
        candidate.target != *target
            || component_type.is_some_and(|value| candidate.component_type != value)
            || property_path.is_some_and(|value| candidate.property_path != value)
    });
    if property_path.is_none() {
        overrides.components.retain(|candidate| {
            candidate.target != *target
                || component_type.is_some_and(|value| candidate.component_type != value)
        });
    }
    if component_type.is_none() && property_path.is_none() {
        overrides
            .structural
            .retain(|candidate| structural_target(candidate) != target);
    }
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, instance_root, &overrides);
    record_prefab_edit_state(world, instance_root, old);
}

pub fn revert_prefab_actor_path(
    world: &mut World,
    instance_root: Entity,
    target: &PrefabActorPath,
) {
    revert_prefab_target_scope(world, instance_root, target, None, None);
}

pub fn apply_prefab_member_scope_to_source(
    world: &mut World,
    member: Entity,
    component_type: Option<&str>,
    property_path: Option<&str>,
) {
    let Some((instance_root, target)) = prefab_member_override_target(world, member) else {
        return;
    };
    apply_prefab_scope_to_source(
        world,
        instance_root,
        Some(&target),
        component_type,
        property_path,
    );
}

pub fn apply_prefab_instance_overrides_to_source(world: &mut World, instance_root: Entity) {
    apply_prefab_scope_to_source(world, instance_root, None, None, None);
}

pub fn apply_prefab_actor_path_to_source(
    world: &mut World,
    instance_root: Entity,
    target: &PrefabActorPath,
) {
    apply_prefab_scope_to_source(world, instance_root, Some(target), None, None);
}

fn apply_prefab_scope_to_source(
    world: &mut World,
    instance_root: Entity,
    target_filter: Option<&PrefabActorPath>,
    component_type: Option<&str>,
    property_path: Option<&str>,
) {
    let Some(instance) = world.get::<PrefabInstance>(instance_root).cloned() else {
        return;
    };
    let Some(project_root) = world
        .get_resource::<crate::project_io::ProjectWorkspace>()
        .map(|workspace| PathBuf::from(&workspace.root))
    else {
        return;
    };
    let source_path = project_root.join(&instance.source_path);
    let before_source = match std::fs::read_to_string(&source_path) {
        Ok(source) => source,
        Err(error) => {
            world.resource_mut::<crate::scene_io::SceneIo>().status =
                format!("Apply to source failed: {error}");
            return;
        }
    };
    let before_snapshot = crate::collaboration::FileSnapshot::from_loaded_bytes(
        &source_path,
        before_source.as_bytes(),
    );
    let mut document = match SceneDocument::from_ron_text(&before_source) {
        Ok(document) => document,
        Err(error) => {
            world.resource_mut::<crate::scene_io::SceneIo>().status =
                format!("Apply to source failed: {error}");
            return;
        }
    };
    let old = capture_prefab_edit_state(world, instance_root);
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return;
    };
    if let Err(error) =
        promote_legacy_visibility_overrides(&project_root, &instance, &mut overrides)
    {
        world.resource_mut::<crate::scene_io::SceneIo>().status =
            format!("Apply to source failed: {error}");
        return;
    }
    let selected_properties: Vec<_> = overrides
        .properties
        .iter()
        .filter(|candidate| {
            target_filter.is_none_or(|target| candidate.target == *target)
                && component_type.is_none_or(|value| candidate.component_type == value)
                && property_path.is_none_or(|value| candidate.property_path == value)
        })
        .cloned()
        .collect();
    let selected_components: Vec<_> = overrides
        .components
        .iter()
        .filter(|candidate| {
            target_filter.is_none_or(|target| candidate.target == *target)
                && component_type.is_none_or(|value| candidate.component_type == value)
                && (property_path.is_none()
                    || (candidate.base_component_ron.is_none()
                        && candidate.value_component_ron.is_some()))
        })
        .cloned()
        .collect();
    let selected_structural: Vec<_> = if component_type.is_none() && property_path.is_none() {
        overrides
            .structural
            .iter()
            .filter(|candidate| {
                target_filter.is_none_or(|target| structural_target(candidate) == target)
            })
            .cloned()
            .collect()
    } else {
        Vec::new()
    };
    if selected_properties.is_empty()
        && selected_components.is_empty()
        && selected_structural.is_empty()
    {
        world.resource_mut::<crate::scene_io::SceneIo>().status =
            "No prefab overrides in this scope".into();
        return;
    }

    let apply_result = (|| {
        for structural in &selected_structural {
            validate_structural_apply_base(&project_root, &instance, structural)?;
        }
        for component in &selected_components {
            validate_component_apply_base(
                &project_root,
                &instance,
                component,
                &selected_structural,
            )?;
        }
        for property in &selected_properties {
            validate_property_apply_base(
                &project_root,
                &instance,
                property,
                &selected_components,
                &selected_structural,
            )?;
        }
        for structural in &selected_structural {
            if matches!(structural, PrefabStructuralOverride::ReparentChild { .. }) {
                apply_or_transfer_structural(&mut document, structural, &project_root)?;
            }
        }
        for component in &selected_components {
            if component.target.instance_chain.is_empty() {
                apply_component_to_document(&mut document, component)?;
            } else {
                transfer_component_to_nested_layer(
                    &mut document,
                    component.clone(),
                    &project_root,
                )?;
            }
        }
        for property in &selected_properties {
            if property.target.instance_chain.is_empty() {
                apply_property_to_document(&mut document, property)?;
            } else {
                transfer_property_to_nested_layer(&mut document, property.clone(), &project_root)?;
            }
        }
        for structural in &selected_structural {
            if !matches!(structural, PrefabStructuralOverride::ReparentChild { .. }) {
                apply_or_transfer_structural(&mut document, structural, &project_root)?;
            }
        }
        let after_source = document.to_ron_text()?;
        scene::validate_prefab_graph_text(&after_source, &source_path, &project_root)?;
        crate::collaboration::publish_authored_file(
            world,
            &source_path,
            after_source.as_bytes(),
            &before_snapshot,
            crate::collaboration::FileWriteIntent::PrefabSource { instance_root },
        )?;
        Ok::<_, String>(after_source)
    })();
    let after_source = match apply_result {
        Ok(source) => source,
        Err(error) => {
            world.resource_mut::<crate::scene_io::SceneIo>().status =
                format!("Apply to source failed: {error}");
            return;
        }
    };

    overrides
        .properties
        .retain(|candidate| !selected_properties.contains(candidate));
    overrides
        .components
        .retain(|candidate| !selected_components.contains(candidate));
    overrides
        .structural
        .retain(|candidate| !selected_structural.contains(candidate));
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, instance_root, &overrides);
    record_prefab_source_apply(
        world,
        instance_root,
        source_path,
        before_source,
        after_source,
        old,
    );
    world.resource_mut::<crate::scene_io::SceneIo>().status =
        "Prefab overrides applied to source".into();
}

fn apply_or_transfer_structural(
    document: &mut SceneDocument,
    structural: &PrefabStructuralOverride,
    project_root: &Path,
) -> Result<(), String> {
    if structural_target(structural).instance_chain.is_empty() {
        apply_structural_to_document(document, structural)
    } else {
        transfer_structural_to_nested_layer(document, structural.clone(), project_root)
    }
}

fn validate_property_apply_base(
    project_root: &Path,
    instance: &PrefabInstance,
    property: &PrefabPropertyOverride,
    selected_components: &[PrefabComponentOverride],
    selected_structural: &[PrefabStructuralOverride],
) -> Result<(), String> {
    let mut current = source_component_for_target(
        project_root,
        instance,
        &property.target,
        &property.component_type,
    )?;
    if property.component_type == std::any::type_name::<Transform>() {
        if let Some(local_transform_ron) =
            selected_structural
                .iter()
                .find_map(|structural| match structural {
                    PrefabStructuralOverride::ReparentChild {
                        target,
                        local_transform_ron,
                        ..
                    } if target == &property.target => local_transform_ron.clone(),
                    _ => None,
                })
        {
            current = Some(SceneComponentBlob::from_ron(
                std::any::type_name::<Transform>(),
                local_transform_ron,
            )?);
        }
    }
    for component in selected_components.iter().filter(|component| {
        component.target == property.target && component.component_type == property.component_type
    }) {
        let current_ron = current.as_ref().map(|value| value.ron.as_str());
        let base_matches = match (&component.base_component_ron, current_ron) {
            (None, None) => true,
            (Some(base), Some(current)) => {
                component_values_equal(&component.component_type, current, base)
            }
            _ => false,
        };
        if !base_matches {
            return Err(format!(
                "conflict: source component `{}` changed",
                component.component_type
            ));
        }
        current = component
            .value_component_ron
            .as_ref()
            .map(|value| {
                SceneComponentBlob::from_ron(component.component_type.clone(), value.clone())
            })
            .transpose()?;
    }
    let source = current.ok_or_else(|| {
        format!(
            "source actor `{}` is missing component `{}`",
            actor_path_key(&property.target),
            property.component_type
        )
    })?;
    if property.component_type == std::any::type_name::<Transform>() {
        apply_transform_property(&source.ron, property).map(|_| ())
    } else if property.component_type == std::any::type_name::<MaterialDesc>() {
        apply_material_property(&source.ron, property).map(|_| ())
    } else {
        Err(format!(
            "property apply does not support component `{}`",
            property.component_type
        ))
    }
}

fn validate_component_apply_base(
    project_root: &Path,
    instance: &PrefabInstance,
    component: &PrefabComponentOverride,
    selected_structural: &[PrefabStructuralOverride],
) -> Result<(), String> {
    let mut current = source_component_for_target(
        project_root,
        instance,
        &component.target,
        &component.component_type,
    )?;
    if component.component_type == std::any::type_name::<Transform>() {
        if let Some(local_transform_ron) =
            selected_structural
                .iter()
                .find_map(|structural| match structural {
                    PrefabStructuralOverride::ReparentChild {
                        target,
                        local_transform_ron,
                        ..
                    } if target == &component.target => local_transform_ron.clone(),
                    _ => None,
                })
        {
            current = Some(SceneComponentBlob::from_ron(
                std::any::type_name::<Transform>(),
                local_transform_ron,
            )?);
        }
    }
    let current_ron = current.as_ref().map(|value| value.ron.as_str());
    let base_matches = match (&component.base_component_ron, current_ron) {
        (None, None) => true,
        (Some(base), Some(current)) => {
            component_values_equal(&component.component_type, current, base)
        }
        _ => false,
    };
    if base_matches {
        Ok(())
    } else {
        Err(format!(
            "conflict: source component `{}` changed",
            component.component_type
        ))
    }
}

fn structural_target(structural: &PrefabStructuralOverride) -> &PrefabActorPath {
    match structural {
        PrefabStructuralOverride::RemoveChild { target, .. }
        | PrefabStructuralOverride::ReparentChild { target, .. } => target,
        PrefabStructuralOverride::AttachLocalChild { parent, .. } => parent,
    }
}

fn promote_legacy_visibility_overrides(
    project_root: &Path,
    instance: &PrefabInstance,
    overrides: &mut PrefabOverrides,
) -> Result<(), String> {
    if overrides.child_visibility.is_empty() {
        return Ok(());
    }
    let direct = PrefabActorPath::direct("");
    let document = source_document_for_target(project_root, instance, &direct)?;
    let component_type = std::any::type_name::<EditorVisibility>().to_string();
    let legacy = std::mem::take(&mut overrides.child_visibility);
    for (key, visible) in legacy {
        let source = document
            .entities
            .iter()
            .find(|entity| {
                entity.actor_id.as_deref() == Some(key.as_str())
                    || entity.name.as_deref() == Some(key.as_str())
            })
            .ok_or_else(|| format!("legacy visibility target `{key}` no longer exists"))?;
        let actor_id = source
            .actor_id
            .clone()
            .ok_or_else(|| format!("legacy visibility target `{key}` has no ActorId"))?;
        let target = PrefabActorPath::direct(actor_id);
        let base_component_ron = source
            .components
            .iter()
            .find(|component| component.type_name == component_type)
            .map(|component| component.ron.clone());
        overrides.components.retain(|candidate| {
            !(candidate.target == target && candidate.component_type == component_type)
        });
        overrides.components.push(PrefabComponentOverride {
            target,
            component_type: component_type.clone(),
            base_component_ron,
            value_component_ron: Some(
                ron::to_string(&EditorVisibility { visible }).map_err(|error| error.to_string())?,
            ),
        });
    }
    Ok(())
}

fn apply_property_to_document(
    document: &mut SceneDocument,
    property: &PrefabPropertyOverride,
) -> Result<(), String> {
    let target = SceneEntityId::ActorId(property.target.actor_id.clone());
    let source = document
        .find_entity(&target)
        .and_then(|entity| {
            entity
                .components
                .iter()
                .find(|component| component.type_name == property.component_type)
        })
        .cloned()
        .ok_or_else(|| {
            format!(
                "source actor `{}` is missing component `{}`",
                property.target.actor_id, property.component_type
            )
        })?;
    let ron = if property.component_type == std::any::type_name::<Transform>() {
        apply_transform_property(&source.ron, property)?
    } else if property.component_type == std::any::type_name::<MaterialDesc>() {
        apply_material_property(&source.ron, property)?
    } else {
        return Err(format!(
            "property apply does not support component `{}`",
            property.component_type
        ));
    };
    document.apply_patch(ScenePatch::SetComponent {
        target,
        component: SceneComponentBlob::from_ron(property.component_type.clone(), ron)?,
    })
}

fn apply_transform_property(
    source_ron: &str,
    property: &PrefabPropertyOverride,
) -> Result<String, String> {
    let mut source: Transform = ron::from_str(source_ron).map_err(|error| error.to_string())?;
    let base: Transform =
        ron::from_str(&property.base_component_ron).map_err(|error| error.to_string())?;
    let value: Transform =
        ron::from_str(&property.value_component_ron).map_err(|error| error.to_string())?;
    let conflict = match property.property_path.as_str() {
        "translation" => {
            let conflict = source.translation != base.translation;
            source.translation = value.translation;
            conflict
        }
        "rotation" => {
            let conflict = source.rotation != base.rotation;
            source.rotation = value.rotation;
            conflict
        }
        "scale" => {
            let conflict = source.scale != base.scale;
            source.scale = value.scale;
            conflict
        }
        path => return Err(format!("unsupported Transform property `{path}`")),
    };
    if conflict {
        return Err(format!(
            "conflict: source Transform.{} changed since the override was created",
            property.property_path
        ));
    }
    ron::to_string(&source).map_err(|error| error.to_string())
}

fn apply_material_property(
    source_ron: &str,
    property: &PrefabPropertyOverride,
) -> Result<String, String> {
    let mut source: MaterialDesc = ron::from_str(source_ron).map_err(|error| error.to_string())?;
    let base: MaterialDesc =
        ron::from_str(&property.base_component_ron).map_err(|error| error.to_string())?;
    let value: MaterialDesc =
        ron::from_str(&property.value_component_ron).map_err(|error| error.to_string())?;
    let conflict = match property.property_path.as_str() {
        "base_color" => {
            let conflict = source.base_color != base.base_color;
            source.base_color = value.base_color;
            conflict
        }
        "metallic" => {
            let conflict = source.metallic != base.metallic;
            source.metallic = value.metallic;
            conflict
        }
        "roughness" => {
            let conflict = source.roughness != base.roughness;
            source.roughness = value.roughness;
            conflict
        }
        path => return Err(format!("unsupported MaterialDesc property `{path}`")),
    };
    if conflict {
        return Err(format!(
            "conflict: source MaterialDesc.{} changed since the override was created",
            property.property_path
        ));
    }
    ron::to_string(&source).map_err(|error| error.to_string())
}

fn apply_component_to_document(
    document: &mut SceneDocument,
    component: &PrefabComponentOverride,
) -> Result<(), String> {
    let target = SceneEntityId::ActorId(component.target.actor_id.clone());
    let current = document.find_entity(&target).and_then(|entity| {
        entity
            .components
            .iter()
            .find(|candidate| candidate.type_name == component.component_type)
    });
    match (&component.base_component_ron, current) {
        (None, Some(_)) => {
            return Err(format!(
                "conflict: source actor `{}` gained component `{}`",
                component.target.actor_id, component.component_type
            ));
        }
        (Some(base), Some(current))
            if !component_values_equal(&component.component_type, &current.ron, base) =>
        {
            return Err(format!(
                "conflict: source component `{}` changed",
                component.component_type
            ));
        }
        (Some(_), None) => {
            return Err(format!(
                "conflict: source component `{}` was removed",
                component.component_type
            ));
        }
        _ => {}
    }
    match &component.value_component_ron {
        Some(value) => document.apply_patch(ScenePatch::SetComponent {
            target,
            component: SceneComponentBlob::from_ron(
                component.component_type.clone(),
                value.clone(),
            )?,
        }),
        None => document.apply_patch(ScenePatch::RemoveComponent {
            target,
            component_type: component.component_type.clone(),
        }),
    }
}

fn component_values_equal(component_type: &str, left: &str, right: &str) -> bool {
    if component_type == std::any::type_name::<Transform>() {
        return ron::from_str::<Transform>(left).ok() == ron::from_str::<Transform>(right).ok();
    }
    if component_type == std::any::type_name::<MaterialDesc>() {
        return ron::from_str::<MaterialDesc>(left).ok()
            == ron::from_str::<MaterialDesc>(right).ok();
    }
    ron::from_str::<ron::Value>(left).ok() == ron::from_str::<ron::Value>(right).ok()
}

fn apply_structural_to_document(
    document: &mut SceneDocument,
    structural: &PrefabStructuralOverride,
) -> Result<(), String> {
    match structural {
        PrefabStructuralOverride::RemoveChild { target, .. } => {
            document.apply_patch(ScenePatch::DeleteEntity {
                target: SceneEntityId::ActorId(target.actor_id.clone()),
            })
        }
        PrefabStructuralOverride::ReparentChild {
            target,
            new_parent,
            local_transform_ron,
            ..
        } => {
            if new_parent
                .as_ref()
                .is_some_and(|parent| !parent.instance_chain.is_empty())
            {
                return Err("cannot apply a cross-layer parent to this source".to_string());
            }
            if document_reparent_would_cycle(document, target, new_parent.as_ref()) {
                return Err(format!(
                    "reparenting `{}` would create a hierarchy cycle",
                    actor_path_key(target)
                ));
            }
            document.apply_patch(ScenePatch::Reparent {
                target: SceneEntityId::ActorId(target.actor_id.clone()),
                parent_actor_id: new_parent.as_ref().map(|parent| parent.actor_id.clone()),
            })?;
            if let Some(transform_ron) = local_transform_ron {
                document.apply_patch(ScenePatch::SetComponent {
                    target: SceneEntityId::ActorId(target.actor_id.clone()),
                    component: SceneComponentBlob::from_ron(
                        std::any::type_name::<Transform>(),
                        transform_ron.clone(),
                    )?,
                })?;
            }
            Ok(())
        }
        PrefabStructuralOverride::AttachLocalChild { .. } => {
            Err("local child attachment must be converted before source apply".to_string())
        }
    }
}

fn document_reparent_would_cycle(
    document: &SceneDocument,
    target: &PrefabActorPath,
    new_parent: Option<&PrefabActorPath>,
) -> bool {
    let Some(new_parent) = new_parent else {
        return false;
    };
    let mut current = Some(new_parent.actor_id.as_str());
    let mut seen = HashSet::new();
    while let Some(actor_id) = current {
        if actor_id == target.actor_id || !seen.insert(actor_id.to_string()) {
            return true;
        }
        current = document
            .find_entity(&SceneEntityId::ActorId(actor_id.to_string()))
            .and_then(|entity| entity.parent_actor_id.as_deref());
    }
    false
}

fn validate_structural_apply_base(
    project_root: &Path,
    instance: &PrefabInstance,
    structural: &PrefabStructuralOverride,
) -> Result<(), String> {
    let target = structural_target(structural);
    let document = source_document_for_target(project_root, instance, target)?;
    let _source = document
        .find_entity(&SceneEntityId::ActorId(target.actor_id.clone()))
        .ok_or_else(|| {
            format!(
                "conflict: source actor `{}` no longer exists",
                actor_path_key(target)
            )
        })?;
    match structural {
        PrefabStructuralOverride::RemoveChild {
            base_subtree_revision,
            ..
        } => {
            let current = prefab_source_revision(project_root, &instance.source_path)?;
            if current != *base_subtree_revision {
                return Err(format!(
                    "conflict: source subtree `{}` changed since removal was authored",
                    actor_path_key(target)
                ));
            }
        }
        PrefabStructuralOverride::ReparentChild {
            base_parent,
            new_parent,
            base_transform_ron,
            ..
        } => {
            let current_parent = source_parent_for_target(project_root, instance, target)?;
            if current_parent != *base_parent {
                return Err(format!(
                    "conflict: source parent for `{}` changed",
                    actor_path_key(target)
                ));
            }
            if let Some(base) = base_transform_ron {
                let current = source_component_for_target(
                    project_root,
                    instance,
                    target,
                    std::any::type_name::<Transform>(),
                )?
                .ok_or_else(|| {
                    format!(
                        "conflict: source transform for `{}` was removed",
                        actor_path_key(target)
                    )
                })?;
                if !component_values_equal(std::any::type_name::<Transform>(), &current.ron, base) {
                    return Err(format!(
                        "conflict: source transform for `{}` changed",
                        actor_path_key(target)
                    ));
                }
            }
            if let Some(parent) = new_parent {
                if parent.instance_chain != target.instance_chain
                    || source_parent_for_target(project_root, instance, parent).is_err()
                {
                    return Err(format!(
                        "conflict: destination parent `{}` is unavailable",
                        actor_path_key(parent)
                    ));
                }
            }
        }
        PrefabStructuralOverride::AttachLocalChild { .. } => {}
    }
    Ok(())
}

fn transfer_property_to_nested_layer(
    document: &mut SceneDocument,
    mut property: PrefabPropertyOverride,
    project_root: &Path,
) -> Result<(), String> {
    let anchor = property.target.instance_chain.remove(0);
    update_nested_instance(document, &anchor, project_root, move |overrides, _| {
        merge_property_override(overrides, property);
    })
}

fn transfer_component_to_nested_layer(
    document: &mut SceneDocument,
    mut component: PrefabComponentOverride,
    project_root: &Path,
) -> Result<(), String> {
    let anchor = component.target.instance_chain.remove(0);
    update_nested_instance(document, &anchor, project_root, move |overrides, _| {
        merge_component_override(overrides, component);
    })
}

fn transfer_structural_to_nested_layer(
    document: &mut SceneDocument,
    mut structural: PrefabStructuralOverride,
    project_root: &Path,
) -> Result<(), String> {
    let anchor = structural_target(&structural)
        .instance_chain
        .first()
        .cloned()
        .ok_or_else(|| "nested structural override has no instance path".to_string())?;
    trim_structural_instance_prefix(&mut structural, &anchor)?;
    update_nested_instance(
        document,
        &anchor,
        project_root,
        move |overrides, nested_instance| {
            let base_revision =
                prefab_source_revision(project_root, &nested_instance.source_path).ok();
            merge_structural_override(overrides, structural, base_revision);
        },
    )
}

fn merge_property_override(overrides: &mut PrefabOverrides, mut property: PrefabPropertyOverride) {
    if let Some(existing) = overrides.properties.iter().find(|candidate| {
        candidate.target == property.target
            && candidate.component_type == property.component_type
            && candidate.property_path == property.property_path
    }) {
        property.base_component_ron = existing.base_component_ron.clone();
    }
    overrides.properties.retain(|candidate| {
        !(candidate.target == property.target
            && candidate.component_type == property.component_type
            && candidate.property_path == property.property_path)
    });
    overrides.properties.push(property);
}

fn merge_component_override(
    overrides: &mut PrefabOverrides,
    mut component: PrefabComponentOverride,
) {
    if let Some(existing) = overrides.components.iter().find(|candidate| {
        candidate.target == component.target && candidate.component_type == component.component_type
    }) {
        component.base_component_ron = existing.base_component_ron.clone();
    }
    overrides.components.retain(|candidate| {
        !(candidate.target == component.target
            && candidate.component_type == component.component_type)
    });
    overrides.components.push(component);
}

fn merge_structural_override(
    overrides: &mut PrefabOverrides,
    mut structural: PrefabStructuralOverride,
    base_revision: Option<String>,
) {
    let target = structural_target(&structural).clone();
    let existing = overrides
        .structural
        .iter()
        .find(|candidate| {
            std::mem::discriminant(*candidate) == std::mem::discriminant(&structural)
                && structural_target(candidate) == &target
        })
        .cloned();
    match (&mut structural, existing) {
        (
            PrefabStructuralOverride::RemoveChild {
                base_subtree_revision,
                ..
            },
            Some(PrefabStructuralOverride::RemoveChild {
                base_subtree_revision: existing,
                ..
            }),
        ) => *base_subtree_revision = existing,
        (
            PrefabStructuralOverride::ReparentChild {
                base_parent,
                base_transform_ron,
                ..
            },
            Some(PrefabStructuralOverride::ReparentChild {
                base_parent: existing_parent,
                base_transform_ron: existing_transform,
                ..
            }),
        ) => {
            *base_parent = existing_parent;
            *base_transform_ron = existing_transform;
        }
        (
            PrefabStructuralOverride::RemoveChild {
                base_subtree_revision,
                ..
            },
            None,
        ) => {
            if let Some(revision) = base_revision {
                *base_subtree_revision = revision;
            }
        }
        _ => {}
    }
    overrides.structural.retain(|candidate| {
        std::mem::discriminant(candidate) != std::mem::discriminant(&structural)
            || structural_target(candidate) != &target
    });
    overrides.structural.push(structural);
}

pub fn fold_nested_overrides_for_unpack(
    world: &mut World,
    instance_root: Entity,
) -> Result<(), String> {
    let instance = world
        .get::<PrefabInstance>(instance_root)
        .cloned()
        .ok_or_else(|| "prefab instance is missing".to_string())?;
    let outer = try_read_overrides(&instance)?;
    let nested_entities: Vec<(Entity, String, PrefabInstance)> = world
        .query::<(Entity, &HydratedPrefabMember, &ActorId, &PrefabInstance)>()
        .iter(world)
        .filter(|(_, owner, _, _)| owner.instance_root == instance_root)
        .map(|(entity, _, actor_id, nested)| (entity, actor_id.0.clone(), nested.clone()))
        .collect();
    let project_root = world
        .get_resource::<crate::project_io::ProjectWorkspace>()
        .map(|workspace| PathBuf::from(&workspace.root));
    let mut updates = Vec::new();
    for (entity, anchor_actor_id, mut nested_instance) in nested_entities {
        let mut nested_overrides = try_read_overrides(&nested_instance)?;
        let mut changed = false;
        for mut property in outer
            .properties
            .iter()
            .filter(|property| property.target.instance_chain.first() == Some(&anchor_actor_id))
            .cloned()
        {
            property.target.instance_chain.remove(0);
            merge_property_override(&mut nested_overrides, property);
            changed = true;
        }
        for mut component in outer
            .components
            .iter()
            .filter(|component| component.target.instance_chain.first() == Some(&anchor_actor_id))
            .cloned()
        {
            component.target.instance_chain.remove(0);
            merge_component_override(&mut nested_overrides, component);
            changed = true;
        }
        for mut structural in outer
            .structural
            .iter()
            .filter(|structural| {
                structural_target(structural).instance_chain.first() == Some(&anchor_actor_id)
            })
            .cloned()
        {
            trim_structural_instance_prefix(&mut structural, &anchor_actor_id)?;
            let base_revision = project_root
                .as_ref()
                .and_then(|root| prefab_source_revision(root, &nested_instance.source_path).ok());
            merge_structural_override(&mut nested_overrides, structural, base_revision);
            changed = true;
        }
        if !changed {
            continue;
        }
        if let Some(root) = project_root.as_ref() {
            nested_overrides.source_revision =
                prefab_source_revision(root, &nested_instance.source_path).ok();
        }
        nested_instance.overrides_ron = encode_prefab_overrides(&nested_overrides)?;
        updates.push((entity, nested_instance));
    }
    for (entity, nested_instance) in updates {
        world.entity_mut(entity).insert(nested_instance);
    }
    Ok(())
}

fn trim_structural_instance_prefix(
    structural: &mut PrefabStructuralOverride,
    prefix: &str,
) -> Result<(), String> {
    let trim = |path: &mut PrefabActorPath| -> Result<(), String> {
        if path.instance_chain.first().map(String::as_str) != Some(prefix) {
            return Err("structural override crosses an instance layer".to_string());
        }
        path.instance_chain.remove(0);
        Ok(())
    };
    match structural {
        PrefabStructuralOverride::RemoveChild { target, .. } => trim(target),
        PrefabStructuralOverride::ReparentChild {
            target,
            base_parent,
            new_parent,
            ..
        } => {
            trim(target)?;
            if let Some(parent) = base_parent {
                trim(parent)?;
            }
            if let Some(parent) = new_parent {
                trim(parent)?;
            }
            Ok(())
        }
        PrefabStructuralOverride::AttachLocalChild { parent, .. } => trim(parent),
    }
}

fn update_nested_instance(
    document: &mut SceneDocument,
    anchor_actor_id: &str,
    project_root: &Path,
    update: impl FnOnce(&mut PrefabOverrides, &PrefabInstance),
) -> Result<(), String> {
    let target = SceneEntityId::ActorId(anchor_actor_id.to_string());
    let component = document
        .find_entity(&target)
        .and_then(|entity| {
            entity
                .components
                .iter()
                .find(|component| component.type_name == std::any::type_name::<PrefabInstance>())
        })
        .cloned()
        .ok_or_else(|| format!("nested prefab anchor `{anchor_actor_id}` is missing"))?;
    let mut instance: PrefabInstance =
        ron::from_str(&component.ron).map_err(|error| error.to_string())?;
    let mut overrides = decode_prefab_overrides(&instance)?;
    update(&mut overrides, &instance);
    overrides.source_revision = prefab_source_revision(project_root, &instance.source_path).ok();
    instance.overrides_ron = encode_prefab_overrides(&overrides)?;
    document.apply_patch(ScenePatch::SetComponent {
        target,
        component: SceneComponentBlob::from_ron(
            std::any::type_name::<PrefabInstance>(),
            ron::to_string(&instance).map_err(|error| error.to_string())?,
        )?,
    })
}

pub fn remove_prefab_source_child(world: &mut World, member: Entity) -> bool {
    let Some((instance_root, target)) = prefab_member_override_target(world, member) else {
        return false;
    };
    let Some(instance) = world.get::<PrefabInstance>(instance_root).cloned() else {
        return false;
    };
    let old = capture_prefab_edit_state(world, instance_root);
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return false;
    };
    overrides
        .properties
        .retain(|candidate| candidate.target != target);
    overrides
        .components
        .retain(|candidate| candidate.target != target);
    overrides
        .structural
        .retain(|candidate| structural_target(candidate) != &target);
    let base_subtree_revision = current_source_revision(world, &instance).unwrap_or_default();
    overrides
        .structural
        .push(PrefabStructuralOverride::RemoveChild {
            target,
            base_subtree_revision,
        });
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, instance_root, &overrides);
    if let Ok(entity_mut) = world.get_entity_mut(member) {
        entity_mut.despawn();
    }
    record_prefab_edit_state(world, instance_root, old);
    true
}

pub fn reparent_prefab_source_child(
    world: &mut World,
    member: Entity,
    new_parent: Option<Entity>,
) -> bool {
    reparent_prefab_source_children(world, &[member], new_parent)
}

pub fn reparent_prefab_source_children(
    world: &mut World,
    members: &[Entity],
    new_parent: Option<Entity>,
) -> bool {
    let Some(first) = members.first().copied() else {
        return false;
    };
    let Some((instance_root, first_target)) = prefab_member_override_target(world, first) else {
        return false;
    };
    let new_parent_target = match new_parent {
        Some(parent) => {
            let Some((parent_root, parent_target)) = prefab_member_override_target(world, parent)
            else {
                return false;
            };
            if parent_root != instance_root
                || parent_target.instance_chain != first_target.instance_chain
            {
                return false;
            }
            Some(parent_target)
        }
        None => None,
    };
    let runtime_parent = new_parent.unwrap_or(instance_root);
    let mut plans = Vec::with_capacity(members.len());
    for member in members.iter().copied() {
        let Some((owner, target)) = prefab_member_override_target(world, member) else {
            return false;
        };
        if owner != instance_root || target.instance_chain != first_target.instance_chain {
            return false;
        }
        if hierarchy_would_cycle(world, member, runtime_parent) {
            world.resource_mut::<crate::scene_io::SceneIo>().status =
                "Prefab reparent blocked: hierarchy cycle".into();
            return false;
        }
        let new_transform = world
            .get::<GlobalTransform>(member)
            .map(
                |global| match world.get::<GlobalTransform>(runtime_parent) {
                    Some(parent_global) => global.reparented_to(parent_global),
                    None => global.compute_transform(),
                },
            )
            .or_else(|| world.get::<Transform>(member).copied());
        let local_transform_ron = new_transform
            .as_ref()
            .and_then(|transform| ron::to_string(transform).ok());
        plans.push((member, target, new_transform, local_transform_ron));
    }
    let Some(instance) = world.get::<PrefabInstance>(instance_root).cloned() else {
        return false;
    };
    let old = capture_prefab_edit_state(world, instance_root);
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return false;
    };
    let project_root = world
        .get_resource::<crate::project_io::ProjectWorkspace>()
        .map(|workspace| PathBuf::from(&workspace.root));
    for (member, target, _, local_transform_ron) in &plans {
        let existing_base = overrides
            .structural
            .iter()
            .find_map(|candidate| match candidate {
                PrefabStructuralOverride::ReparentChild {
                    target: existing,
                    base_parent,
                    base_transform_ron,
                    ..
                } if existing == target => Some((base_parent.clone(), base_transform_ron.clone())),
                _ => None,
            });
        let (base_parent, base_transform_ron) = existing_base.unwrap_or_else(|| {
            let source_parent = project_root
                .as_ref()
                .and_then(|root| source_parent_for_target(root, &instance, target).ok());
            let base_parent = source_parent.unwrap_or_else(|| {
                world
                    .get::<ChildOf>(*member)
                    .map(ChildOf::parent)
                    .and_then(|parent| prefab_member_override_target(world, parent))
                    .filter(|(root, path)| {
                        *root == instance_root && path.instance_chain == target.instance_chain
                    })
                    .map(|(_, path)| path)
            });
            let base_transform = project_root
                .as_ref()
                .and_then(|root| {
                    source_component_for_target(
                        root,
                        &instance,
                        target,
                        std::any::type_name::<Transform>(),
                    )
                    .ok()
                    .flatten()
                    .map(|component| component.ron)
                })
                .or_else(|| {
                    world
                        .get::<Transform>(*member)
                        .and_then(|transform| ron::to_string(transform).ok())
                });
            (base_parent, base_transform)
        });
        overrides.structural.retain(|candidate| {
            !matches!(candidate, PrefabStructuralOverride::ReparentChild { target: existing, .. } if existing == target)
        });
        if new_parent_target != base_parent {
            overrides
                .structural
                .push(PrefabStructuralOverride::ReparentChild {
                    target: target.clone(),
                    base_parent,
                    new_parent: new_parent_target.clone(),
                    base_transform_ron,
                    local_transform_ron: local_transform_ron.clone(),
                });
        }
    }
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, instance_root, &overrides);
    for (member, _, new_transform, _) in plans {
        if let Ok(mut entity_mut) = world.get_entity_mut(member) {
            entity_mut.insert(ChildOf(runtime_parent));
            if let Some(transform) = new_transform {
                entity_mut.insert(transform);
            }
        }
    }
    record_prefab_edit_state(world, instance_root, old);
    true
}

fn hierarchy_would_cycle(world: &World, entity: Entity, parent: Entity) -> bool {
    let mut current = Some(parent);
    let mut seen = HashSet::new();
    while let Some(candidate) = current {
        if candidate == entity || !seen.insert(candidate) {
            return true;
        }
        current = world.get::<ChildOf>(candidate).map(ChildOf::parent);
    }
    false
}

pub fn revert_child_visibility_overrides(world: &mut World, entity: Entity) {
    let old = capture_prefab_edit_state(world, entity);
    let Some(instance) = world.get::<PrefabInstance>(entity).cloned() else {
        return;
    };
    let Some(mut overrides) = read_overrides_for_edit(world, &instance) else {
        return;
    };
    if let Some(project_root) = world
        .get_resource::<crate::project_io::ProjectWorkspace>()
        .map(|workspace| PathBuf::from(&workspace.root))
    {
        let _ = promote_legacy_visibility_overrides(&project_root, &instance, &mut overrides);
    }
    let component_type = std::any::type_name::<EditorVisibility>();
    let restorations: Vec<_> = overrides
        .components
        .iter()
        .filter(|component| component.component_type == component_type)
        .map(|component| {
            (
                component.target.clone(),
                component.base_component_ron.clone(),
            )
        })
        .collect();
    for (target, base) in restorations {
        let Some(member) = prefab_member_for_target(world, entity, &target) else {
            continue;
        };
        if let Some(value) = base.and_then(|ron| ron::from_str::<EditorVisibility>(&ron).ok()) {
            world.entity_mut(member).insert(value);
        } else {
            world.entity_mut(member).remove::<EditorVisibility>();
        }
    }
    overrides.child_visibility.clear();
    overrides
        .components
        .retain(|component| component.component_type != component_type);
    track_current_source_revision(world, &instance, &mut overrides);
    write_overrides(world, entity, &overrides);
    record_prefab_edit_state(world, entity, old);
}

pub fn apply_stored_overrides(world: &mut World, entity: Entity) {
    let Some(instance) = world.get::<PrefabInstance>(entity).cloned() else {
        return;
    };
    let Ok(overrides) = try_read_overrides(&instance) else {
        return;
    };
    for child in prefab_instance_members(world, entity) {
        let is_direct = prefab_member_override_target(world, child)
            .is_some_and(|(root, target)| root == entity && target.instance_chain.is_empty());
        if !is_direct {
            continue;
        }
        let Some(actor_id) = world.get::<ActorId>(child).map(|id| id.0.as_str()) else {
            continue;
        };
        let legacy_name = world.get::<Name>(child).map(|name| name.as_str());
        if let Some(&visible) = overrides
            .child_visibility
            .get(actor_id)
            .or_else(|| legacy_name.and_then(|name| overrides.child_visibility.get(name)))
        {
            if let Ok(mut entity_mut) = world.get_entity_mut(child) {
                entity_mut.insert(EditorVisibility { visible });
            }
        }
    }
}

fn prefab_member_for_target(
    world: &mut World,
    instance_root: Entity,
    target: &PrefabActorPath,
) -> Option<Entity> {
    let candidates: Vec<Entity> = world
        .query_filtered::<Entity, With<HydratedPrefabMember>>()
        .iter(world)
        .collect();
    candidates.into_iter().find(|candidate| {
        prefab_member_override_target(world, *candidate).is_some_and(|(root, candidate_target)| {
            root == instance_root && candidate_target == *target
        })
    })
}

pub fn invalidate_prefab_health_on_ready(
    ready: On<WorldInstanceReady>,
    mut health_cache: Option<ResMut<PrefabHealthCache>>,
) {
    if let Some(cache) = health_cache.as_deref_mut() {
        cache.entries.remove(&ready.entity);
    }
}

pub fn guard_prefab_reimports(
    mut events: MessageReader<AssetEvent<DynamicWorld>>,
    mut failures: MessageReader<bevy::asset::AssetLoadFailedEvent<DynamicWorld>>,
    roots: Query<(Entity, &DynamicWorldRoot, &PrefabInstance)>,
    workspace: Res<crate::project_io::ProjectWorkspace>,
    mut commands: Commands,
) {
    let mut changed: std::collections::HashSet<_> = events
        .read()
        .filter_map(|event| match event {
            AssetEvent::Modified { id }
            | AssetEvent::Removed { id }
            | AssetEvent::LoadedWithDependencies { id } => Some(*id),
            _ => None,
        })
        .collect();
    let failed: HashMap<_, _> = failures
        .read()
        .map(|failure| {
            (
                failure.id,
                format!("could not load {}: {}", failure.path, failure.error),
            )
        })
        .collect();
    changed.extend(failed.keys().copied());
    if changed.is_empty() {
        return;
    }
    let project_root = Path::new(&workspace.root);
    for (entity, dynamic_root, instance) in &roots {
        if !changed.contains(&dynamic_root.id()) {
            continue;
        }
        if let Some(reason) = failed.get(&dynamic_root.id()) {
            let reason = reason.clone();
            commands.queue(move |world: &mut World| {
                block_prefab_hydration(world, entity, reason);
            });
            continue;
        }
        match scene::validate_prefab_graph(&project_root.join(&instance.source_path), project_root)
        {
            Ok(()) => {
                commands
                    .entity(entity)
                    .remove::<shared::PrefabHydrationBlocked>();
            }
            Err(reason) => {
                commands.queue(move |world: &mut World| {
                    block_prefab_hydration(world, entity, reason);
                });
            }
        }
    }
}

fn block_prefab_hydration(world: &mut World, entity: Entity, reason: String) {
    let mut entity = entity;
    let mut seen = HashSet::new();
    while seen.insert(entity) {
        let Some(owner) = world
            .get::<HydratedPrefabMember>(entity)
            .map(|member| member.instance_root)
        else {
            break;
        };
        entity = owner;
    }

    // Nested instances are owned by generated members of their parent instance. Walk that
    // ownership graph so an invalid reload cannot leave an unregistered nested tree behind.
    let ownership: Vec<(Entity, Entity)> = world
        .query::<(Entity, &HydratedPrefabMember)>()
        .iter(world)
        .map(|(member, owner)| (member, owner.instance_root))
        .collect();
    let mut owned_roots = HashSet::from([entity]);
    let mut changed = true;
    while changed {
        changed = false;
        for (member, owner) in &ownership {
            if owned_roots.contains(owner) && owned_roots.insert(*member) {
                changed = true;
            }
        }
    }
    owned_roots.remove(&entity);
    for member in owned_roots {
        if let Ok(entity_mut) = world.get_entity_mut(member) {
            entity_mut.despawn();
        }
    }

    if let Ok(mut root) = world.get_entity_mut(entity) {
        // Remove the asset root while WorldInstance is still present so Bevy unregisters the
        // instance, then remove the stale ID before a repaired source is hydrated.
        root.insert(shared::PrefabHydrationBlocked { reason })
            .remove::<HydratedPrefabReady>()
            .remove::<DynamicWorldRoot>()
            .remove::<WorldInstance>();
    }
    if let Some(mut cache) = world.get_resource_mut::<PrefabHealthCache>() {
        cache.entries.remove(&entity);
    }
}

fn retry_prefab_source(world: &mut World, entity: Entity) {
    let Some(instance) = world.get::<PrefabInstance>(entity).cloned() else {
        return;
    };
    let Some(project_root) = world
        .get_resource::<crate::project_io::ProjectWorkspace>()
        .map(|workspace| PathBuf::from(&workspace.root))
    else {
        return;
    };
    if let Err(error) =
        scene::validate_prefab_graph(&project_root.join(&instance.source_path), &project_root)
    {
        world.resource_mut::<crate::scene_io::SceneIo>().status =
            format!("Prefab retry failed: {error}");
        return;
    }

    block_prefab_hydration(world, entity, "restarting prefab hydration".to_string());
    if let Ok(mut root) = world.get_entity_mut(entity) {
        root.remove::<shared::PrefabHydrationBlocked>().insert((
            instance.clone(),
            PrefabRef::new(instance.source_path.clone()),
        ));
    }
    world.resource_mut::<crate::scene_io::SceneIo>().status =
        "Prefab source queued for reload".into();
}

pub(crate) fn reload_prefab_source_after_file_conflict(
    world: &mut World,
    instance_root: Entity,
) -> Result<String, String> {
    if world.get::<PrefabInstance>(instance_root).is_none() {
        return Err("the prefab instance no longer exists".into());
    }
    retry_prefab_source(world, instance_root);
    let status = world.resource::<crate::scene_io::SceneIo>().status.clone();
    if status.starts_with("Prefab retry failed") {
        Err(status)
    } else {
        Ok(status)
    }
}

pub fn prefab_member_inspector_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) {
    let Some((instance_root, target)) = prefab_member_override_target(world, entity) else {
        ui.label(egui::RichText::new("Prefab target unavailable").color(ERROR));
        return;
    };
    let Some(instance) = world.get::<PrefabInstance>(instance_root).cloned() else {
        return;
    };
    let overrides = match try_read_overrides(&instance) {
        Ok(overrides) => overrides,
        Err(error) => {
            ui.label(egui::RichText::new(error).color(ERROR));
            ui.label("Repair the prefab instance from its root inspector before editing members.");
            return;
        }
    };
    let transform_type = std::any::type_name::<Transform>();
    let material_type = std::any::type_name::<MaterialDesc>();
    let property_active = |component_type: &str, property_path: &str| {
        overrides.properties.iter().any(|candidate| {
            candidate.target == target
                && candidate.component_type == component_type
                && candidate.property_path == property_path
        })
    };
    let component_active = |component_type: &str| {
        overrides.components.iter().any(|candidate| {
            candidate.target == target && candidate.component_type == component_type
        })
    };

    ui.horizontal_wrapped(|ui| {
        ui.label(
            egui::RichText::new("Override layer")
                .strong()
                .color(WARNING),
        );
        ui.label(
            egui::RichText::new(format!("{} / {}", instance.source_path, target.actor_id))
                .color(TEXT_MUTED),
        );
    });
    ui.add_space(4.0);

    if let Some(mut transform) = world.get::<Transform>(entity).copied() {
        ui.collapsing("Transform", |ui| {
            let old_translation = transform.translation;
            ui.horizontal_wrapped(|ui| {
                ui.label("Position");
                ui.add(egui::DragValue::new(&mut transform.translation.x).speed(0.1));
                ui.add(egui::DragValue::new(&mut transform.translation.y).speed(0.1));
                ui.add(egui::DragValue::new(&mut transform.translation.z).speed(0.1));
                if property_active(transform_type, "translation")
                    && ui.small_button("Revert").clicked()
                {
                    revert_prefab_override_scope(
                        world,
                        entity,
                        Some(transform_type),
                        Some("translation"),
                    );
                }
                if property_active(transform_type, "translation")
                    && ui.small_button("Apply to source").clicked()
                {
                    apply_prefab_member_scope_to_source(
                        world,
                        entity,
                        Some(transform_type),
                        Some("translation"),
                    );
                }
            });
            if transform.translation != old_translation {
                set_prefab_transform_property_override(world, entity, "translation", transform);
            }

            let old_rotation = transform.rotation;
            let (yaw, pitch, roll) = transform.rotation.to_euler(EulerRot::YXZ);
            let mut degrees = Vec3::new(pitch.to_degrees(), yaw.to_degrees(), roll.to_degrees());
            ui.horizontal_wrapped(|ui| {
                ui.label("Rotation");
                ui.add(egui::DragValue::new(&mut degrees.x).speed(0.5));
                ui.add(egui::DragValue::new(&mut degrees.y).speed(0.5));
                ui.add(egui::DragValue::new(&mut degrees.z).speed(0.5));
                if property_active(transform_type, "rotation")
                    && ui.small_button("Revert").clicked()
                {
                    revert_prefab_override_scope(
                        world,
                        entity,
                        Some(transform_type),
                        Some("rotation"),
                    );
                }
                if property_active(transform_type, "rotation")
                    && ui.small_button("Apply to source").clicked()
                {
                    apply_prefab_member_scope_to_source(
                        world,
                        entity,
                        Some(transform_type),
                        Some("rotation"),
                    );
                }
            });
            transform.rotation = Quat::from_euler(
                EulerRot::YXZ,
                degrees.y.to_radians(),
                degrees.x.to_radians(),
                degrees.z.to_radians(),
            );
            if transform.rotation != old_rotation {
                set_prefab_transform_property_override(world, entity, "rotation", transform);
            }

            let old_scale = transform.scale;
            ui.horizontal_wrapped(|ui| {
                ui.label("Scale");
                ui.add(egui::DragValue::new(&mut transform.scale.x).speed(0.05));
                ui.add(egui::DragValue::new(&mut transform.scale.y).speed(0.05));
                ui.add(egui::DragValue::new(&mut transform.scale.z).speed(0.05));
                if property_active(transform_type, "scale") && ui.small_button("Revert").clicked() {
                    revert_prefab_override_scope(
                        world,
                        entity,
                        Some(transform_type),
                        Some("scale"),
                    );
                }
                if property_active(transform_type, "scale")
                    && ui.small_button("Apply to source").clicked()
                {
                    apply_prefab_member_scope_to_source(
                        world,
                        entity,
                        Some(transform_type),
                        Some("scale"),
                    );
                }
            });
            if transform.scale != old_scale {
                set_prefab_transform_property_override(world, entity, "scale", transform);
            }
        });
    }

    ui.collapsing("Material", |ui| {
        if let Some(mut material) = world.get::<MaterialDesc>(entity).cloned() {
            let mut color = [
                material.base_color.r,
                material.base_color.g,
                material.base_color.b,
                material.base_color.a,
            ];
            ui.horizontal_wrapped(|ui| {
                ui.label("Base color");
                if ui.color_edit_button_rgba_unmultiplied(&mut color).changed() {
                    material.base_color.r = color[0];
                    material.base_color.g = color[1];
                    material.base_color.b = color[2];
                    material.base_color.a = color[3];
                    set_prefab_material_property_override(
                        world,
                        entity,
                        "base_color",
                        material.clone(),
                    );
                }
                if property_active(material_type, "base_color")
                    && ui.small_button("Revert").clicked()
                {
                    revert_prefab_override_scope(
                        world,
                        entity,
                        Some(material_type),
                        Some("base_color"),
                    );
                }
                if property_active(material_type, "base_color")
                    && ui.small_button("Apply to source").clicked()
                {
                    apply_prefab_member_scope_to_source(
                        world,
                        entity,
                        Some(material_type),
                        Some("base_color"),
                    );
                }
            });

            let old_metallic = material.metallic;
            ui.horizontal_wrapped(|ui| {
                ui.label("Metallic");
                ui.add(egui::Slider::new(&mut material.metallic, 0.0..=1.0));
                if property_active(material_type, "metallic") && ui.small_button("Revert").clicked()
                {
                    revert_prefab_override_scope(
                        world,
                        entity,
                        Some(material_type),
                        Some("metallic"),
                    );
                }
                if property_active(material_type, "metallic")
                    && ui.small_button("Apply to source").clicked()
                {
                    apply_prefab_member_scope_to_source(
                        world,
                        entity,
                        Some(material_type),
                        Some("metallic"),
                    );
                }
            });
            if material.metallic != old_metallic {
                set_prefab_material_property_override(world, entity, "metallic", material.clone());
            }

            let old_roughness = material.roughness;
            ui.horizontal_wrapped(|ui| {
                ui.label("Roughness");
                ui.add(egui::Slider::new(&mut material.roughness, 0.0..=1.0));
                if property_active(material_type, "roughness")
                    && ui.small_button("Revert").clicked()
                {
                    revert_prefab_override_scope(
                        world,
                        entity,
                        Some(material_type),
                        Some("roughness"),
                    );
                }
                if property_active(material_type, "roughness")
                    && ui.small_button("Apply to source").clicked()
                {
                    apply_prefab_member_scope_to_source(
                        world,
                        entity,
                        Some(material_type),
                        Some("roughness"),
                    );
                }
            });
            if material.roughness != old_roughness {
                set_prefab_material_property_override(world, entity, "roughness", material);
            }

            if ui.small_button("Remove component").clicked() {
                remove_prefab_component_override::<MaterialDesc>(world, entity);
            }
            if overrides.properties.iter().any(|candidate| {
                candidate.target == target && candidate.component_type == material_type
            }) || component_active(material_type)
            {
                ui.horizontal_wrapped(|ui| {
                    if ui.small_button("Apply component to source").clicked() {
                        apply_prefab_member_scope_to_source(
                            world,
                            entity,
                            Some(material_type),
                            None,
                        );
                    }
                    if ui.small_button("Revert component overrides").clicked() {
                        revert_prefab_override_scope(world, entity, Some(material_type), None);
                    }
                });
            }
        } else if component_active(material_type) {
            ui.horizontal_wrapped(|ui| {
                if ui.button("Apply removal to source").clicked() {
                    apply_prefab_member_scope_to_source(world, entity, Some(material_type), None);
                }
                if ui.button("Restore source component").clicked() {
                    revert_prefab_override_scope(world, entity, Some(material_type), None);
                }
            });
        } else if ui.button("Add material component").clicked() {
            set_prefab_component_override(world, entity, MaterialDesc::default());
        }
    });

    ui.separator();
    ui.horizontal_wrapped(|ui| {
        if ui.button("Apply actor to source").clicked() {
            apply_prefab_member_scope_to_source(world, entity, None, None);
        }
        if ui.button("Revert actor overrides").clicked() {
            revert_prefab_override_scope(world, entity, None, None);
        }
        if ui
            .add(egui::Button::new("Remove source actor"))
            .on_hover_text("Creates an undoable structural override")
            .clicked()
        {
            remove_prefab_source_child(world, entity);
        }
    });
}

pub fn prefab_instance_inspector_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) {
    let Some(instance) = world.get::<PrefabInstance>(entity).cloned() else {
        return;
    };
    let parsed_overrides = try_read_overrides(&instance);
    let overrides_valid = parsed_overrides.is_ok();
    let overrides = parsed_overrides.clone().unwrap_or_default();
    let health = parsed_overrides.map_or_else(PrefabHealth::InvalidOverrides, |_| {
        prefab_health(world, entity, &instance, &overrides)
    });

    let mut options = ComponentCardOptions::fixed(
        shared::COMPONENT_PREFAB_INSTANCE,
        "Prefab Instance",
        icons::PACKAGE,
    );
    options.resettable = false;
    let card = component_card_context(world, entity, options);
    let card_response = component_card(ui, &card, |ui| {
        property_row(ui, "Asset ID", |ui| {
            ui.add(egui::Label::new(instance.asset_id.as_str()).truncate());
        });
        property_row(ui, "Source", |ui| {
            ui.add(egui::Label::new(instance.source_path.as_str()).truncate());
        });
        property_row(ui, "Status", |ui| {
            let (label, color, detail) = match &health {
                PrefabHealth::Ready => ("Ready", SUCCESS, None),
                PrefabHealth::Loading => (
                    "Loading source",
                    TEXT_MUTED,
                    Some("Override targets will be available when the prefab finishes loading"),
                ),
                PrefabHealth::Untracked => (
                    "Base not tracked",
                    WARNING,
                    Some("Record the current source revision before production use"),
                ),
                PrefabHealth::ChangedBase => (
                    "Base changed",
                    WARNING,
                    Some("The source file changed after these overrides were recorded"),
                ),
                PrefabHealth::Conflict(stale) => (
                    "Override conflict",
                    ERROR,
                    Some(if stale.len() == 1 {
                        "One override conflicts with the current source value, hierarchy, or target"
                    } else {
                        "Multiple overrides conflict with current source values, hierarchy, or targets"
                    }),
                ),
                PrefabHealth::Broken(error) => ("Broken source", ERROR, Some(error.as_str())),
                PrefabHealth::InvalidOverrides(error) => {
                    ("Invalid overrides", ERROR, Some(error.as_str()))
                }
            };
            let response = ui.label(egui::RichText::new(label).strong().color(color));
            if let Some(detail) = detail {
                response.on_hover_text(detail);
            }
        });

        ui.horizontal_wrapped(|ui| match &health {
            PrefabHealth::Ready => {
                if ui.small_button("Reload source").clicked() {
                    world.entity_mut(entity).insert((
                        instance.clone(),
                        PrefabRef::new(instance.source_path.clone()),
                    ));
                }
                if ui.small_button("Relink...").clicked() {
                    relink_prefab_source(world, entity);
                }
            }
            PrefabHealth::Untracked | PrefabHealth::ChangedBase => {
                if ui.small_button("Accept current base").clicked() {
                    accept_current_source(world, entity);
                }
                if ui.small_button("Relink...").clicked() {
                    relink_prefab_source(world, entity);
                }
            }
            PrefabHealth::Conflict(stale) => {
                ui.label(
                    egui::RichText::new(format!("{} conflicts", stale.len())).color(TEXT_MUTED),
                );
                if ui.small_button("Keep overrides").clicked() {
                    keep_prefab_overrides(world, entity);
                }
                if ui.small_button("Take source").clicked() {
                    take_source_for_conflicts(world, entity);
                }
                if ui.small_button("Remove missing targets").clicked() {
                    remove_stale_visibility_overrides(world, entity);
                }
                if ui.small_button("Relink...").clicked() {
                    relink_prefab_source(world, entity);
                }
            }
            PrefabHealth::Broken(_) => {
                if ui.small_button("Retry source").clicked() {
                    retry_prefab_source(world, entity);
                }
                if ui.small_button("Relink source...").clicked() {
                    relink_prefab_source(world, entity);
                }
            }
            PrefabHealth::Loading => {
                ui.spinner();
            }
            PrefabHealth::InvalidOverrides(_) => {
                if ui.small_button("Discard invalid data").clicked() {
                    discard_invalid_overrides(world, entity);
                }
            }
        });

        ui.add_enabled_ui(overrides_valid, |ui| {
            let operation_count = overrides.properties.len()
                + overrides.components.len()
                + overrides.structural.len()
                + overrides.child_visibility.len();
            ui.horizontal_wrapped(|ui| {
                ui.label(
                    egui::RichText::new(format!("{operation_count} override operations"))
                        .color(TEXT_MUTED),
                );
                if ui
                    .add_enabled(
                        operation_count > 0,
                        egui::Button::new("Apply all to source"),
                    )
                    .clicked()
                {
                    apply_prefab_instance_overrides_to_source(world, entity);
                }
                if ui
                    .add_enabled(operation_count > 0, egui::Button::new("Revert all"))
                    .clicked()
                {
                    revert_prefab_instance_overrides(world, entity);
                }
            });
            if !overrides.structural.is_empty() {
                let mut structural_action = None;
                ui.collapsing("Structural overrides", |ui| {
                    for structural in &overrides.structural {
                        let target = structural_target(structural).clone();
                        let operation = match structural {
                            PrefabStructuralOverride::RemoveChild { .. } => "Removed",
                            PrefabStructuralOverride::ReparentChild { .. } => "Reparented",
                            PrefabStructuralOverride::AttachLocalChild { .. } => "Attached",
                        };
                        ui.horizontal_wrapped(|ui| {
                            ui.label(
                                egui::RichText::new(format!(
                                    "{operation} {}",
                                    actor_path_key(&target)
                                ))
                                .color(TEXT_MUTED),
                            );
                            if ui.small_button("Apply actor").clicked() {
                                structural_action = Some((true, target.clone()));
                            }
                            if ui.small_button("Revert actor").clicked() {
                                structural_action = Some((false, target));
                            }
                        });
                    }
                });
                if let Some((apply, target)) = structural_action {
                    if apply {
                        apply_prefab_actor_path_to_source(world, entity, &target);
                    } else {
                        revert_prefab_actor_path(world, entity, &target);
                    }
                }
            }
            ui.collapsing("Visibility overrides", |ui| {
                let visibility_type = std::any::type_name::<EditorVisibility>();
                let active = !overrides.child_visibility.is_empty()
                    || overrides
                        .components
                        .iter()
                        .any(|component| component.component_type == visibility_type);
                if active {
                    ui.label(egui::RichText::new("Overrides use stable nested actor paths").weak());
                }
                for child in prefab_instance_members(world, entity) {
                    let Some((_, target)) = prefab_member_override_target(world, child) else {
                        continue;
                    };
                    let name = world
                        .get::<Name>(child)
                        .map(|n| n.to_string())
                        .unwrap_or_else(|| format!("{child:?}"));
                    let mut visible = overrides
                        .components
                        .iter()
                        .find(|component| {
                            component.target == target
                                && component.component_type == visibility_type
                        })
                        .and_then(|component| component.value_component_ron.as_deref())
                        .and_then(|ron| ron::from_str::<EditorVisibility>(ron).ok())
                        .map(|visibility| visibility.visible)
                        .or_else(|| {
                            target
                                .instance_chain
                                .is_empty()
                                .then(|| overrides.child_visibility.get(&target.actor_id).copied())
                                .flatten()
                        })
                        .unwrap_or_else(|| {
                            world
                                .get::<EditorVisibility>(child)
                                .map(|visibility| visibility.visible)
                                .unwrap_or(true)
                        });
                    ui.horizontal_wrapped(|ui| {
                        ui.add(
                            egui::Label::new(format!("{name} ({})", actor_path_key(&target)))
                                .truncate(),
                        );
                        if ui.checkbox(&mut visible, "Visible").changed() {
                            set_child_visibility_override(world, entity, child, visible);
                        }
                    });
                }
                ui.horizontal_wrapped(|ui| {
                    if ui
                        .add_enabled(active, egui::Button::new("Revert all"))
                        .clicked()
                    {
                        revert_child_visibility_overrides(world, entity);
                    }
                });
            });
        });

        ui.horizontal_wrapped(|ui| {
            if ui.button("Create Variant...").clicked() {
                world.resource_mut::<crate::scene_io::SceneIo>().request =
                    Some(crate::scene_io::SceneIoRequest::SaveSelectionAsPrefab);
            }
            if ui
                .add_enabled(
                    world.get::<HydratedPrefabReady>(entity).is_some(),
                    egui::Button::new("Unpack Layer"),
                )
                .on_disabled_hover_text("Prefab source has not finished loading")
                .on_hover_text("Make this prefab layer local while preserving nested prefab links")
                .clicked()
            {
                crate::history::unpack_prefab_instance(world, entity);
            }
            if ui
                .add_enabled(
                    world.get::<HydratedPrefabReady>(entity).is_some(),
                    egui::Button::new("Convert to Local"),
                )
                .on_disabled_hover_text("Prefab source has not finished loading")
                .on_hover_text(
                    "Recursively replace this instance and all nested links with local actors",
                )
                .clicked()
            {
                crate::history::convert_prefab_completely_to_local(world, entity);
            }
        });
    });
    apply_component_card_response(world, entity, card_response);
}

fn prefab_instance_members(world: &mut World, root: Entity) -> Vec<Entity> {
    let candidates: Vec<Entity> = world
        .query_filtered::<Entity, With<HydratedPrefabMember>>()
        .iter(world)
        .collect();
    let mut members: Vec<_> = candidates
        .into_iter()
        .filter_map(|candidate| {
            prefab_member_override_target(world, candidate)
                .filter(|(owner, _)| *owner == root)
                .map(|(_, target)| (actor_path_key(&target), candidate))
        })
        .collect();
    members.sort_by(|(left, _), (right, _)| left.cmp(right));
    members
        .into_iter()
        .map(|(_, candidate)| candidate)
        .collect()
}

pub fn list_prefab_dependencies(path: &str) -> Vec<String> {
    vec![path.to_string()]
}

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

    fn prefab_source(actor_id: &str, material: Option<&MaterialDesc>) -> String {
        let material = material.map(|material| {
            format!(
                ",\"{}\":{}",
                std::any::type_name::<MaterialDesc>(),
                ron::to_string(material).unwrap()
            )
        });
        format!(
            "(schema_version:2,resources:{{}},entities:{{1:(components:{{\"shared::components::LevelObject\":(),\"shared::components::ActorId\":(\"{actor_id}\"),\"shared::components::ActorKind\":Empty,\"{}\":{}{} }})}})",
            std::any::type_name::<Transform>(),
            ron::to_string(&Transform::default()).unwrap(),
            material.unwrap_or_default(),
        )
    }

    fn hierarchy_prefab_source(child_transform: Transform) -> String {
        format!(
            "(schema_version:2,resources:{{}},entities:{{1:(components:{{\"shared::components::LevelObject\":(),\"shared::components::ActorId\":(\"parent\"),\"shared::components::ActorKind\":Empty,\"{}\":{}}}),2:(components:{{\"shared::components::LevelObject\":(),\"shared::components::ActorId\":(\"child\"),\"shared::components::ActorKind\":Empty,\"{}\":{},\"bevy_ecs::hierarchy::ChildOf\":(1)}})}})",
            std::any::type_name::<Transform>(),
            ron::to_string(&Transform::default()).unwrap(),
            std::any::type_name::<Transform>(),
            ron::to_string(&child_transform).unwrap(),
        )
    }

    fn prefab_test_world(project_root: &Path) -> World {
        let mut world = World::new();
        world.init_resource::<crate::scene_io::SceneIo>();
        world.init_resource::<crate::history::EditorHistory>();
        world.insert_resource(crate::project_io::ProjectWorkspace {
            root: project_root.display().to_string(),
            ..Default::default()
        });
        world
    }

    #[test]
    fn empty_overrides_roundtrip() {
        let instance = PrefabInstance::new("id", "assets/prefabs/test.scn.ron");
        assert_eq!(read_overrides(&instance), PrefabOverrides::default());
    }

    #[test]
    fn collaboration_reload_restarts_prefab_hydration_without_dropping_overrides() {
        let root = std::env::temp_dir().join(format!(
            "blacksite-prefab-collaboration-reload-{}",
            uuid::Uuid::new_v4()
        ));
        let source_path = root.join("assets/prefabs/base.scn.ron");
        std::fs::create_dir_all(source_path.parent().unwrap()).unwrap();
        std::fs::write(&source_path, prefab_source("mesh", None)).unwrap();
        let mut world = prefab_test_world(&root);
        let mut instance = PrefabInstance::new("base", "assets/prefabs/base.scn.ron");
        instance.overrides_ron = encode_prefab_overrides(&PrefabOverrides {
            source_revision: Some("previous".into()),
            ..Default::default()
        })
        .unwrap();
        let instance_root = world.spawn((instance.clone(), HydratedPrefabReady)).id();

        let status = reload_prefab_source_after_file_conflict(&mut world, instance_root).unwrap();

        assert_eq!(status, "Prefab source queued for reload");
        assert_eq!(
            world
                .get::<PrefabInstance>(instance_root)
                .map(|current| &current.overrides_ron),
            Some(&instance.overrides_ron)
        );
        assert!(world.get::<HydratedPrefabReady>(instance_root).is_none());
        assert!(world.get::<PrefabRef>(instance_root).is_some());
        std::fs::remove_dir_all(root).unwrap();
    }

    #[test]
    fn nested_unpack_fold_is_transactional_when_a_later_link_is_invalid() {
        let mut world = World::new();
        world.init_resource::<crate::scene_io::SceneIo>();
        let material = MaterialDesc::default();
        let mut outer = PrefabInstance::new("outer", "assets/prefabs/outer.scn.ron");
        outer.overrides_ron = encode_prefab_overrides(&PrefabOverrides {
            properties: ["first", "second"]
                .into_iter()
                .map(|anchor| PrefabPropertyOverride {
                    target: PrefabActorPath {
                        instance_chain: vec![anchor.to_string()],
                        actor_id: "mesh".to_string(),
                    },
                    component_type: std::any::type_name::<MaterialDesc>().to_string(),
                    property_path: "roughness".to_string(),
                    base_component_ron: ron::to_string(&material).unwrap(),
                    value_component_ron: ron::to_string(&material).unwrap(),
                })
                .collect(),
            ..default()
        })
        .unwrap();
        let root = world.spawn(outer).id();
        let first = world
            .spawn((
                ActorId::new("first"),
                PrefabInstance::new("first", "assets/prefabs/first.scn.ron"),
                HydratedPrefabMember {
                    instance_root: root,
                },
            ))
            .id();
        let mut invalid = PrefabInstance::new("second", "assets/prefabs/second.scn.ron");
        invalid.overrides_ron = Some("invalid".to_string());
        world.spawn((
            ActorId::new("second"),
            invalid,
            HydratedPrefabMember {
                instance_root: root,
            },
        ));

        assert!(fold_nested_overrides_for_unpack(&mut world, root).is_err());
        assert!(world
            .get::<PrefabInstance>(first)
            .unwrap()
            .overrides_ron
            .is_none());
    }

    #[test]
    fn property_apply_to_source_is_transactional_and_history_guarded() {
        let root = std::env::temp_dir().join(format!(
            "blacksite-prefab-source-apply-{}",
            uuid::Uuid::new_v4()
        ));
        let prefabs = root.join("assets/prefabs");
        std::fs::create_dir_all(&prefabs).unwrap();
        std::fs::write(root.join("assets/project.ron"), "()").unwrap();
        let source_path = prefabs.join("base.scn.ron");
        let base_material = MaterialDesc::default();
        let before_source = prefab_source("mesh", Some(&base_material));
        std::fs::write(&source_path, &before_source).unwrap();

        let mut world = prefab_test_world(&root);
        let instance_root = world
            .spawn(PrefabInstance::new("base", "assets/prefabs/base.scn.ron"))
            .id();
        let member = world
            .spawn((
                ActorId::new("mesh"),
                base_material.clone(),
                HydratedPrefabMember { instance_root },
                ChildOf(instance_root),
            ))
            .id();
        let mut overridden = base_material.clone();
        overridden.roughness = 0.2;
        set_prefab_material_property_override(&mut world, member, "roughness", overridden);

        apply_prefab_member_scope_to_source(
            &mut world,
            member,
            Some(std::any::type_name::<MaterialDesc>()),
            Some("roughness"),
        );

        let applied_source = std::fs::read_to_string(&source_path).unwrap();
        let document = SceneDocument::from_ron_text(&applied_source).unwrap();
        let material: MaterialDesc = ron::from_str(
            &document.entities[0]
                .components
                .iter()
                .find(|component| component.type_name == std::any::type_name::<MaterialDesc>())
                .unwrap()
                .ron,
        )
        .unwrap();
        assert_eq!(material.roughness, 0.2);
        assert!(
            read_overrides(world.get::<PrefabInstance>(instance_root).unwrap())
                .properties
                .is_empty()
        );
        assert_eq!(
            world
                .resource::<crate::history::EditorHistory>()
                .undo_depth(),
            2
        );

        crate::history::apply_command_undo(&mut world);
        assert_eq!(
            std::fs::read_to_string(&source_path).unwrap(),
            before_source
        );
        assert_eq!(
            read_overrides(world.get::<PrefabInstance>(instance_root).unwrap())
                .properties
                .len(),
            1
        );
        crate::history::apply_command_redo(&mut world);
        assert_eq!(
            std::fs::read_to_string(&source_path).unwrap(),
            applied_source
        );

        std::fs::write(&source_path, format!("{applied_source}\n")).unwrap();
        let undo_depth = world
            .resource::<crate::history::EditorHistory>()
            .undo_depth();
        crate::history::apply_command_undo(&mut world);
        assert_eq!(
            world
                .resource::<crate::history::EditorHistory>()
                .undo_depth(),
            undo_depth
        );
        assert!(world
            .resource::<crate::history::EditorHistory>()
            .status
            .contains("changed outside"));
        std::fs::remove_dir_all(root).unwrap();
    }

    #[test]
    fn added_component_field_apply_commits_prerequisite_and_round_trips() {
        let root = std::env::temp_dir().join(format!(
            "blacksite-prefab-component-apply-{}",
            uuid::Uuid::new_v4()
        ));
        let prefabs = root.join("assets/prefabs");
        std::fs::create_dir_all(&prefabs).unwrap();
        std::fs::write(root.join("assets/project.ron"), "()").unwrap();
        let source_path = prefabs.join("base.scn.ron");
        let before_source = prefab_source("mesh", None);
        std::fs::write(&source_path, &before_source).unwrap();

        let mut world = prefab_test_world(&root);
        let instance_root = world
            .spawn(PrefabInstance::new("base", "assets/prefabs/base.scn.ron"))
            .id();
        let member = world
            .spawn((
                ActorId::new("mesh"),
                HydratedPrefabMember { instance_root },
                ChildOf(instance_root),
            ))
            .id();
        let base = MaterialDesc::default();
        set_prefab_component_override(&mut world, member, base.clone());
        let mut edited = base;
        edited.roughness = 0.17;
        set_prefab_material_property_override(&mut world, member, "roughness", edited);

        apply_prefab_member_scope_to_source(
            &mut world,
            member,
            Some(std::any::type_name::<MaterialDesc>()),
            Some("roughness"),
        );

        let after_source = std::fs::read_to_string(&source_path).unwrap();
        let document = SceneDocument::from_ron_text(&after_source).unwrap();
        let material: MaterialDesc = ron::from_str(
            &document
                .find_entity(&SceneEntityId::ActorId("mesh".into()))
                .unwrap()
                .components
                .iter()
                .find(|component| component.type_name == std::any::type_name::<MaterialDesc>())
                .unwrap()
                .ron,
        )
        .unwrap();
        assert_eq!(material.roughness, 0.17);
        let applied = read_overrides(world.get::<PrefabInstance>(instance_root).unwrap());
        assert!(applied.components.is_empty());
        assert!(applied.properties.is_empty());

        crate::history::apply_command_undo(&mut world);
        assert_eq!(
            std::fs::read_to_string(&source_path).unwrap(),
            before_source
        );
        let restored = read_overrides(world.get::<PrefabInstance>(instance_root).unwrap());
        assert_eq!(restored.components.len(), 1);
        assert_eq!(restored.properties.len(), 1);

        crate::history::apply_command_redo(&mut world);
        assert_eq!(std::fs::read_to_string(&source_path).unwrap(), after_source);
        std::fs::remove_dir_all(root).unwrap();
    }

    #[test]
    fn removed_component_apply_and_undo_preserve_source_bytes() {
        let root = std::env::temp_dir().join(format!(
            "blacksite-prefab-component-remove-{}",
            uuid::Uuid::new_v4()
        ));
        let prefabs = root.join("assets/prefabs");
        std::fs::create_dir_all(&prefabs).unwrap();
        std::fs::write(root.join("assets/project.ron"), "()").unwrap();
        let source_path = prefabs.join("base.scn.ron");
        let material = MaterialDesc::default();
        let before_source = prefab_source("mesh", Some(&material));
        std::fs::write(&source_path, &before_source).unwrap();

        let mut world = prefab_test_world(&root);
        let instance_root = world
            .spawn(PrefabInstance::new("base", "assets/prefabs/base.scn.ron"))
            .id();
        let member = world
            .spawn((
                ActorId::new("mesh"),
                material,
                HydratedPrefabMember { instance_root },
                ChildOf(instance_root),
            ))
            .id();
        remove_prefab_component_override::<MaterialDesc>(&mut world, member);

        apply_prefab_member_scope_to_source(
            &mut world,
            member,
            Some(std::any::type_name::<MaterialDesc>()),
            None,
        );

        let after_source = std::fs::read_to_string(&source_path).unwrap();
        let document = SceneDocument::from_ron_text(&after_source).unwrap();
        assert!(document.entities[0]
            .components
            .iter()
            .all(|component| component.type_name != std::any::type_name::<MaterialDesc>()));
        assert!(
            read_overrides(world.get::<PrefabInstance>(instance_root).unwrap())
                .components
                .is_empty()
        );

        crate::history::apply_command_undo(&mut world);
        assert_eq!(
            std::fs::read_to_string(&source_path).unwrap(),
            before_source
        );
        let restored = read_overrides(world.get::<PrefabInstance>(instance_root).unwrap());
        assert_eq!(restored.components.len(), 1);
        assert!(restored.components[0].value_component_ron.is_none());
        std::fs::remove_dir_all(root).unwrap();
    }

    #[test]
    fn structural_remove_apply_is_exact_byte_undoable() {
        let root = std::env::temp_dir().join(format!(
            "blacksite-prefab-structural-apply-{}",
            uuid::Uuid::new_v4()
        ));
        let prefabs = root.join("assets/prefabs");
        std::fs::create_dir_all(&prefabs).unwrap();
        std::fs::write(root.join("assets/project.ron"), "()").unwrap();
        let source_path = prefabs.join("hierarchy.scn.ron");
        let before_source = hierarchy_prefab_source(Transform::from_xyz(1.0, 2.0, 3.0));
        std::fs::write(&source_path, &before_source).unwrap();

        let mut world = prefab_test_world(&root);
        let instance = PrefabInstance::new("hierarchy", "assets/prefabs/hierarchy.scn.ron");
        let instance_root = world.spawn(instance.clone()).id();
        let revision = prefab_source_revision(&root, &instance.source_path).unwrap();
        write_overrides(
            &mut world,
            instance_root,
            &PrefabOverrides {
                source_revision: Some(revision.clone()),
                structural: vec![PrefabStructuralOverride::RemoveChild {
                    target: PrefabActorPath::direct("child"),
                    base_subtree_revision: revision,
                }],
                ..default()
            },
        );

        apply_prefab_instance_overrides_to_source(&mut world, instance_root);

        let after_source = std::fs::read_to_string(&source_path).unwrap();
        let document = SceneDocument::from_ron_text(&after_source).unwrap();
        assert!(document
            .find_entity(&SceneEntityId::ActorId("child".into()))
            .is_none());
        assert!(
            read_overrides(world.get::<PrefabInstance>(instance_root).unwrap())
                .structural
                .is_empty()
        );

        crate::history::apply_command_undo(&mut world);
        assert_eq!(
            std::fs::read_to_string(&source_path).unwrap(),
            before_source
        );
        assert_eq!(
            read_overrides(world.get::<PrefabInstance>(instance_root).unwrap())
                .structural
                .len(),
            1
        );
        crate::history::apply_command_redo(&mut world);
        assert_eq!(std::fs::read_to_string(&source_path).unwrap(), after_source);
        std::fs::remove_dir_all(root).unwrap();
    }

    #[test]
    fn reparent_then_transform_edit_applies_in_layer_order_and_undoes() {
        let root = std::env::temp_dir().join(format!(
            "blacksite-prefab-reparent-apply-{}",
            uuid::Uuid::new_v4()
        ));
        let prefabs = root.join("assets/prefabs");
        std::fs::create_dir_all(&prefabs).unwrap();
        std::fs::write(root.join("assets/project.ron"), "()").unwrap();
        let source_path = prefabs.join("hierarchy.scn.ron");
        let before_source = hierarchy_prefab_source(Transform::from_xyz(1.0, 0.0, 0.0));
        std::fs::write(&source_path, &before_source).unwrap();

        let mut world = prefab_test_world(&root);
        let instance_root = world
            .spawn(PrefabInstance::new(
                "hierarchy",
                "assets/prefabs/hierarchy.scn.ron",
            ))
            .id();
        let parent = world
            .spawn((
                ActorId::new("parent"),
                Transform::default(),
                GlobalTransform::from_xyz(10.0, 0.0, 0.0),
                HydratedPrefabMember { instance_root },
                ChildOf(instance_root),
            ))
            .id();
        let child = world
            .spawn((
                ActorId::new("child"),
                Transform::from_xyz(1.0, 0.0, 0.0),
                GlobalTransform::from_xyz(11.0, 0.0, 0.0),
                HydratedPrefabMember { instance_root },
                ChildOf(parent),
            ))
            .id();

        assert!(reparent_prefab_source_child(&mut world, child, None));
        assert_eq!(world.get::<Transform>(child).unwrap().translation.x, 11.0);
        set_prefab_transform_property_override(
            &mut world,
            child,
            "translation",
            Transform::from_xyz(12.0, 0.0, 0.0),
        );
        let instance = world.get::<PrefabInstance>(instance_root).unwrap().clone();
        let overrides = read_overrides(&instance);
        assert!(prefab_semantic_conflicts(&root, &instance, &overrides).is_empty());

        apply_prefab_actor_path_to_source(
            &mut world,
            instance_root,
            &PrefabActorPath::direct("child"),
        );

        let after_source = std::fs::read_to_string(&source_path).unwrap();
        let document = SceneDocument::from_ron_text(&after_source).unwrap();
        let child_source = document
            .find_entity(&SceneEntityId::ActorId("child".into()))
            .unwrap();
        assert!(child_source.parent_actor_id.is_none());
        let transform: Transform = ron::from_str(
            &child_source
                .components
                .iter()
                .find(|component| component.type_name == std::any::type_name::<Transform>())
                .unwrap()
                .ron,
        )
        .unwrap();
        assert_eq!(transform.translation.x, 12.0);

        crate::history::apply_command_undo(&mut world);
        assert_eq!(
            std::fs::read_to_string(&source_path).unwrap(),
            before_source
        );
        let restored = read_overrides(world.get::<PrefabInstance>(instance_root).unwrap());
        assert_eq!(restored.structural.len(), 1);
        assert_eq!(restored.properties.len(), 1);
        std::fs::remove_dir_all(root).unwrap();
    }

    #[test]
    fn multi_member_reparent_is_atomic_and_records_one_history_entry() {
        let mut world = World::new();
        world.init_resource::<crate::scene_io::SceneIo>();
        world.init_resource::<crate::history::EditorHistory>();
        let root = world
            .spawn(PrefabInstance::new("prefab", "assets/prefabs/test.scn.ron"))
            .id();
        let parent = world
            .spawn((
                ActorId::new("parent"),
                GlobalTransform::from_xyz(10.0, 0.0, 0.0),
                HydratedPrefabMember {
                    instance_root: root,
                },
                ChildOf(root),
            ))
            .id();
        let first = world
            .spawn((
                ActorId::new("first"),
                Transform::default(),
                GlobalTransform::from_xyz(1.0, 0.0, 0.0),
                HydratedPrefabMember {
                    instance_root: root,
                },
                ChildOf(root),
            ))
            .id();
        let second = world
            .spawn((
                ActorId::new("second"),
                Transform::default(),
                GlobalTransform::from_xyz(2.0, 0.0, 0.0),
                HydratedPrefabMember {
                    instance_root: root,
                },
                ChildOf(root),
            ))
            .id();

        assert!(reparent_prefab_source_children(
            &mut world,
            &[first, second],
            Some(parent),
        ));
        assert_eq!(
            world.get::<ChildOf>(first).map(ChildOf::parent),
            Some(parent)
        );
        assert_eq!(
            world.get::<ChildOf>(second).map(ChildOf::parent),
            Some(parent)
        );
        assert_eq!(
            read_overrides(world.get::<PrefabInstance>(root).unwrap())
                .structural
                .len(),
            2
        );
        assert_eq!(
            world
                .resource::<crate::history::EditorHistory>()
                .undo_depth(),
            1
        );

        let invalid = world.spawn_empty().id();
        let before = world.get::<ChildOf>(first).map(ChildOf::parent);
        assert!(!reparent_prefab_source_children(
            &mut world,
            &[first, invalid],
            None,
        ));
        assert_eq!(world.get::<ChildOf>(first).map(ChildOf::parent), before);
        assert_eq!(
            world
                .resource::<crate::history::EditorHistory>()
                .undo_depth(),
            1
        );
    }

    #[test]
    fn nested_apply_updates_immediate_source_layer() {
        let root = std::env::temp_dir().join(format!(
            "blacksite-prefab-nested-apply-{}",
            uuid::Uuid::new_v4()
        ));
        let prefabs = root.join("assets/prefabs");
        std::fs::create_dir_all(&prefabs).unwrap();
        std::fs::write(root.join("assets/project.ron"), "()").unwrap();
        let base_path = prefabs.join("base.scn.ron");
        let variant_path = prefabs.join("variant.scn.ron");
        let base_material = MaterialDesc::default();
        let base_source = prefab_source("mesh", Some(&base_material));
        std::fs::write(&base_path, &base_source).unwrap();
        let mut variant_material = base_material.clone();
        variant_material.roughness = 0.2;
        let mut nested_instance = PrefabInstance::new("base", "assets/prefabs/base.scn.ron");
        nested_instance.overrides_ron = encode_prefab_overrides(&PrefabOverrides {
            properties: vec![PrefabPropertyOverride {
                target: PrefabActorPath::direct("mesh"),
                component_type: std::any::type_name::<MaterialDesc>().to_string(),
                property_path: "roughness".to_string(),
                base_component_ron: ron::to_string(&base_material).unwrap(),
                value_component_ron: ron::to_string(&variant_material).unwrap(),
            }],
            ..default()
        })
        .unwrap();
        let variant_source = format!(
            "(schema_version:2,resources:{{}},entities:{{1:(components:{{\"shared::components::LevelObject\":(),\"shared::components::ActorId\":(\"nested\"),\"shared::components::ActorKind\":PrefabAnchor,\"{}\":{},\"{}\":{} }})}})",
            std::any::type_name::<Transform>(),
            ron::to_string(&Transform::default()).unwrap(),
            std::any::type_name::<PrefabInstance>(),
            ron::to_string(&nested_instance).unwrap(),
        );
        std::fs::write(&variant_path, &variant_source).unwrap();

        let mut world = prefab_test_world(&root);
        let outer = world
            .spawn(PrefabInstance::new(
                "variant",
                "assets/prefabs/variant.scn.ron",
            ))
            .id();
        let nested = world
            .spawn((
                ActorId::new("nested"),
                nested_instance.clone(),
                HydratedPrefabMember {
                    instance_root: outer,
                },
                ChildOf(outer),
            ))
            .id();
        let member = world
            .spawn((
                ActorId::new("mesh"),
                variant_material.clone(),
                HydratedPrefabMember {
                    instance_root: nested,
                },
                ChildOf(nested),
            ))
            .id();
        let mut overridden = variant_material;
        overridden.roughness = 0.1;
        set_prefab_material_property_override(&mut world, member, "roughness", overridden);
        let outer_instance = world.get::<PrefabInstance>(outer).unwrap().clone();
        let outer_overrides = read_overrides(&outer_instance);
        assert!(prefab_semantic_conflicts(&root, &outer_instance, &outer_overrides).is_empty());

        apply_prefab_member_scope_to_source(
            &mut world,
            member,
            Some(std::any::type_name::<MaterialDesc>()),
            Some("roughness"),
        );

        assert_eq!(std::fs::read_to_string(&base_path).unwrap(), base_source);
        let document =
            SceneDocument::from_ron_text(&std::fs::read_to_string(&variant_path).unwrap()).unwrap();
        let nested_overrides = decode_prefab_overrides(&document.prefab_instances[0]).unwrap();
        assert_eq!(nested_overrides.properties.len(), 1);
        assert!(nested_overrides.properties[0]
            .target
            .instance_chain
            .is_empty());
        assert_eq!(nested_overrides.properties[0].target.actor_id, "mesh");
        let preserved_base: MaterialDesc =
            ron::from_str(&nested_overrides.properties[0].base_component_ron).unwrap();
        let committed_value: MaterialDesc =
            ron::from_str(&nested_overrides.properties[0].value_component_ron).unwrap();
        assert_eq!(preserved_base.roughness, base_material.roughness);
        assert_eq!(committed_value.roughness, 0.1);
        let effective = source_component_for_target(
            &root,
            world.get::<PrefabInstance>(outer).unwrap(),
            &PrefabActorPath {
                instance_chain: vec!["nested".to_string()],
                actor_id: "mesh".to_string(),
            },
            std::any::type_name::<MaterialDesc>(),
        )
        .unwrap()
        .unwrap();
        assert_eq!(
            ron::from_str::<MaterialDesc>(&effective.ron)
                .unwrap()
                .roughness,
            0.1
        );
        assert!(read_overrides(world.get::<PrefabInstance>(outer).unwrap())
            .properties
            .is_empty());

        crate::history::apply_command_undo(&mut world);
        assert_eq!(
            std::fs::read_to_string(&variant_path).unwrap(),
            variant_source
        );
        assert_eq!(
            read_overrides(world.get::<PrefabInstance>(outer).unwrap())
                .properties
                .len(),
            1
        );
        std::fs::remove_dir_all(root).unwrap();
    }

    #[test]
    fn source_field_conflict_keeps_override_recoverable() {
        let root = std::env::temp_dir().join(format!(
            "blacksite-prefab-source-conflict-{}",
            uuid::Uuid::new_v4()
        ));
        let prefabs = root.join("assets/prefabs");
        std::fs::create_dir_all(&prefabs).unwrap();
        std::fs::write(root.join("assets/project.ron"), "()").unwrap();
        let source_path = prefabs.join("base.scn.ron");
        let base = MaterialDesc::default();
        std::fs::write(&source_path, prefab_source("mesh", Some(&base))).unwrap();

        let mut world = prefab_test_world(&root);
        let instance_root = world
            .spawn(PrefabInstance::new("base", "assets/prefabs/base.scn.ron"))
            .id();
        let member = world
            .spawn((
                ActorId::new("mesh"),
                base.clone(),
                HydratedPrefabMember { instance_root },
                ChildOf(instance_root),
            ))
            .id();
        let mut overridden = base.clone();
        overridden.roughness = 0.1;
        set_prefab_material_property_override(&mut world, member, "roughness", overridden);
        let mut externally_changed = base;
        externally_changed.roughness = 0.4;
        let changed_source = prefab_source("mesh", Some(&externally_changed));
        std::fs::write(&source_path, &changed_source).unwrap();

        apply_prefab_member_scope_to_source(
            &mut world,
            member,
            Some(std::any::type_name::<MaterialDesc>()),
            Some("roughness"),
        );

        assert_eq!(
            std::fs::read_to_string(&source_path).unwrap(),
            changed_source
        );
        assert_eq!(
            read_overrides(world.get::<PrefabInstance>(instance_root).unwrap())
                .properties
                .len(),
            1
        );
        assert!(world
            .resource::<crate::scene_io::SceneIo>()
            .status
            .contains("conflict"));
        assert_eq!(
            world
                .resource::<crate::history::EditorHistory>()
                .undo_depth(),
            1
        );
        std::fs::remove_dir_all(root).unwrap();
    }

    #[test]
    fn visibility_component_override_survives_rename_by_actor_path() {
        let mut world = World::new();
        world.init_resource::<crate::scene_io::SceneIo>();
        world.init_resource::<crate::history::EditorHistory>();
        let root = world
            .spawn((
                LevelObject,
                PrefabInstance::new("prefab-id", "assets/prefabs/test.scn.ron"),
            ))
            .id();
        let child = world
            .spawn((
                LevelObject,
                ActorId::new("stable-child"),
                Name::new("Old Name"),
                EditorVisibility { visible: false },
                ChildOf(root),
                HydratedPrefabMember {
                    instance_root: root,
                },
            ))
            .id();

        set_child_visibility_override(&mut world, root, child, false);
        let overrides = read_overrides(world.get::<PrefabInstance>(root).unwrap());
        assert!(overrides.child_visibility.is_empty());
        assert_eq!(overrides.components.len(), 1);
        assert_eq!(
            overrides.components[0].target,
            PrefabActorPath::direct("stable-child")
        );
        assert_eq!(
            overrides.components[0].component_type,
            std::any::type_name::<EditorVisibility>()
        );
        assert_eq!(
            world
                .resource::<crate::history::EditorHistory>()
                .undo_depth(),
            1
        );

        crate::history::apply_command_undo(&mut world);
        assert!(read_overrides(world.get::<PrefabInstance>(root).unwrap())
            .components
            .is_empty());
        crate::history::apply_command_redo(&mut world);
        assert_eq!(
            read_overrides(world.get::<PrefabInstance>(root).unwrap()).components[0].target,
            PrefabActorPath::direct("stable-child")
        );

        world.entity_mut(child).insert(Name::new("Renamed Child"));
        assert_eq!(
            read_overrides(world.get::<PrefabInstance>(root).unwrap()).components[0].target,
            PrefabActorPath::direct("stable-child")
        );

        revert_child_visibility_overrides(&mut world, root);
        assert_eq!(
            world
                .resource::<crate::history::EditorHistory>()
                .undo_depth(),
            2
        );
        assert!(world.get::<EditorVisibility>(child).is_none());
        assert!(read_overrides(world.get::<PrefabInstance>(root).unwrap())
            .components
            .is_empty());

        crate::history::apply_command_undo(&mut world);
        assert_eq!(
            read_overrides(world.get::<PrefabInstance>(root).unwrap()).components[0].target,
            PrefabActorPath::direct("stable-child")
        );
        crate::history::apply_command_redo(&mut world);
        assert!(read_overrides(world.get::<PrefabInstance>(root).unwrap())
            .components
            .is_empty());
    }

    #[test]
    fn nested_visibility_overrides_distinguish_repeated_leaf_actor_ids() {
        let mut world = World::new();
        world.init_resource::<crate::scene_io::SceneIo>();
        world.init_resource::<crate::history::EditorHistory>();
        let root = world
            .spawn(PrefabInstance::new("outer", "assets/prefabs/outer.scn.ron"))
            .id();
        let nested_a = world
            .spawn((
                ActorId::new("nested-a"),
                PrefabInstance::new("leaf", "assets/prefabs/leaf.scn.ron"),
                HydratedPrefabMember {
                    instance_root: root,
                },
                ChildOf(root),
            ))
            .id();
        let nested_b = world
            .spawn((
                ActorId::new("nested-b"),
                PrefabInstance::new("leaf", "assets/prefabs/leaf.scn.ron"),
                HydratedPrefabMember {
                    instance_root: root,
                },
                ChildOf(root),
            ))
            .id();
        let leaf_a = world
            .spawn((
                ActorId::new("shared-leaf"),
                EditorVisibility::default(),
                HydratedPrefabMember {
                    instance_root: nested_a,
                },
                ChildOf(nested_a),
            ))
            .id();
        let leaf_b = world
            .spawn((
                ActorId::new("shared-leaf"),
                EditorVisibility::default(),
                HydratedPrefabMember {
                    instance_root: nested_b,
                },
                ChildOf(nested_b),
            ))
            .id();

        set_child_visibility_override(&mut world, root, leaf_a, false);
        set_child_visibility_override(&mut world, root, leaf_b, false);

        let overrides = read_overrides(world.get::<PrefabInstance>(root).unwrap());
        let targets: HashSet<_> = overrides
            .components
            .iter()
            .map(|component| component.target.clone())
            .collect();
        assert_eq!(targets.len(), 2);
        assert!(targets.contains(&PrefabActorPath {
            instance_chain: vec!["nested-a".to_string()],
            actor_id: "shared-leaf".to_string(),
        }));
        assert!(targets.contains(&PrefabActorPath {
            instance_chain: vec!["nested-b".to_string()],
            actor_id: "shared-leaf".to_string(),
        }));

        crate::history::apply_command_undo(&mut world);
        assert_eq!(
            read_overrides(world.get::<PrefabInstance>(root).unwrap())
                .components
                .len(),
            1
        );
        crate::history::apply_command_redo(&mut world);
        assert_eq!(
            read_overrides(world.get::<PrefabInstance>(root).unwrap())
                .components
                .len(),
            2
        );
    }

    #[test]
    fn ready_instance_reapplies_child_visibility_by_actor_id() {
        let mut app = App::new();
        app.add_plugins((AssetPlugin::default(), WorldSerializationPlugin))
            .register_type::<ActorId>()
            .add_observer(shared::apply_prefab_overrides_on_ready);

        let mut source = World::new();
        source.spawn(ActorId::new("stable-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-id", "assets/prefabs/test.scn.ron"),
                DynamicWorldRoot(handle),
            ))
            .id();
        write_overrides(
            app.world_mut(),
            root,
            &PrefabOverrides {
                child_visibility: HashMap::from([("stable-child".to_string(), false)]),
                ..default()
            },
        );

        app.update();

        let world = app.world_mut();
        let (_, visibility) = world
            .query::<(&ActorId, &EditorVisibility)>()
            .single(world)
            .expect("ready observer should apply the stable child override");
        assert!(!visibility.visible);
    }

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

        let mut source = World::new();
        source.spawn(ActorId::new("source-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-id", "assets/prefabs/test.scn.ron"),
                DynamicWorldRoot(handle.clone()),
            ))
            .id();
        app.update();

        let first_instance = **app
            .world()
            .get::<WorldInstance>(root)
            .expect("initial prefab should be registered");
        let first_members: Vec<_> = app
            .world()
            .resource::<WorldInstanceSpawner>()
            .iter_instance_entities(first_instance)
            .collect();
        assert_eq!(first_members.len(), 1);
        for member in first_members {
            app.world_mut()
                .entity_mut(member)
                .insert(HydratedPrefabMember {
                    instance_root: root,
                });
        }

        block_prefab_hydration(app.world_mut(), root, "invalid source".to_string());

        assert!(app.world().get::<DynamicWorldRoot>(root).is_none());
        assert!(app.world().get::<WorldInstance>(root).is_none());
        assert!(app
            .world()
            .get::<shared::PrefabHydrationBlocked>(root)
            .is_some());
        assert_eq!(
            app.world_mut()
                .query::<&HydratedPrefabMember>()
                .iter(app.world())
                .count(),
            0
        );
        assert!(!app
            .world()
            .resource::<WorldInstanceSpawner>()
            .instance_is_ready(first_instance));

        app.world_mut()
            .entity_mut(root)
            .remove::<shared::PrefabHydrationBlocked>()
            .insert(DynamicWorldRoot(handle));
        app.update();

        let repaired_instance = **app
            .world()
            .get::<WorldInstance>(root)
            .expect("repaired prefab should register a fresh instance");
        let repaired_members: Vec<_> = app
            .world()
            .resource::<WorldInstanceSpawner>()
            .iter_instance_entities(repaired_instance)
            .collect();
        assert_ne!(repaired_instance, first_instance);
        assert_eq!(repaired_members.len(), 1);
        assert_eq!(
            app.world_mut()
                .query::<&ActorId>()
                .iter(app.world())
                .count(),
            1,
            "retry must not duplicate generated source state"
        );
    }

    #[test]
    fn removed_asset_event_blocks_prefab_and_cleans_generated_state() {
        let project_root = std::env::temp_dir().join(format!(
            "blacksite-prefab-removed-event-{}",
            uuid::Uuid::new_v4()
        ));
        std::fs::create_dir_all(project_root.join("assets/prefabs")).unwrap();
        std::fs::write(project_root.join("assets/project.ron"), "()").unwrap();

        let mut app = App::new();
        app.add_plugins((AssetPlugin::default(), WorldSerializationPlugin))
            .init_resource::<crate::scene_io::SceneIo>()
            .insert_resource(crate::project_io::ProjectWorkspace {
                root: project_root.display().to_string(),
                ..Default::default()
            })
            .add_systems(Update, guard_prefab_reimports);
        let handle = app
            .world_mut()
            .resource_mut::<Assets<DynamicWorld>>()
            .add(DynamicWorld::default());
        let root = app
            .world_mut()
            .spawn((
                DynamicWorldRoot(handle.clone()),
                PrefabInstance::new("missing", "assets/prefabs/missing.scn.ron"),
            ))
            .id();
        let generated = app
            .world_mut()
            .spawn(HydratedPrefabMember {
                instance_root: root,
            })
            .id();
        app.world_mut()
            .resource_mut::<Messages<AssetEvent<DynamicWorld>>>()
            .write(AssetEvent::Removed { id: handle.id() });

        app.update();

        assert!(app.world().get_entity(generated).is_err());
        assert!(app
            .world()
            .get::<shared::PrefabHydrationBlocked>(root)
            .is_some());
        assert!(app.world().get::<DynamicWorldRoot>(root).is_none());
        std::fs::remove_dir_all(project_root).unwrap();
    }

    #[test]
    fn nested_invalid_reimport_blocks_top_authored_owner() {
        let mut world = World::new();
        let root = world
            .spawn((
                PrefabInstance::new("outer", "assets/prefabs/outer.scn.ron"),
                DynamicWorldRoot::default(),
            ))
            .id();
        let nested = world
            .spawn((
                PrefabInstance::new("nested", "assets/prefabs/nested.scn.ron"),
                HydratedPrefabMember {
                    instance_root: root,
                },
            ))
            .id();
        let nested_child = world
            .spawn(HydratedPrefabMember {
                instance_root: nested,
            })
            .id();

        block_prefab_hydration(&mut world, nested, "nested source invalid".to_string());

        assert!(world
            .get::<shared::PrefabHydrationBlocked>(root)
            .is_some_and(|blocked| blocked.reason == "nested source invalid"));
        assert!(world.get::<DynamicWorldRoot>(root).is_none());
        assert!(world.get_entity(nested).is_err());
        assert!(world.get_entity(nested_child).is_err());
    }
}
