//! Schema-aware scene document seam over the current DynamicScene RON format.
//!
//! This module intentionally stays Bevy-free. The editor can keep loading and
//! saving DynamicScene RON while tools start depending on stable actor/component
//! document concepts instead of runtime ECS entity IDs.

use std::collections::BTreeMap;

use ron::ser::PrettyConfig;
use serde::{Deserialize, Serialize};
use shared::{ActorId, PrefabInstance, PrefabRef, SceneComposition};

use crate::{
    migrate_scene_text, read_schema_version, stamp_schema_version, strip_schema_version,
    validate_scene_authoring_only,
};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SceneDocument {
    pub schema_version: u32,
    pub entities: Vec<SceneEntity>,
    pub composition: Option<SceneComposition>,
    pub resource_types: Vec<String>,
    pub prefab_instances: Vec<PrefabInstance>,
    pub prefab_refs: Vec<PrefabRef>,
    normalized_ron: String,
    resources: BTreeMap<String, String>,
    source_keys: BTreeMap<SceneEntityId, u64>,
}

impl SceneDocument {
    pub fn from_ron_text(text: &str) -> Result<Self, String> {
        let normalized = migrate_scene_text(text)?;
        validate_scene_authoring_only(&normalized)?;
        validate_hierarchy_references(&normalized)?;
        let body = strip_schema_version(&normalized)?;
        let parsed: DynamicSceneBody = ron::from_str(&body)
            .map_err(|err| format!("could not parse scene document body: {err}"))?;
        let (resources, mut raw_entities) = extract_raw_scene_values(&normalized)?;

        let composition = extract_scene_composition(&parsed.resources)?;
        let resource_types = parsed.resources.keys().cloned().collect();
        let prefab_instances = extract_prefab_instances(&parsed.entities)?;
        let prefab_refs = extract_prefab_refs(&parsed.entities)?;
        let mut entities = Vec::with_capacity(parsed.entities.len());
        let mut source_keys = BTreeMap::new();
        for (index, (source_key, _entity)) in parsed.entities.into_iter().enumerate() {
            let components = raw_entities
                .remove(&source_key)
                .ok_or_else(|| format!("scene entity `{source_key}` has no lossless syntax node"))?
                .into_iter()
                .map(|(type_name, ron)| SceneComponentBlob::from_ron(type_name, ron))
                .collect::<Result<Vec<_>, _>>()?;

            let actor_id = extract_actor_id(&components);
            let name = extract_name(&components);
            let document_id = actor_id
                .as_ref()
                .map(|id| SceneEntityId::ActorId(id.clone()))
                .unwrap_or_else(|| SceneEntityId::Anonymous(format!("anonymous:{index}")));

            entities.push(SceneEntity {
                document_id: document_id.clone(),
                actor_id,
                name,
                parent_actor_id: None,
                components,
            });
            source_keys.insert(document_id, source_key);
        }

        let key_to_actor: BTreeMap<u64, String> = entities
            .iter()
            .filter_map(|entity| {
                let source_key = source_keys.get(&entity.document_id).copied()?;
                Some((source_key, entity.actor_id.clone()?))
            })
            .collect();
        for entity in &mut entities {
            entity.parent_actor_id = entity
                .components
                .iter()
                .find(|component| component.type_name == CHILD_OF_COMPONENT)
                .and_then(|component| parse_child_of_source_key(&component.ron))
                .and_then(|source_key| key_to_actor.get(&source_key).cloned());
        }

        Ok(Self {
            schema_version: read_schema_version(&normalized),
            entities,
            composition,
            resource_types,
            prefab_instances,
            prefab_refs,
            normalized_ron: normalized,
            resources,
            source_keys,
        })
    }

