use super::*;

pub(super) fn component_category_label(category: EditorComponentCategory) -> &'static str {
    match category {
        EditorComponentCategory::Authoring => "Authoring",
        EditorComponentCategory::Rendering => "Rendering",
        EditorComponentCategory::Animation => "Animation",
        EditorComponentCategory::Audio => "Audio",
        EditorComponentCategory::Navigation => "Navigation",
        EditorComponentCategory::Physics => "Physics",
        EditorComponentCategory::Gameplay => "Gameplay",
        EditorComponentCategory::Volumes => "Volumes",
        EditorComponentCategory::Editor => "Editor",
    }
}

pub(super) struct ComponentAddState {
    pub(super) addable: bool,
    reason: Option<String>,
    required: Vec<&'static str>,
    recommended: Vec<&'static str>,
    conflicts: Vec<&'static str>,
}

pub(super) fn descriptor_matches_search(
    descriptor: &EditorComponentDescriptor,
    search: &str,
) -> bool {
    search.trim().is_empty()
        || descriptor.display_name.to_lowercase().contains(search)
        || descriptor.type_name.to_lowercase().contains(search)
        || descriptor.description.to_lowercase().contains(search)
        || descriptor
            .search_terms
            .iter()
            .any(|term| term.to_lowercase().contains(search))
}

pub(super) fn component_add_state(
    world: &World,
    entity: Entity,
    descriptor: &EditorComponentDescriptor,
    descriptors: &[EditorComponentDescriptor],
) -> ComponentAddState {
    let duplicate = component_present(world, entity, descriptor.type_name);
    let conflicts = descriptor
        .conflicts_with
        .iter()
        .copied()
        .filter(|type_name| component_present(world, entity, type_name))
        .collect::<Vec<_>>();
    let recommended = descriptor
        .recommended
        .iter()
        .copied()
        .filter(|type_name| !component_present(world, entity, type_name))
        .collect::<Vec<_>>();
    let registry = world.resource::<EditorComponentRegistry>();
    let required = registry
        .required_component_ids(descriptor)
        .iter()
        .filter_map(|id| registry.by_id(id))
        .filter(|required| !registry.component_present(world, entity, required.type_name))
        .map(|required| required.display_name)
        .collect::<Vec<_>>();
    let reason = if duplicate {
        Some("Already present on this actor.".to_string())
    } else if !required.is_empty() {
        Some(format!("Requires {}.", required.join(", ")))
    } else if !conflicts.is_empty() {
        Some(format!(
            "Conflicts with {}.",
            conflicts
                .iter()
                .map(|type_name| component_display_name(descriptors, type_name))
                .collect::<Vec<_>>()
                .join(", ")
        ))
    } else {
        None
    };
    ComponentAddState {
        addable: reason.is_none(),
        reason,
        required,
        recommended,
        conflicts,
    }
}

pub(super) fn component_hover_text(
    descriptor: &EditorComponentDescriptor,
    state: &ComponentAddState,
) -> String {
    let mut lines = vec![
        descriptor.description.to_string(),
        format!("Type: {}", descriptor.type_name),
        format!("Hydration: {}", descriptor.hydration_effect),
        format!(
            "Inspector: removable={} reorderable={}",
            descriptor.removable, descriptor.reorderable
        ),
    ];
    if let Some(reason) = &state.reason {
        lines.push(format!("Unavailable: {reason}"));
    }
    if !state.required.is_empty() {
        lines.push(format!("Requires: {}", state.required.join(", ")));
    }
    if !state.recommended.is_empty() {
        lines.push(format!(
            "Recommended with: {}",
            state.recommended.join(", ")
        ));
    }
    if !state.conflicts.is_empty() {
        lines.push(format!("Conflicts: {}", state.conflicts.join(", ")));
    }
    lines.push(format!("Search: {}", descriptor.search_terms.join(", ")));
    lines.join("\n")
}

pub(super) fn component_display_name(
    descriptors: &[EditorComponentDescriptor],
    type_name: &str,
) -> &'static str {
    descriptors
        .iter()
        .find(|descriptor| descriptor.type_name == type_name)
        .map(|descriptor| descriptor.display_name)
        .unwrap_or("component")
}

