//! Hierarchy tree building, sibling ordering, and reparent helpers.

use bevy::prelude::*;
use shared::{
    ActorKind, AuthoringLightKind, EditorVisibility, HierarchySiblingIndex, HydratedPrefabMember,
    LevelObject, LightDesc, ModelRef, PhysicsBody, PlayerSpawn, PrefabInstance,
    SkinnedMeshRenderer,
};

use super::helpers::{entity_name, is_level_object, HierarchyNodeKind};
use super::hierarchy_state::HierarchySort;
use crate::history::SiblingChange;

pub fn sibling_index(world: &World, entity: Entity) -> i32 {
    world
        .get::<HierarchySiblingIndex>(entity)
        .map(|index| index.0)
        .unwrap_or(0)
}

pub fn is_ancestor(world: &World, ancestor: Entity, descendant: Entity) -> bool {
    if ancestor == descendant {
        return true;
    }
    let mut current = world
        .get::<ChildOf>(descendant)
        .map(|child_of| child_of.parent());
    while let Some(entity) = current {
        if entity == ancestor {
            return true;
        }
        current = world
            .get::<ChildOf>(entity)
            .map(|child_of| child_of.parent());
    }
    false
}

pub fn would_create_cycle(world: &World, targets: &[Entity], new_parent: Option<Entity>) -> bool {
    let Some(parent) = new_parent else {
        return false;
    };
    targets
        .iter()
        .any(|entity| is_ancestor(world, *entity, parent))
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HierarchyDropViolation {
    Cycle,
    PrefabBoundary { root: Entity },
}

/// Returns the authored prefab-instance root containing `entity`, including the root itself.
pub fn prefab_instance_boundary(world: &World, entity: Entity) -> Option<Entity> {
    let mut current = Some(entity);
    while let Some(candidate) = current {
        if world.get::<PrefabInstance>(candidate).is_some() {
            return Some(candidate);
        }
        current = world
            .get::<ChildOf>(candidate)
            .map(|child_of| child_of.parent())
            .filter(|parent| is_level_object(world, *parent));
    }
    None
}

/// Authored local children may be attached to instances; hydrated source members remain read-only.
pub fn hierarchy_drop_violation(
    world: &World,
    targets: &[Entity],
    new_parent: Option<Entity>,
) -> Option<HierarchyDropViolation> {
    if would_create_cycle(world, targets, new_parent) {
        return Some(HierarchyDropViolation::Cycle);
    }
    let prefab_targets: Vec<_> = targets
        .iter()
        .filter_map(|target| {
            crate::assets::prefab_overrides::prefab_member_override_target(world, *target)
        })
        .collect();
    if !prefab_targets.is_empty() {
        let (root, path) = &prefab_targets[0];
        let compatible_targets = prefab_targets.len() == targets.len()
            && prefab_targets.iter().all(|(candidate_root, candidate)| {
                candidate_root == root && candidate.instance_chain == path.instance_chain
            });
        let compatible_parent = new_parent.is_none_or(|parent| {
            crate::assets::prefab_overrides::prefab_member_override_target(world, parent)
                .is_some_and(|(parent_root, parent_path)| {
                    parent_root == *root && parent_path.instance_chain == path.instance_chain
                })
        });
        if compatible_targets && compatible_parent {
            return None;
        }
        return Some(HierarchyDropViolation::PrefabBoundary { root: *root });
    }
    new_parent
        .and_then(|parent| world.get::<HydratedPrefabMember>(parent))
        .map(|owner| HierarchyDropViolation::PrefabBoundary {
            root: owner.instance_root,
        })
}

pub fn level_object_roots(world: &mut World) -> Vec<Entity> {
    let mut query = world.query_filtered::<(Entity, Option<&ChildOf>), With<LevelObject>>();
    query
        .iter(world)
        .filter_map(|(entity, parent)| match parent {
            Some(parent) if is_level_object(world, parent.parent()) => None,
            _ => Some(entity),
        })
        .collect()
}

pub fn authored_children(world: &World, parent: Entity) -> Vec<Entity> {
    world
        .get::<Children>(parent)
        .map(|children| {
            children
                .iter()
                .filter(|child| is_level_object(world, *child))
                .collect()
        })
        .unwrap_or_default()
}

pub fn mutable_authored_children(world: &World, parent: Entity) -> Vec<Entity> {
    authored_children(world, parent)
        .into_iter()
        .filter(|entity| {
            world.get::<HydratedPrefabMember>(*entity).is_none()
                && world
                    .get::<crate::scene_io::ComposedSceneMember>(*entity)
                    .is_none()
        })
        .collect()
}

fn mutable_level_object_roots(world: &mut World) -> Vec<Entity> {
    level_object_roots(world)
        .into_iter()
        .filter(|entity| {
            world.get::<HydratedPrefabMember>(*entity).is_none()
                && world
                    .get::<crate::scene_io::ComposedSceneMember>(*entity)
                    .is_none()
        })
        .collect()
}

fn entity_tie_break(a: Entity, b: Entity) -> std::cmp::Ordering {
    a.to_bits().cmp(&b.to_bits())
}

pub fn sort_siblings(world: &World, entities: &mut [Entity], sort_mode: HierarchySort) {
    match sort_mode {
        HierarchySort::Manual => {
            entities.sort_by(|a, b| {
                linked_order_class(world, *a)
                    .cmp(&linked_order_class(world, *b))
                    .then_with(|| sibling_index(world, *a).cmp(&sibling_index(world, *b)))
                    .then_with(|| entity_tie_break(*a, *b))
            });
        }
        HierarchySort::Name => {
            entities.sort_by(|a, b| {
                entity_name(world, *a)
                    .cmp(&entity_name(world, *b))
                    .then_with(|| sibling_index(world, *a).cmp(&sibling_index(world, *b)))
                    .then_with(|| entity_tie_break(*a, *b))
            });
        }
        HierarchySort::Type => {
            entities.sort_by(|a, b| {
                actor_kind_sort_key(world, *a)
                    .cmp(&actor_kind_sort_key(world, *b))
                    .then_with(|| entity_name(world, *a).cmp(&entity_name(world, *b)))
                    .then_with(|| sibling_index(world, *a).cmp(&sibling_index(world, *b)))
                    .then_with(|| entity_tie_break(*a, *b))
            });
        }
    }
}

fn linked_order_class(world: &World, entity: Entity) -> u8 {
    if world.get::<HydratedPrefabMember>(entity).is_some()
        || world
            .get::<crate::scene_io::ComposedSceneMember>(entity)
            .is_some()
    {
        0
    } else {
        1
    }
}

fn actor_kind_sort_key(world: &World, entity: Entity) -> u8 {
    world
        .get::<ActorKind>(entity)
        .copied()
        .map(|kind| match kind {
            ActorKind::Empty => 0,
            ActorKind::Brush
            | ActorKind::StaticMesh
            | ActorKind::SkinnedMesh
            | ActorKind::ImportedModel => 1,
            ActorKind::Light => 2,
            ActorKind::AudioSource | ActorKind::AudioListener => 3,
            ActorKind::PrefabAnchor => 3,
            ActorKind::PlayerSpawn => 4,
            ActorKind::WeaponSpawn => 5,
            ActorKind::TriggerVolume => 6,
            ActorKind::PostProcessVolume => 6,
            ActorKind::TeamSpawn => 7,
            ActorKind::Objective => 8,
            ActorKind::Navigation => 6,
        })
        .unwrap_or(9)
}

pub fn entity_hierarchy_path(world: &World, entity: Entity) -> String {
    let mut parts = vec![entity_name(world, entity)];
    let mut current = world
        .get::<ChildOf>(entity)
        .map(|child_of| child_of.parent());
    while let Some(parent) = current {
        if !is_level_object(world, parent) {
            break;
        }
        parts.push(entity_name(world, parent));
        current = world
            .get::<ChildOf>(parent)
            .map(|child_of| child_of.parent());
    }
    parts.reverse();
    parts.join("/")
}

pub fn next_sibling_index(world: &mut World, parent: Option<Entity>) -> i32 {
    let siblings = match parent {
        Some(parent) => authored_children(world, parent),
        None => level_object_roots(world),
    };
    siblings
        .iter()
        .map(|entity| sibling_index(world, *entity))
        .max()
        .map(|max| max + 1)
        .unwrap_or(0)
}

pub fn renumber_siblings(world: &mut World, parent: Option<Entity>, ordered: &[Entity]) {
    for (index, entity) in ordered.iter().enumerate() {
        if let Ok(mut entity_mut) = world.get_entity_mut(*entity) {
            entity_mut.insert(HierarchySiblingIndex(index as i32));
        }
    }
    let _ = parent;
}

pub fn capture_sibling_state(world: &World, entity: Entity) -> SiblingChange {
    let transform = world.get::<Transform>(entity).copied();
    SiblingChange {
        entity,
        old_parent: world
            .get::<ChildOf>(entity)
            .map(|child_of| child_of.parent()),
        new_parent: world
            .get::<ChildOf>(entity)
            .map(|child_of| child_of.parent()),
        old_index: sibling_index(world, entity),
        new_index: sibling_index(world, entity),
        old_transform: transform,
        new_transform: transform,
    }
}

pub fn apply_sibling_change(world: &mut World, change: &SiblingChange) {
    if let Ok(mut entity_mut) = world.get_entity_mut(change.entity) {
        if let Some(parent) = change.old_parent {
            entity_mut.insert(ChildOf(parent));
        } else {
            entity_mut.remove::<ChildOf>();
        }
        entity_mut.insert(HierarchySiblingIndex(change.old_index));
        if let Some(transform) = change.old_transform {
            entity_mut.insert(transform);
        }
    }
}

pub fn apply_sibling_change_new(world: &mut World, change: &SiblingChange) {
    if let Ok(mut entity_mut) = world.get_entity_mut(change.entity) {
        if let Some(parent) = change.new_parent {
            entity_mut.insert(ChildOf(parent));
        } else {
            entity_mut.remove::<ChildOf>();
        }
        entity_mut.insert(HierarchySiblingIndex(change.new_index));
        if let Some(transform) = change.new_transform {
            entity_mut.insert(transform);
        }
    }
}

pub fn reorder_entities_under_parent(
    world: &mut World,
    moving: &[Entity],
    new_parent: Option<Entity>,
    insert_index: i32,
) -> Vec<SiblingChange> {
    let mut changes = Vec::new();
    let old_parents: std::collections::HashSet<Option<Entity>> = moving
        .iter()
        .map(|entity| {
            world
                .get::<ChildOf>(*entity)
                .map(|child_of| child_of.parent())
        })
        .collect();

    changes.extend(renumber_inserted_siblings(
        world,
        moving,
        new_parent,
        insert_index,
    ));

    for old_parent in old_parents {
        if old_parent == new_parent {
            continue;
        }
        changes.extend(renumber_existing_siblings(world, old_parent, moving));
    }

    changes
}

fn renumber_inserted_siblings(
    world: &mut World,
    moving: &[Entity],
    new_parent: Option<Entity>,
    insert_index: i32,
) -> Vec<SiblingChange> {
    let mut siblings = match new_parent {
        Some(parent) => mutable_authored_children(world, parent),
        None => mutable_level_object_roots(world),
    };
    siblings.retain(|entity| !moving.contains(entity));
    let insert = insert_index.clamp(0, siblings.len() as i32) as usize;
    for (offset, entity) in moving.iter().enumerate() {
        siblings.insert(insert + offset, *entity);
    }
    apply_sibling_order(world, new_parent, &siblings)
}

fn renumber_existing_siblings(
    world: &mut World,
    parent: Option<Entity>,
    exclude: &[Entity],
) -> Vec<SiblingChange> {
    let mut siblings = match parent {
        Some(parent) => mutable_authored_children(world, parent),
        None => mutable_level_object_roots(world),
    };
    siblings.retain(|entity| !exclude.contains(entity));
    sort_siblings(world, &mut siblings, HierarchySort::Manual);
    apply_sibling_order(world, parent, &siblings)
}

fn apply_sibling_order(
    world: &mut World,
    parent: Option<Entity>,
    siblings: &[Entity],
) -> Vec<SiblingChange> {
    let mut changes = Vec::new();
    for (index, entity) in siblings.iter().enumerate() {
        let old_parent = world
            .get::<ChildOf>(*entity)
            .map(|child_of| child_of.parent());
        let old_index = sibling_index(world, *entity);
        let new_index = index as i32;
        let old_transform = world.get::<Transform>(*entity).copied();
        let new_transform = if old_parent != parent {
            world
                .get::<GlobalTransform>(*entity)
                .map(|global| {
                    match parent.and_then(|parent| world.get::<GlobalTransform>(parent)) {
                        Some(parent_global) => global.reparented_to(parent_global),
                        None => global.compute_transform(),
                    }
                })
                .or(old_transform)
        } else {
            old_transform
        };
        if old_parent != parent || old_index != new_index {
            changes.push(SiblingChange {
                entity: *entity,
                old_parent,
                new_parent: parent,
                old_index,
                new_index,
                old_transform,
                new_transform,
            });
        }
        if let Ok(mut entity_mut) = world.get_entity_mut(*entity) {
            if let Some(parent) = parent {
                entity_mut.insert(ChildOf(parent));
            } else {
                entity_mut.remove::<ChildOf>();
            }
            entity_mut.insert(HierarchySiblingIndex(new_index));
            if let Some(transform) = new_transform {
                entity_mut.insert(transform);
            }
        }
    }
    changes
}

pub fn backfill_missing_editor_visibility(world: &mut World) {
    let mut query =
        world.query_filtered::<Entity, (With<LevelObject>, Without<EditorVisibility>)>();
    let entities: Vec<Entity> = query.iter(world).collect();
    for entity in entities {
        if let Ok(mut entity_mut) = world.get_entity_mut(entity) {
            entity_mut.insert(EditorVisibility::default());
        }
    }
}

pub fn backfill_missing_sibling_indices(world: &mut World) {
    let mut roots = level_object_roots(world);
    if roots
        .iter()
        .any(|entity| world.get::<HierarchySiblingIndex>(*entity).is_none())
    {
        sort_siblings(world, &mut roots, HierarchySort::Manual);
        renumber_siblings(world, None, &roots);
    }
    for root in roots {
        backfill_children(world, root);
    }
}

fn backfill_children(world: &mut World, parent: Entity) {
    let mut children = authored_children(world, parent);
    if children
        .iter()
        .any(|entity| world.get::<HierarchySiblingIndex>(*entity).is_none())
    {
        sort_siblings(world, &mut children, HierarchySort::Manual);
        renumber_siblings(world, Some(parent), &children);
    }
    for child in children {
        backfill_children(world, child);
    }
}

pub fn actor_kind_icon(kind: ActorKind) -> &'static str {
    use egui_phosphor_icons::icons;
    match kind {
        ActorKind::Empty => icons::FOLDER.as_str(),
        ActorKind::Brush => icons::CUBE.as_str(),
        ActorKind::StaticMesh => icons::CUBE.as_str(),
        ActorKind::SkinnedMesh => icons::PERSON_SIMPLE_RUN.as_str(),
        ActorKind::ImportedModel => icons::CUBE_TRANSPARENT.as_str(),
        ActorKind::Light => icons::LIGHTBULB.as_str(),
        ActorKind::AudioSource => icons::SPEAKER_HIGH.as_str(),
        ActorKind::AudioListener => icons::HEADPHONES.as_str(),
        ActorKind::PrefabAnchor => icons::PACKAGE.as_str(),
        ActorKind::PlayerSpawn => icons::USER_CIRCLE.as_str(),
        ActorKind::WeaponSpawn => icons::CROSSHAIR.as_str(),
        ActorKind::TriggerVolume => icons::BROADCAST.as_str(),
        ActorKind::PostProcessVolume => icons::CAMERA.as_str(),
        ActorKind::TeamSpawn => icons::FLAG.as_str(),
        ActorKind::Objective => icons::TARGET.as_str(),
        ActorKind::Navigation => icons::PATH.as_str(),
    }
}