    pub fn to_ron_text(&self) -> Result<String, String> {
        let mut resources = self.resources.clone();
        match &self.composition {
            Some(composition) => {
                let composition_ron = ron::to_string(composition)
                    .map_err(|error| format!("could not encode scene composition: {error}"))?;
                resources.insert(SCENE_COMPOSITION_RESOURCE.to_string(), composition_ron);
            }
            None => {
                resources.remove(SCENE_COMPOSITION_RESOURCE);
            }
        }

        let mut used_keys: std::collections::HashSet<u64> =
            self.source_keys.values().copied().collect();
        let mut next_key = used_keys
            .iter()
            .copied()
            .max()
            .unwrap_or(0)
            .saturating_add(1);
        let mut entities = BTreeMap::new();
        for entity in &self.entities {
            let source_key = self
                .source_keys
                .get(&entity.document_id)
                .copied()
                .unwrap_or_else(|| {
                    while used_keys.contains(&next_key) {
                        next_key = next_key.saturating_add(1);
                    }
                    let assigned = next_key;
                    used_keys.insert(assigned);
                    next_key = next_key.saturating_add(1);
                    assigned
                });
            let components = entity
                .components
                .iter()
                .map(|component| {
                    ron::from_str::<ron::Value>(&component.ron).map_err(|error| {
                        format!("invalid component RON `{}`: {error}", component.type_name)
                    })?;
                    Ok((component.type_name.clone(), component.ron.clone()))
                })
                .collect::<Result<BTreeMap<_, _>, String>>()?;
            entities.insert(source_key, components);
        }
        encode_lossless_scene_body(&resources, &entities)
    }

    pub fn normalized_ron(&self) -> &str {
        &self.normalized_ron
    }

    pub fn find_entity(&self, id: &SceneEntityId) -> Option<&SceneEntity> {
        self.entities
            .iter()
            .find(|entity| &entity.document_id == id)
    }

    pub fn apply_patch(&mut self, patch: ScenePatch) -> Result<(), String> {
        match patch {
            ScenePatch::SetComponent { target, component } => {
                let entity = self
                    .find_entity_mut(&target)
                    .ok_or_else(|| format!("scene entity `{target}` not found"))?;
                if let Some(existing) = entity
                    .components
                    .iter_mut()
                    .find(|existing| existing.type_name == component.type_name)
                {
                    *existing = component;
                } else {
                    entity.components.push(component);
                }
            }
            ScenePatch::RemoveComponent {
                target,
                component_type,
            } => {
                let entity = self
                    .find_entity_mut(&target)
                    .ok_or_else(|| format!("scene entity `{target}` not found"))?;
                entity
                    .components
                    .retain(|component| component.type_name != component_type);
            }
            ScenePatch::AddEntity { entity } => {
                if self.find_entity(&entity.document_id).is_some() {
                    return Err(format!(
                        "scene entity `{}` already exists",
                        entity.document_id
                    ));
                }
                let next_key = self
                    .source_keys
                    .values()
                    .copied()
                    .max()
                    .unwrap_or(0)
                    .saturating_add(1);
                self.source_keys
                    .insert(entity.document_id.clone(), next_key);
                self.entities.push(entity);
            }
            ScenePatch::DeleteEntity { target } => {
                let Some(target_actor_id) = self
                    .find_entity(&target)
                    .and_then(|entity| entity.actor_id.clone())
                else {
                    return Err(format!("scene entity `{target}` not found"));
                };
                let mut removed_actor_ids = std::collections::HashSet::from([target_actor_id]);
                let mut changed = true;
                while changed {
                    changed = false;
                    for entity in &self.entities {
                        if entity
                            .parent_actor_id
                            .as_ref()
                            .is_some_and(|parent| removed_actor_ids.contains(parent))
                        {
                            if let Some(actor_id) = entity.actor_id.clone() {
                                changed |= removed_actor_ids.insert(actor_id);
                            }
                        }
                    }
                }
                let removed_ids: Vec<_> = self
                    .entities
                    .iter()
                    .filter(|entity| {
                        entity
                            .actor_id
                            .as_ref()
                            .is_some_and(|actor_id| removed_actor_ids.contains(actor_id))
                    })
                    .map(|entity| entity.document_id.clone())
                    .collect();
                self.entities
                    .retain(|entity| !removed_ids.contains(&entity.document_id));
                for removed in removed_ids {
                    self.source_keys.remove(&removed);
                }
            }
            ScenePatch::Reparent {
                target,
                parent_actor_id,
            } => {
                let parent_source_key = parent_actor_id
                    .as_ref()
                    .map(|parent| {
                        self.source_keys
                            .get(&SceneEntityId::ActorId(parent.clone()))
                            .copied()
                            .ok_or_else(|| format!("parent actor `{parent}` not found"))
                    })
                    .transpose()?;
                let entity = self
                    .find_entity_mut(&target)
                    .ok_or_else(|| format!("scene entity `{target}` not found"))?;
                match parent_source_key {
                    Some(parent_source_key) => {
                        let component = SceneComponentBlob::from_ron(
                            CHILD_OF_COMPONENT,
                            format!("({parent_source_key})"),
                        )?;
                        if let Some(existing) = entity
                            .components
                            .iter_mut()
                            .find(|component| component.type_name == CHILD_OF_COMPONENT)
                        {
                            *existing = component;
                        } else {
                            entity.components.push(component);
                        }
                    }
                    None => entity
                        .components
                        .retain(|component| component.type_name != CHILD_OF_COMPONENT),
                }
                entity.parent_actor_id = parent_actor_id;
            }
            ScenePatch::Rename { target, name } => {
                let entity = self
                    .find_entity_mut(&target)
                    .ok_or_else(|| format!("scene entity `{target}` not found"))?;
                let component = SceneComponentBlob::from_serializable(NAME_COMPONENT, &name)?;
                if let Some(existing) = entity
                    .components
                    .iter_mut()
                    .find(|existing| existing.type_name == NAME_COMPONENT)
                {
                    *existing = component;
                } else {
                    entity.components.push(component);
                }
                entity.name = Some(name);
            }
            ScenePatch::SetTransform { target, transform } => {
                let entity = self
                    .find_entity_mut(&target)
                    .ok_or_else(|| format!("scene entity `{target}` not found"))?;
                let component =
                    SceneComponentBlob::from_serializable(TRANSFORM_COMPONENT, &transform)?;
                if let Some(existing) = entity
                    .components
                    .iter_mut()
                    .find(|existing| existing.type_name == TRANSFORM_COMPONENT)
                {
                    *existing = component;
                } else {
                    entity.components.push(component);
                }
            }
        }
        Ok(())
    }