pub(super) fn component_present(world: &World, entity: Entity, type_name: &str) -> bool {
    if let Some(registry) = world.get_resource::<EditorComponentRegistry>() {
        if registry.by_type_name(type_name).is_some() {
            return registry.component_present(world, entity, type_name);
        }
    }
    match type_name {
        COMPONENT_ANIMATION_CONTROLLER_DESC => {
            world.get::<AnimationControllerDesc>(entity).is_some()
        }
        "shared::components::Primitive" => world.get::<Primitive>(entity).is_some(),
        "shared::components::BrushDesc" => world.get::<BrushDesc>(entity).is_some(),
        "shared::components::StaticMeshRenderer" => {
            world.get::<StaticMeshRenderer>(entity).is_some()
        }
        COMPONENT_SKINNED_MESH_RENDERER => world.get::<SkinnedMeshRenderer>(entity).is_some(),
        "shared::components::MaterialDesc" => {
            world.get::<MaterialDesc>(entity).is_some() && world.get::<BrushDesc>(entity).is_some()
        }
        "shared::components::LightDesc" => world.get::<LightDesc>(entity).is_some(),
        "shared::components::AudioSourceDesc" => world.get::<AudioSourceDesc>(entity).is_some(),
        "shared::components::AudioListenerDesc" => world.get::<AudioListenerDesc>(entity).is_some(),
        "shared::components::RigidBodyDesc" => world.get::<RigidBodyDesc>(entity).is_some(),
        "shared::components::ColliderDesc" => world.get::<ColliderDesc>(entity).is_some(),
        "shared::components::PlayerSpawn" => world.get::<PlayerSpawn>(entity).is_some(),
        "shared::components::WeaponSpawn" => world.get::<WeaponSpawn>(entity).is_some(),
        "shared::components::TriggerVolume" => world.get::<TriggerVolume>(entity).is_some(),
        "shared::components::TeamSpawn" => world.get::<TeamSpawn>(entity).is_some(),
        "shared::components::ObjectiveMarker" => world.get::<ObjectiveMarker>(entity).is_some(),
        "shared::components::PostProcessVolumeDesc" => {
            world.get::<PostProcessVolumeDesc>(entity).is_some()
        }
        COMPONENT_NAVIGATION_BOUNDS => world.get::<NavigationBounds>(entity).is_some(),
        COMPONENT_NAVIGATION_OBSTACLE => world.get::<NavigationObstacle>(entity).is_some(),
        COMPONENT_NAVIGATION_AREA => world.get::<NavigationArea>(entity).is_some(),
        COMPONENT_NAVIGATION_LINK => world.get::<NavigationLink>(entity).is_some(),
        _ => false,
    }
}