pub struct HierarchyLabel {
    pub icon: &'static str,
    pub text: String,
}

pub fn hierarchy_label_for_entity(
    world: &World,
    entity: Entity,
    kind: HierarchyNodeKind,
) -> HierarchyLabel {
    use egui_phosphor_icons::icons;
    let icon = world
        .get::<ActorKind>(entity)
        .copied()
        .map(actor_kind_icon)
        .unwrap_or_else(|| match kind {
            HierarchyNodeKind::Authored => icons::CUBE.as_str(),
            HierarchyNodeKind::Generated => icons::STACK.as_str(),
            HierarchyNodeKind::Runtime => icons::CPU.as_str(),
        });
    let mut text = entity_name(world, entity);
    if let Some(light) = world.get::<LightDesc>(entity) {
        if matches!(light.kind, AuthoringLightKind::Directional) {
            text.push_str("  (sun)");
        }
    }
    match kind {
        HierarchyNodeKind::Authored => {}
        HierarchyNodeKind::Generated => text.push_str("  (generated)"),
        HierarchyNodeKind::Runtime => text.push_str("  (runtime)"),
    }
    HierarchyLabel { icon, text }
}

pub fn entity_matches_filter(world: &World, entity: Entity, filter_lower: &str) -> bool {
    if filter_lower.is_empty() {
        return true;
    }
    if entity_name(world, entity)
        .to_lowercase()
        .contains(filter_lower)
    {
        return true;
    }
    if let Some(kind) = world.get::<ActorKind>(entity) {
        if format!("{kind:?}").to_lowercase().contains(filter_lower) {
            return true;
        }
    }
    let tags = [
        world.get::<LightDesc>(entity).is_some().then_some("light"),
        world
            .get::<PlayerSpawn>(entity)
            .is_some()
            .then_some("spawn"),
        world.get::<ModelRef>(entity).is_some().then_some("model"),
        world
            .get::<SkinnedMeshRenderer>(entity)
            .is_some()
            .then_some("skinned model"),
        world
            .get::<PhysicsBody>(entity)
            .is_some()
            .then_some("physics"),
        world
            .get::<shared::PostProcessVolumeDesc>(entity)
            .is_some()
            .then_some("postprocess volume"),
    ];
    tags.into_iter()
        .flatten()
        .any(|tag| tag.contains(filter_lower))
}