    fn find_entity_mut(&mut self, id: &SceneEntityId) -> Option<&mut SceneEntity> {
        self.entities
            .iter_mut()
            .find(|entity| &entity.document_id == id)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum SceneEntityId {
    ActorId(String),
    Anonymous(String),
}

impl std::fmt::Display for SceneEntityId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ActorId(id) => write!(f, "actor:{id}"),
            Self::Anonymous(id) => write!(f, "{id}"),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SceneEntity {
    pub document_id: SceneEntityId,
    pub actor_id: Option<String>,
    pub name: Option<String>,
    pub parent_actor_id: Option<String>,
    pub components: Vec<SceneComponentBlob>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SceneComponentBlob {
    pub type_name: String,
    pub ron: String,
}

impl SceneComponentBlob {
    pub fn from_serializable(
        type_name: impl Into<String>,
        value: &impl Serialize,
    ) -> Result<Self, String> {
        let pretty = PrettyConfig::new();
        let ron = ron::ser::to_string_pretty(value, pretty)
            .map_err(|err| format!("could not serialize component blob: {err}"))?;
        Ok(Self {
            type_name: type_name.into(),
            ron,
        })
    }

    pub fn from_ron(type_name: impl Into<String>, ron: impl Into<String>) -> Result<Self, String> {
        let type_name = type_name.into();
        let ron = ron.into();
        ron::from_str::<ron::Value>(&ron)
            .map_err(|error| format!("invalid component RON `{type_name}`: {error}"))?;
        Ok(Self { type_name, ron })
    }

    pub fn to_value(&self) -> Result<ron::Value, String> {
        ron::from_str(&self.ron)
            .map_err(|error| format!("invalid component RON `{}`: {error}", self.type_name))
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ScenePatch {
    SetComponent {
        target: SceneEntityId,
        component: SceneComponentBlob,
    },
    RemoveComponent {
        target: SceneEntityId,
        component_type: String,
    },
    AddEntity {
        entity: SceneEntity,
    },
    DeleteEntity {
        target: SceneEntityId,
    },
    Reparent {
        target: SceneEntityId,
        parent_actor_id: Option<String>,
    },
    Rename {
        target: SceneEntityId,
        name: String,
    },
    SetTransform {
        target: SceneEntityId,
        transform: SceneTransform,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct SceneTransform {
    pub translation: [f32; 3],
    pub rotation: [f32; 4],
    pub scale: [f32; 3],
}

impl Default for SceneTransform {
    fn default() -> Self {
        Self {
            translation: [0.0, 0.0, 0.0],
            rotation: [0.0, 0.0, 0.0, 1.0],
            scale: [1.0, 1.0, 1.0],
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct DynamicSceneBody {
    #[serde(default)]
    resources: BTreeMap<String, ron::Value>,
    #[serde(default)]
    entities: BTreeMap<u64, DynamicSceneEntity>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct DynamicSceneEntity {
    #[serde(default)]
    components: BTreeMap<String, ron::Value>,
}

const ACTOR_ID_COMPONENT: &str = "shared::components::ActorId";
const CHILD_OF_COMPONENT: &str = "bevy_ecs::hierarchy::ChildOf";
const NAME_COMPONENT: &str = "bevy_ecs::name::Name";
const SCENE_COMPOSITION_RESOURCE: &str = "shared::components::SceneComposition";
const PREFAB_INSTANCE_COMPONENT: &str = "shared::components::PrefabInstance";
const PREFAB_REF_COMPONENT: &str = "shared::components::PrefabRef";
pub const TRANSFORM_COMPONENT: &str = "bevy_transform::components::transform::Transform";

type RawSceneEntities = BTreeMap<u64, BTreeMap<String, String>>;

fn extract_raw_scene_values(
    text: &str,
) -> Result<(BTreeMap<String, String>, RawSceneEntities), String> {
    let file = ron_edit::File::try_from(text)
        .map_err(|error| format!("could not parse lossless scene syntax: {error}"))?;
    let root = as_struct(&file.value.content, "scene root")?;
    let resources = as_map(struct_field(root, "resources")?, "scene resources")?;
    let entities = as_map(struct_field(root, "entities")?, "scene entities")?;

    let resources = resources
        .0
        .values
        .iter()
        .map(|entry| {
            Ok((
                parse_string_key(&entry.content.key)?,
                entry.content.value.content.to_string(),
            ))
        })
        .collect::<Result<BTreeMap<_, _>, String>>()?;
    let entities = entities
        .0
        .values
        .iter()
        .map(|entry| {
            let source_key = entry
                .content
                .key
                .to_string()
                .parse::<u64>()
                .map_err(|error| format!("invalid scene entity key: {error}"))?;
            let entity = as_struct(&entry.content.value.content, "scene entity")?;
            let components = as_map(struct_field(entity, "components")?, "entity components")?;
            let components = components
                .0
                .values
                .iter()
                .map(|component| {
                    Ok((
                        parse_string_key(&component.content.key)?,
                        component.content.value.content.to_string(),
                    ))
                })
                .collect::<Result<BTreeMap<_, _>, String>>()?;
            Ok((source_key, components))
        })
        .collect::<Result<BTreeMap<_, _>, String>>()?;
    Ok((resources, entities))
}

fn as_struct<'a, 's>(
    value: &'a ron_edit::Value<'s>,
    label: &str,
) -> Result<&'a ron_edit::Struct<'s>, String> {
    match value {
        ron_edit::Value::Struct(value) => Ok(value),
        _ => Err(format!("{label} must be a RON struct")),
    }
}

fn as_map<'a, 's>(
    value: &'a ron_edit::Value<'s>,
    label: &str,
) -> Result<&'a ron_edit::Map<'s>, String> {
    match value {
        ron_edit::Value::Map(value) => Ok(value),
        _ => Err(format!("{label} must be a RON map")),
    }
}

fn struct_field<'a, 's>(
    value: &'a ron_edit::Struct<'s>,
    name: &str,
) -> Result<&'a ron_edit::Value<'s>, String> {
    value
        .fields
        .values
        .iter()
        .find(|field| field.content.key == name)
        .map(|field| &field.content.value.content)
        .ok_or_else(|| format!("RON struct is missing `{name}`"))
}

fn parse_string_key(value: &ron_edit::Value<'_>) -> Result<String, String> {
    ron::from_str(&value.to_string()).map_err(|error| format!("invalid string map key: {error}"))
}

fn parse_child_of_source_key(text: &str) -> Option<u64> {
    let value = ron::from_str::<ron::Value>(text).ok()?;
    parse_child_of_source_key_value(&value)
}

fn parse_child_of_source_key_value(value: &ron::Value) -> Option<u64> {
    let ron::Value::Seq(values) = value else {
        return None;
    };
    let ron::Value::Number(ron::value::Number::Integer(value)) = values.first()? else {
        return None;
    };
    u64::try_from(*value).ok()
}

pub(crate) fn validate_hierarchy_references(text: &str) -> Result<(), String> {
    let body = strip_schema_version(text)?;
    let parsed: DynamicSceneBody = ron::from_str(&body)
        .map_err(|error| format!("could not parse scene hierarchy: {error}"))?;
    let mut parents = BTreeMap::new();

    for (entity_key, entity) in &parsed.entities {
        let Some(value) = entity.components.get(CHILD_OF_COMPONENT) else {
            continue;
        };
        let parent_key = parse_child_of_source_key_value(value).ok_or_else(|| {
            format!("scene entity `{entity_key}` has a malformed `{CHILD_OF_COMPONENT}` value")
        })?;
        if !parsed.entities.contains_key(&parent_key) {
            return Err(format!(
                "scene entity `{entity_key}` references missing hierarchy parent `{parent_key}`"
            ));
        }
        parents.insert(*entity_key, parent_key);
    }

    for start in parents.keys().copied() {
        let mut visited = std::collections::BTreeSet::new();
        let mut current = start;
        while let Some(parent) = parents.get(&current).copied() {
            if !visited.insert(current) {
                return Err(format!(
                    "scene hierarchy contains a cycle involving entity `{current}`"
                ));
            }
            current = parent;
        }
    }

    Ok(())
}

fn encode_lossless_scene_body(
    resources: &BTreeMap<String, String>,
    entities: &BTreeMap<u64, BTreeMap<String, String>>,
) -> Result<String, String> {
    let mut body = String::from("(resources: {\n");
    for (type_name, value) in resources {
        ron::from_str::<ron::Value>(value)
            .map_err(|error| format!("invalid resource RON `{type_name}`: {error}"))?;
        body.push_str("        ");
        body.push_str(
            &ron::to_string(type_name)
                .map_err(|error| format!("could not encode resource type: {error}"))?,
        );
        body.push_str(": ");
        body.push_str(value);
        body.push_str(",\n");
    }
    body.push_str("    },\n    entities: {\n");
    for (source_key, components) in entities {
        body.push_str(&format!("        {source_key}: (components: {{\n"));
        for (type_name, value) in components {
            body.push_str("            ");
            body.push_str(
                &ron::to_string(type_name)
                    .map_err(|error| format!("could not encode component type: {error}"))?,
            );
            body.push_str(": ");
            body.push_str(value);
            body.push_str(",\n");
        }
        body.push_str("        }),\n");
    }
    body.push_str("    },\n)");
    stamp_schema_version(&body)
}

fn extract_scene_composition(
    resources: &BTreeMap<String, ron::Value>,
) -> Result<Option<SceneComposition>, String> {
    let mut matches = resources
        .iter()
        .filter(|(type_name, _)| type_name.as_str() == SCENE_COMPOSITION_RESOURCE);
    let Some((_type_name, value)) = matches.next() else {
        return Ok(None);
    };
    if matches.next().is_some() {
        return Err("scene contains multiple composition resources".to_string());
    }
    value
        .clone()
        .into_rust::<SceneComposition>()
        .map(Some)
        .map_err(|error| format!("could not parse scene composition: {error}"))
}

fn extract_prefab_instances(
    entities: &BTreeMap<u64, DynamicSceneEntity>,
) -> Result<Vec<PrefabInstance>, String> {
    entities
        .values()
        .filter_map(|entity| entity.components.get(PREFAB_INSTANCE_COMPONENT))
        .map(|value| {
            value
                .clone()
                .into_rust::<PrefabInstance>()
                .map_err(|error| format!("could not parse prefab instance: {error}"))
        })
        .collect()
}

fn extract_prefab_refs(
    entities: &BTreeMap<u64, DynamicSceneEntity>,
) -> Result<Vec<PrefabRef>, String> {
    entities
        .values()
        .filter_map(|entity| entity.components.get(PREFAB_REF_COMPONENT))
        .map(|value| {
            value
                .clone()
                .into_rust::<PrefabRef>()
                .map_err(|error| format!("could not parse prefab reference: {error}"))
        })
        .collect()
}

fn extract_name(components: &[SceneComponentBlob]) -> Option<String> {
    components
        .iter()
        .find(|component| component.type_name == NAME_COMPONENT)
        .map(|component| component.ron.trim().trim_matches('"').to_string())
        .filter(|name| !name.is_empty())
}

fn extract_actor_id(components: &[SceneComponentBlob]) -> Option<String> {
    let component = components
        .iter()
        .find(|component| component.type_name == ACTOR_ID_COMPONENT)?;
    ron::from_str::<ActorId>(&component.ron)
        .ok()
        .map(|id| id.0)
        .or_else(|| ron::from_str::<String>(&component.ron).ok())
        .or_else(|| {
            ron::from_str::<Vec<String>>(&component.ron)
                .ok()
                .and_then(|values| values.into_iter().next())
        })
        .filter(|value| !value.trim().is_empty())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn lossless_parser_accepts_anonymous_structs_and_tuples() {
        let value = "(translation: (1.0, 2.0, 3.0), scale: (1.0, 1.0, 1.0))";
        assert!(ron_edit::File::try_from(value).is_ok());
    }

    #[test]
    fn parses_scene_document_without_exposing_runtime_entity_ids() {
        let text = r#"(schema_version: 2,
  resources: {},
  entities: {
    4294967133: (
      components: {
        "bevy_ecs::name::Name": "Crate",
        "shared::components::ActorId": ("crate-01"),
        "shared::components::LevelObject": (),
      },
    ),
  },
)"#;
        let document = SceneDocument::from_ron_text(text).unwrap();
        assert_eq!(document.schema_version, crate::CURRENT_SCENE_SCHEMA_VERSION);
        assert_eq!(document.entities.len(), 1);
        assert_eq!(document.entities[0].actor_id.as_deref(), Some("crate-01"));
        assert_eq!(document.entities[0].name.as_deref(), Some("Crate"));
        assert_eq!(
            document.entities[0].document_id,
            SceneEntityId::ActorId("crate-01".into())
        );
        assert!(!format!("{:?}", document.entities[0].document_id).contains("4294967133"));
    }

    #[test]
    fn round_trips_normalized_ron_text() {
        let body = "(resources: {}, entities: {})";
        let document = SceneDocument::from_ron_text(body).unwrap();
        let text = document.to_ron_text().unwrap();
        assert!(text.starts_with("(schema_version: 4,"));
        assert!(SceneDocument::from_ron_text(&text)
            .unwrap()
            .entities
            .is_empty());
    }

    #[test]
    fn patch_rename_and_transform_are_representable() {
        let text = r#"(schema_version: 2,
  resources: {},
  entities: {
    1: (
      components: {
        "shared::components::ActorId": ("actor-a"),
        "shared::components::LevelObject": (),
      },
    ),
  },
)"#;
        let mut document = SceneDocument::from_ron_text(text).unwrap();
        let target = SceneEntityId::ActorId("actor-a".into());
        document
            .apply_patch(ScenePatch::Rename {
                target: target.clone(),
                name: "Renamed".into(),
            })
            .unwrap();
        document
            .apply_patch(ScenePatch::SetTransform {
                target: target.clone(),
                transform: SceneTransform {
                    translation: [1.0, 2.0, 3.0],
                    ..Default::default()
                },
            })
            .unwrap();

        let entity = document.find_entity(&target).unwrap();
        assert_eq!(entity.name.as_deref(), Some("Renamed"));
        assert!(entity
            .components
            .iter()
            .any(|component| component.type_name == TRANSFORM_COMPONENT));

        let reparsed = SceneDocument::from_ron_text(&document.to_ron_text().unwrap()).unwrap();
        let entity = reparsed.find_entity(&target).unwrap();
        assert_eq!(entity.name.as_deref(), Some("Renamed"));
        let transform = entity
            .components
            .iter()
            .find(|component| component.type_name == TRANSFORM_COMPONENT)
            .unwrap();
        assert!(transform.ron.contains("1.0"));
    }
}