pub(super) fn insert_registered_component(world: &mut World, entity: Entity, type_name: &str) {
    let descriptor = world
        .resource::<EditorComponentRegistry>()
        .by_type_name(type_name)
        .cloned();
    if let Some(descriptor) = descriptor {
        if !descriptor.addable || component_present(world, entity, descriptor.type_name) {
            return;
        }
        if descriptor.type_name == COMPONENT_ANIMATION_CONTROLLER_DESC {
            let controller =
                crate::ui::animation_inspector::default_controller_for_actor(world, entity);
            let _ = crate::history::reflected_component_transaction(
                world,
                entity,
                "Add Component",
                descriptor.id,
                descriptor.type_name,
                move |world, entity| {
                    world.entity_mut(entity).insert(controller);
                    Ok(())
                },
            );
        } else if descriptor.type_name == COMPONENT_NAVIGATION_BOUNDS {
            let bounds = navigation_bounds_for_entity(world, entity);
            let _ = crate::history::reflected_component_transaction(
                world,
                entity,
                "Add Component",
                descriptor.id,
                descriptor.type_name,
                move |world, entity| {
                    world.entity_mut(entity).insert(bounds);
                    Ok(())
                },
            );
        } else {
            let _ = crate::history::reflected_component_transaction(
                world,
                entity,
                "Add Component",
                descriptor.id,
                descriptor.type_name,
                move |world, entity| {
                    crate::history::apply_reflected_default(world, entity, descriptor.type_name)
                },
            );
        }
        return;
    }
    match type_name {
        COMPONENT_ANIMATION_CONTROLLER_DESC => {
            let controller =
                crate::ui::animation_inspector::default_controller_for_actor(world, entity);
            insert_component(world, entity, move |world, e| {
                world.entity_mut(e).insert(controller);
            });
        }
        "shared::components::Primitive" => insert_component(world, entity, |world, e| {
            world
                .entity_mut(e)
                .insert((shared::ActorKind::StaticMesh, Primitive::cuboid(Vec3::ONE)));
        }),
        "shared::components::BrushDesc" => insert_component(world, entity, |world, e| {
            world
                .entity_mut(e)
                .insert((shared::ActorKind::Brush, BrushDesc::default()));
        }),
        "shared::components::StaticMeshRenderer" => insert_component(world, entity, |world, e| {
            world
                .entity_mut(e)
                .insert((shared::ActorKind::StaticMesh, StaticMeshRenderer::default()));
        }),
        "shared::components::MaterialDesc" => insert_component(world, entity, |world, e| {
            world.entity_mut(e).insert(MaterialDesc::default());
        }),
        "shared::components::LightDesc" => insert_component(world, entity, |world, e| {
            world.entity_mut(e).insert(LightDesc::default());
        }),
        "shared::components::AudioSourceDesc" => insert_component(world, entity, |world, e| {
            world
                .entity_mut(e)
                .insert((shared::ActorKind::AudioSource, AudioSourceDesc::default()));
        }),
        "shared::components::AudioListenerDesc" => insert_component(world, entity, |world, e| {
            world.entity_mut(e).insert((
                shared::ActorKind::AudioListener,
                AudioListenerDesc::default(),
            ));
        }),
        "shared::components::RigidBodyDesc" => insert_component(world, entity, |world, e| {
            world.entity_mut(e).insert(RigidBodyDesc::default());
        }),
        "shared::components::ColliderDesc" => insert_component(world, entity, |world, e| {
            world.entity_mut(e).insert(ColliderDesc::default());
        }),
        "shared::components::PlayerSpawn" => insert_component(world, entity, |world, e| {
            world.entity_mut(e).insert(PlayerSpawn);
        }),
        "shared::components::WeaponSpawn" => insert_component(world, entity, |world, e| {
            world.entity_mut(e).insert(WeaponSpawn {
                weapon_id: "rifle".into(),
            });
        }),
        "shared::components::TriggerVolume" => insert_component(world, entity, |world, e| {
            world.entity_mut(e).insert(TriggerVolume::default());
        }),
        "shared::components::TeamSpawn" => insert_component(world, entity, |world, e| {
            world.entity_mut(e).insert(TeamSpawn { team_id: 0 });
        }),
        "shared::components::ObjectiveMarker" => insert_component(world, entity, |world, e| {
            world.entity_mut(e).insert(ObjectiveMarker {
                objective_id: "objective".into(),
            });
        }),
        COMPONENT_NAVIGATION_BOUNDS => insert_component(world, entity, |world, e| {
            let bounds = navigation_bounds_for_entity(world, e);
            world.entity_mut(e).insert((ActorKind::Navigation, bounds));
        }),
        COMPONENT_NAVIGATION_OBSTACLE => insert_component(world, entity, |world, e| {
            world
                .entity_mut(e)
                .insert((ActorKind::Navigation, NavigationObstacle::default()));
        }),
        COMPONENT_NAVIGATION_AREA => insert_component(world, entity, |world, e| {
            world
                .entity_mut(e)
                .insert((ActorKind::Navigation, NavigationArea::default()));
        }),
        COMPONENT_NAVIGATION_LINK => insert_component(world, entity, |world, e| {
            world
                .entity_mut(e)
                .insert((ActorKind::Navigation, NavigationLink::default()));
        }),
        "shared::components::PostProcessVolumeDesc" => {
            insert_component(world, entity, |world, e| {
                world.entity_mut(e).insert(PostProcessVolumeDesc::default());
            })
        }
        _ => {}
    }
}

pub(super) fn navigation_bounds_for_entity(world: &World, entity: Entity) -> NavigationBounds {
    world
        .get::<ActorId>(entity)
        .map(|actor_id| NavigationBounds::for_actor(&actor_id.0))
        .unwrap_or_else(|| NavigationBounds::for_actor(&uuid::Uuid::new_v4().to_string()))
}