pub fn editor_visibility(world: &World, entity: Entity) -> EditorVisibility {
    world
        .get::<EditorVisibility>(entity)
        .copied()
        .unwrap_or_default()
}

pub fn is_entity_locked(locked: &std::collections::HashSet<Entity>, entity: Entity) -> bool {
    locked.contains(&entity)
}

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

    #[test]
    fn sort_by_name_is_stable_for_duplicate_names() {
        let mut world = World::new();
        let a = world.spawn((LevelObject, Name::new("Pillar"))).id();
        let b = world.spawn((LevelObject, Name::new("Pillar"))).id();
        let mut entities = vec![a, b];

        sort_siblings(&world, &mut entities, HierarchySort::Name);
        let first = entities.clone();

        sort_siblings(&world, &mut entities, HierarchySort::Name);
        assert_eq!(entities, first, "duplicate-name sort must be deterministic");
    }

    #[test]
    fn reparent_preserves_world_transform_and_round_trips_history() {
        let mut world = World::new();
        let parent = world
            .spawn((
                LevelObject,
                Transform::from_xyz(10.0, 0.0, 0.0),
                GlobalTransform::from_xyz(10.0, 0.0, 0.0),
                HierarchySiblingIndex(0),
            ))
            .id();
        let child = world
            .spawn((
                LevelObject,
                Transform::from_xyz(12.0, 1.0, -3.0),
                GlobalTransform::from_xyz(12.0, 1.0, -3.0),
                HierarchySiblingIndex(1),
            ))
            .id();

        let changes = reorder_entities_under_parent(&mut world, &[child], Some(parent), 0);
        let child_change = changes
            .iter()
            .find(|change| change.entity == child)
            .expect("reparent should record the moved actor");

        assert_eq!(world.get::<ChildOf>(child).unwrap().parent(), parent);
        assert_eq!(
            world.get::<Transform>(child).unwrap().translation,
            Vec3::new(2.0, 1.0, -3.0)
        );

        apply_sibling_change(&mut world, child_change);
        assert!(world.get::<ChildOf>(child).is_none());
        assert_eq!(
            world.get::<Transform>(child).unwrap().translation,
            Vec3::new(12.0, 1.0, -3.0)
        );

        apply_sibling_change_new(&mut world, child_change);
        assert_eq!(world.get::<ChildOf>(child).unwrap().parent(), parent);
        assert_eq!(
            world.get::<Transform>(child).unwrap().translation,
            Vec3::new(2.0, 1.0, -3.0)
        );
    }

    #[test]
    fn cycle_detection_rejects_descendant_parent() {
        let mut world = World::new();
        let parent = world.spawn(LevelObject).id();
        let child = world.spawn((LevelObject, ChildOf(parent))).id();

        assert!(would_create_cycle(&world, &[parent], Some(child)));
        assert!(!would_create_cycle(&world, &[child], None));
    }

    #[test]
    fn prefab_boundary_allows_local_and_same_layer_override_structure() {
        let mut world = World::new();
        let instance = world.spawn((LevelObject, PrefabInstance::default())).id();
        let local_child = world.spawn((LevelObject, ChildOf(instance))).id();
        let generated_child = world
            .spawn((
                LevelObject,
                ActorId::new("generated-child"),
                ChildOf(instance),
                HydratedPrefabMember {
                    instance_root: instance,
                },
            ))
            .id();
        let actor = world.spawn(LevelObject).id();
        let nested_instance = world.spawn((LevelObject, PrefabInstance::default())).id();

        assert_eq!(
            hierarchy_drop_violation(&world, &[actor], Some(instance)),
            None
        );
        assert_eq!(
            hierarchy_drop_violation(&world, &[nested_instance], Some(local_child)),
            None
        );
        assert_eq!(
            hierarchy_drop_violation(&world, &[nested_instance], Some(generated_child)),
            Some(HierarchyDropViolation::PrefabBoundary { root: instance })
        );
        assert_eq!(
            hierarchy_drop_violation(&world, &[generated_child], None),
            None
        );
        assert_eq!(hierarchy_drop_violation(&world, &[instance], None), None);
        assert_eq!(hierarchy_drop_violation(&world, &[local_child], None), None);
    }
}