pub(super) fn reset_navigation_bounds_for_entity(
    world: &World,
    entity: Entity,
) -> NavigationBounds {
    let mut bounds = navigation_bounds_for_entity(world, entity);
    if let Some(current) = world.get::<NavigationBounds>(entity) {
        bounds.artifact_path.clone_from(&current.artifact_path);
    }
    bounds
}

pub(super) fn insert_component(
    world: &mut World,
    entity: Entity,
    insert: impl FnOnce(&mut World, Entity),
) {
    let before = crate::history::snapshot_entity(world, entity);
    let old_kind = before.as_ref().map(|s| s.actor_kind);
    insert(world, entity);
    let after = crate::history::snapshot_entity(world, entity);
    if let (Some(before), Some(after)) = (before, after) {
        if let Some(old) = old_kind {
            if old != after.actor_kind {
                crate::history::set_actor_kind_with_history(world, entity, old, after.actor_kind);
            }
        }
        crate::history::push_command(
            world,
            crate::history::EditorCommand::AddComponent {
                entity,
                snapshot: diff_added(&before, &after),
            },
        );
    }
}

pub(super) fn diff_added(
    before: &EditorEntitySnapshot,
    after: &EditorEntitySnapshot,
) -> EditorEntitySnapshot {
    EditorEntitySnapshot {
        actor_id: None,
        actor_kind: after.actor_kind,
        actor_name: None,
        name: None,
        transform: after.transform,
        primitive: after
            .primitive
            .clone()
            .filter(|_| before.primitive.is_none()),
        brush: after.brush.clone().filter(|_| before.brush.is_none()),
        static_mesh_renderer: after
            .static_mesh_renderer
            .clone()
            .filter(|_| before.static_mesh_renderer.is_none()),
        skinned_mesh_renderer: after
            .skinned_mesh_renderer
            .clone()
            .filter(|_| before.skinned_mesh_renderer.is_none()),
        material: after.material.clone().filter(|_| before.material.is_none()),
        material_override: after
            .material_override
            .clone()
            .filter(|_| before.material_override.is_none()),
        rigid_body: after.rigid_body.filter(|_| before.rigid_body.is_none()),
        collider: after.collider.clone().filter(|_| before.collider.is_none()),
        physics: after.physics.clone().filter(|_| before.physics.is_none()),
        light: after.light.clone().filter(|_| before.light.is_none()),
        animation_controller: after
            .animation_controller
            .clone()
            .filter(|_| before.animation_controller.is_none()),
        audio_source: after
            .audio_source
            .clone()
            .filter(|_| before.audio_source.is_none()),
        audio_listener: after
            .audio_listener
            .filter(|_| before.audio_listener.is_none()),
        player_spawn: after.player_spawn && !before.player_spawn,
        model: after.model.clone().filter(|_| before.model.is_none()),
        prefab: after.prefab.clone().filter(|_| before.prefab.is_none()),
        prefab_instance: after
            .prefab_instance
            .clone()
            .filter(|_| before.prefab_instance.is_none()),
        weapon_spawn: after
            .weapon_spawn
            .clone()
            .filter(|_| before.weapon_spawn.is_none()),
        trigger_volume: after
            .trigger_volume
            .clone()
            .filter(|_| before.trigger_volume.is_none()),
        post_process_volume: after
            .post_process_volume
            .clone()
            .filter(|_| before.post_process_volume.is_none()),
        team_spawn: after
            .team_spawn
            .clone()
            .filter(|_| before.team_spawn.is_none()),
        objective: after
            .objective
            .clone()
            .filter(|_| before.objective.is_none()),
        navigation_bounds: after
            .navigation_bounds
            .clone()
            .filter(|_| before.navigation_bounds.is_none()),
        navigation_obstacle: after
            .navigation_obstacle
            .clone()
            .filter(|_| before.navigation_obstacle.is_none()),
        navigation_area: after
            .navigation_area
            .clone()
            .filter(|_| before.navigation_area.is_none()),
        navigation_link: after
            .navigation_link
            .clone()
            .filter(|_| before.navigation_link.is_none()),
        hierarchy_sibling_index: after.hierarchy_sibling_index,
        editor_visibility: after.editor_visibility,
        inspector_order: None,
        component_states: None,
        children: Vec::new(),
    }
}
