use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::ecs::bounding_volume::components::BoundingVolume;
use crate::ecs::decal::Decal;
use crate::ecs::graphics::resources::Atmosphere;
use crate::ecs::lines::components::Line;
use crate::ecs::navmesh::NavMeshAgent;
use crate::ecs::primitives::RenderLayer;
use crate::ecs::text::Text;
use crate::ecs::transform::LocalTransform;

use super::animation::SceneAnimationPlayer;
use super::asset_uuid::AssetUuid;
use super::audio::{EmbeddedAudio, EmbeddedTexture, SceneAudioSource, ScenePrefabInstance};
use super::character_controller::SceneCharacterController;
use super::lighting::{SceneCamera, SceneLight};
use super::material::SceneMaterial;
use super::mesh::{EmbeddedMesh, SceneInstancedMesh};
use super::navigation::SceneNavMesh;
use super::particles::SceneParticleEmitter;
use super::physics::{SceneJointConnection, ScenePhysics};
use super::settings::SceneSettings;
use super::typed::{TypedComponent, TypedResource};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum MetadataValue {
    Bool(bool),
    Integer(i64),
    Float(f64),
    String(String),
    Array(Vec<MetadataValue>),
    Map(HashMap<String, MetadataValue>),
}

pub const SCENE_FORMAT_VERSION: u32 = 1;
pub const ENGINE_VERSION: &str = env!("CARGO_PKG_VERSION");

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum AssetMode {
    #[default]
    Referenced,
    Embedded,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneHeader {
    pub format_version: u32,
    pub engine_version: String,
    pub name: String,
    pub asset_mode: AssetMode,
    /// Stable identifier for `.nsprefab` files. None for map scenes;
    /// minted when a Scene is saved as a prefab so instances can link
    /// back to the source file regardless of path changes.
    #[serde(default)]
    pub prefab_id: Option<AssetUuid>,
}

impl Default for SceneHeader {
    fn default() -> Self {
        Self {
            format_version: SCENE_FORMAT_VERSION,
            engine_version: ENGINE_VERSION.to_string(),
            name: String::new(),
            asset_mode: AssetMode::Referenced,
            prefab_id: None,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct ChunkId(pub u32);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct LayerId(pub u32);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneLayerConfig {
    pub id: LayerId,
    pub name: String,
    #[serde(default)]
    pub enabled: bool,
    #[serde(default)]
    pub order: i32,
}

impl SceneLayerConfig {
    pub fn new(id: u32, name: impl Into<String>) -> Self {
        Self {
            id: LayerId(id),
            name: name.into(),
            enabled: true,
            order: 0,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneChunkConfig {
    pub id: ChunkId,
    pub name: String,
    pub bounds_min: [f32; 3],
    pub bounds_max: [f32; 3],
    #[serde(default)]
    pub loaded: bool,
    #[serde(default)]
    pub load_distance: f32,
    #[serde(default)]
    pub unload_distance: f32,
}

impl SceneChunkConfig {
    pub fn new(
        id: u32,
        name: impl Into<String>,
        bounds_min: [f32; 3],
        bounds_max: [f32; 3],
    ) -> Self {
        Self {
            id: ChunkId(id),
            name: name.into(),
            bounds_min,
            bounds_max,
            loaded: true,
            load_distance: 100.0,
            unload_distance: 150.0,
        }
    }
}

impl Default for SceneChunkConfig {
    fn default() -> Self {
        Self {
            id: ChunkId(0),
            name: String::new(),
            bounds_min: [-f32::INFINITY, -f32::INFINITY, -f32::INFINITY],
            bounds_max: [f32::INFINITY, f32::INFINITY, f32::INFINITY],
            loaded: true,
            load_distance: 100.0,
            unload_distance: 150.0,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SceneChunk {
    pub config: SceneChunkConfig,
    pub entities: Vec<SceneEntity>,
    #[serde(default)]
    pub joints: Vec<SceneJointConnection>,
}

impl SceneChunk {
    pub fn new(config: SceneChunkConfig) -> Self {
        Self {
            config,
            entities: Vec::new(),
            joints: Vec::new(),
        }
    }

    pub fn add_entity(&mut self, mut entity: SceneEntity) -> AssetUuid {
        entity.chunk_id = Some(self.config.id);
        let uuid = entity.uuid;
        self.entities.push(entity);
        uuid
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct Scene {
    pub header: SceneHeader,
    pub atmosphere: Atmosphere,
    #[serde(default)]
    pub hdr_skybox: Option<SceneHdrSkybox>,
    #[serde(default)]
    pub settings: SceneSettings,
    pub entities: Vec<SceneEntity>,
    #[serde(default)]
    pub joints: Vec<SceneJointConnection>,
    #[serde(default)]
    pub layers: Vec<SceneLayerConfig>,
    #[serde(default)]
    pub chunks: Vec<SceneChunkConfig>,
    #[serde(default)]
    pub embedded_meshes: HashMap<AssetUuid, EmbeddedMesh>,
    #[serde(default)]
    pub embedded_textures: HashMap<AssetUuid, EmbeddedTexture>,
    #[serde(default)]
    pub embedded_audio: HashMap<AssetUuid, EmbeddedAudio>,
    #[serde(default)]
    pub metadata: HashMap<String, MetadataValue>,
    /// Typed game-side resources (singletons) serialized for this
    /// scene. Same typed-JSON channel as `game_components` but for
    /// non-entity state like wallets, quest progress, or score.
    #[serde(default)]
    pub game_resources: Vec<TypedResource>,
    /// Highest guid value the writer minted. The loader seeds
    /// `EntityRegistry::next_guid` from this so subsequent spawns
    /// don't alias an authored id.
    #[serde(default)]
    pub next_guid: u64,
    #[serde(default)]
    pub navmesh: Option<SceneNavMesh>,
    #[serde(skip)]
    pub spawn_order: Vec<AssetUuid>,
    #[serde(skip)]
    pub uuid_index: HashMap<AssetUuid, usize>,
    #[serde(skip)]
    pub chunk_index: HashMap<ChunkId, Vec<usize>>,
}

impl Scene {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            header: SceneHeader {
                format_version: SCENE_FORMAT_VERSION,
                engine_version: ENGINE_VERSION.to_string(),
                name: name.into(),
                asset_mode: AssetMode::Referenced,
                prefab_id: None,
            },
            ..Default::default()
        }
    }

    pub fn add_entity(&mut self, entity: SceneEntity) -> AssetUuid {
        let uuid = entity.uuid;
        let index = self.entities.len();
        self.uuid_index.insert(uuid, index);
        self.entities.push(entity);
        uuid
    }

    pub fn find_entity(&self, uuid: AssetUuid) -> Option<&SceneEntity> {
        if let Some(&index) = self.uuid_index.get(&uuid) {
            self.entities.get(index)
        } else {
            self.entities.iter().find(|entity| entity.uuid == uuid)
        }
    }

    pub fn find_entity_mut(&mut self, uuid: AssetUuid) -> Option<&mut SceneEntity> {
        if let Some(&index) = self.uuid_index.get(&uuid) {
            self.entities.get_mut(index)
        } else {
            self.entities.iter_mut().find(|entity| entity.uuid == uuid)
        }
    }

    pub fn root_entities(&self) -> impl Iterator<Item = &SceneEntity> {
        self.entities
            .iter()
            .filter(|entity| entity.parent.is_none())
    }

    pub fn children_of(&self, parent_uuid: AssetUuid) -> impl Iterator<Item = &SceneEntity> {
        self.entities
            .iter()
            .filter(move |entity| entity.parent == Some(parent_uuid))
    }

    pub fn add_joint(&mut self, joint: SceneJointConnection) {
        self.joints.push(joint);
    }

    pub fn rebuild_uuid_index(&mut self) {
        self.uuid_index.clear();
        self.chunk_index.clear();
        for (index, entity) in self.entities.iter().enumerate() {
            self.uuid_index.insert(entity.uuid, index);
            if let Some(chunk_id) = entity.chunk_id {
                self.chunk_index.entry(chunk_id).or_default().push(index);
            }
        }
    }

    pub fn compute_spawn_order(&mut self) {
        let uuid_set: std::collections::HashSet<AssetUuid> =
            self.entities.iter().map(|entity| entity.uuid).collect();

        let mut result: Vec<AssetUuid> = Vec::with_capacity(self.entities.len());
        let mut visited: std::collections::HashSet<AssetUuid> = std::collections::HashSet::new();
        let mut in_stack: std::collections::HashSet<AssetUuid> = std::collections::HashSet::new();

        let entity_map: HashMap<AssetUuid, &SceneEntity> = self
            .entities
            .iter()
            .map(|entity| (entity.uuid, entity))
            .collect();

        fn visit(
            uuid: AssetUuid,
            entity_map: &HashMap<AssetUuid, &SceneEntity>,
            uuid_set: &std::collections::HashSet<AssetUuid>,
            visited: &mut std::collections::HashSet<AssetUuid>,
            in_stack: &mut std::collections::HashSet<AssetUuid>,
            result: &mut Vec<AssetUuid>,
        ) {
            if visited.contains(&uuid) {
                return;
            }

            if in_stack.contains(&uuid) {
                return;
            }

            in_stack.insert(uuid);

            if let Some(entity) = entity_map.get(&uuid)
                && let Some(parent_uuid) = entity.parent
                && uuid_set.contains(&parent_uuid)
            {
                visit(parent_uuid, entity_map, uuid_set, visited, in_stack, result);
            }

            in_stack.remove(&uuid);
            visited.insert(uuid);
            result.push(uuid);
        }

        for entity in &self.entities {
            visit(
                entity.uuid,
                &entity_map,
                &uuid_set,
                &mut visited,
                &mut in_stack,
                &mut result,
            );
        }

        self.spawn_order = result;
    }

    pub fn remove_entity(&mut self, uuid: AssetUuid) -> Option<SceneEntity> {
        if let Some(index) = self.uuid_index.remove(&uuid) {
            let entity = self.entities.remove(index);
            for (existing_uuid, existing_index) in self.uuid_index.iter_mut() {
                if *existing_index > index {
                    *existing_index -= 1;
                }
                let _ = existing_uuid;
            }
            Some(entity)
        } else if let Some(pos) = self.entities.iter().position(|entity| entity.uuid == uuid) {
            Some(self.entities.remove(pos))
        } else {
            None
        }
    }

    pub fn add_layer(&mut self, layer: SceneLayerConfig) {
        self.layers.push(layer);
    }

    pub fn add_chunk(&mut self, chunk: SceneChunkConfig) {
        self.chunks.push(chunk);
    }

    pub fn get_layer(&self, layer_id: LayerId) -> Option<&SceneLayerConfig> {
        self.layers.iter().find(|layer| layer.id == layer_id)
    }

    pub fn get_chunk(&self, chunk_id: ChunkId) -> Option<&SceneChunkConfig> {
        self.chunks.iter().find(|chunk| chunk.id == chunk_id)
    }

    pub fn entities_in_layer(&self, layer_id: LayerId) -> impl Iterator<Item = &SceneEntity> {
        self.entities
            .iter()
            .filter(move |entity| entity.layer == Some(layer_id))
    }

    pub fn entities_in_chunk(&self, chunk_id: ChunkId) -> impl Iterator<Item = &SceneEntity> {
        self.chunk_index
            .get(&chunk_id)
            .into_iter()
            .flatten()
            .filter_map(|&index| self.entities.get(index))
    }

    pub fn extract_chunk(&self, chunk_id: ChunkId) -> SceneChunk {
        let config = self
            .get_chunk(chunk_id)
            .cloned()
            .unwrap_or_else(|| SceneChunkConfig {
                id: chunk_id,
                ..Default::default()
            });

        let entities: Vec<SceneEntity> = self
            .entities
            .iter()
            .filter(|entity| entity.chunk_id == Some(chunk_id))
            .cloned()
            .collect();

        let entity_uuids: std::collections::HashSet<AssetUuid> =
            entities.iter().map(|entity| entity.uuid).collect();

        let joints: Vec<SceneJointConnection> = self
            .joints
            .iter()
            .filter(|joint| {
                entity_uuids.contains(&joint.parent_entity)
                    || entity_uuids.contains(&joint.child_entity)
            })
            .cloned()
            .collect();

        SceneChunk {
            config,
            entities,
            joints,
        }
    }

    pub fn merge_chunk(&mut self, chunk: SceneChunk) {
        if !self.chunks.iter().any(|c| c.id == chunk.config.id) {
            self.chunks.push(chunk.config);
        }

        for entity in chunk.entities {
            self.add_entity(entity);
        }

        for joint in chunk.joints {
            self.joints.push(joint);
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SceneHdrSkybox {
    Embedded { data: Vec<u8> },
    Reference { path: String },
    Asset { uuid: AssetUuid },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneEntity {
    pub uuid: AssetUuid,
    #[serde(default)]
    pub parent: Option<AssetUuid>,
    #[serde(default)]
    pub name: Option<String>,
    pub transform: LocalTransform,
    #[serde(default)]
    pub layer: Option<LayerId>,
    #[serde(default)]
    pub chunk_id: Option<ChunkId>,
    /// Optional stable identity. Present iff the live entity carried a
    /// `Guid` component at save time. Loader restores it via
    /// `assign_guid` so cross-references resolve.
    #[serde(default)]
    pub guid: Option<u64>,
    /// Set on entities that originated from a `.nsprefab` instance: the
    /// uuid this entity had inside the source prefab file before remap.
    /// Lets editors pair a live entity with its source `SceneEntity`
    /// for refresh-from-source and override tracking across save/load.
    #[serde(default)]
    pub source_entity_uuid: Option<AssetUuid>,
    #[serde(default)]
    pub components: SceneComponents,
}

impl SceneEntity {
    pub fn new() -> Self {
        Self {
            uuid: AssetUuid::random(),
            parent: None,
            name: None,
            transform: LocalTransform::default(),
            layer: None,
            chunk_id: None,
            guid: None,
            source_entity_uuid: None,
            components: SceneComponents::default(),
        }
    }

    pub fn with_name(mut self, name: impl Into<String>) -> Self {
        self.name = Some(name.into());
        self
    }

    pub fn with_transform(mut self, transform: LocalTransform) -> Self {
        self.transform = transform;
        self
    }

    pub fn with_parent(mut self, parent: AssetUuid) -> Self {
        self.parent = Some(parent);
        self
    }

    pub fn with_layer(mut self, layer: LayerId) -> Self {
        self.layer = Some(layer);
        self
    }

    pub fn with_chunk(mut self, chunk_id: ChunkId) -> Self {
        self.chunk_id = Some(chunk_id);
        self
    }

    pub fn with_components(mut self, components: SceneComponents) -> Self {
        self.components = components;
        self
    }

    pub fn with_mesh(mut self, mesh: SceneMesh) -> Self {
        self.components.mesh = Some(mesh);
        self
    }

    pub fn with_light(mut self, light: SceneLight) -> Self {
        self.components.light = Some(light);
        self
    }

    pub fn with_camera(mut self, camera: SceneCamera) -> Self {
        self.components.camera = Some(camera);
        self
    }

    pub fn with_casts_shadow(mut self, casts_shadow: bool) -> Self {
        self.components.casts_shadow = casts_shadow;
        self
    }

    pub fn with_visible(mut self, visible: bool) -> Self {
        self.components.visible = visible;
        self
    }
}

impl Default for SceneEntity {
    fn default() -> Self {
        Self::new()
    }
}

fn default_true() -> bool {
    true
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneComponents {
    #[serde(default)]
    pub mesh: Option<SceneMesh>,
    #[serde(default)]
    pub instanced_mesh: Option<SceneInstancedMesh>,
    #[serde(default)]
    pub light: Option<SceneLight>,
    #[serde(default)]
    pub camera: Option<SceneCamera>,
    #[serde(default)]
    pub physics: Option<ScenePhysics>,
    #[serde(default)]
    pub audio: Option<SceneAudioSource>,
    #[serde(default)]
    pub prefab: Option<ScenePrefabInstance>,
    #[serde(default)]
    pub bounding_volume: Option<BoundingVolume>,
    #[serde(default)]
    pub animation_player: Option<SceneAnimationPlayer>,
    #[serde(default)]
    pub lines: Vec<Line>,
    #[serde(default = "default_true")]
    pub casts_shadow: bool,
    #[serde(default = "default_true")]
    pub visible: bool,
    #[serde(default)]
    pub particle_emitter: Option<SceneParticleEmitter>,
    #[serde(default)]
    pub decal: Option<Decal>,
    #[serde(default)]
    pub render_layer: Option<RenderLayer>,
    #[serde(default)]
    pub text: Option<Text>,
    /// String content for a text entity. The live content lives in
    /// `world.resources.text.cache` keyed by `Text::text_index`; the
    /// cache is not part of the scene format, so we ferry the string
    /// here at save time and write it back to a fresh cache slot at
    /// spawn time.
    #[serde(default)]
    pub text_content: Option<String>,
    #[serde(default)]
    pub character_controller: Option<SceneCharacterController>,
    #[serde(default)]
    pub navmesh_agent: Option<NavMeshAgent>,
    #[serde(default)]
    pub tags: Vec<String>,
    /// Typed game-side payloads serialized for this entity. Engine
    /// stores them as a typed-JSON channel between the game and the
    /// scene file; the engine does not inspect the contents.
    #[serde(default)]
    pub game_components: Vec<TypedComponent>,
}

impl Default for SceneComponents {
    fn default() -> Self {
        Self {
            mesh: None,
            instanced_mesh: None,
            light: None,
            camera: None,
            physics: None,
            audio: None,
            prefab: None,
            bounding_volume: None,
            animation_player: None,
            lines: Vec::new(),
            casts_shadow: true,
            visible: true,
            particle_emitter: None,
            decal: None,
            render_layer: None,
            text: None,
            character_controller: None,
            navmesh_agent: None,
            tags: Vec::new(),
            game_components: Vec::new(),
            text_content: None,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneMesh {
    #[serde(default)]
    pub mesh_uuid: Option<AssetUuid>,
    #[serde(default)]
    pub mesh_name: Option<String>,
    #[serde(default)]
    pub material: Option<SceneMaterial>,
}

impl SceneMesh {
    pub fn from_uuid(uuid: AssetUuid) -> Self {
        Self {
            mesh_uuid: Some(uuid),
            mesh_name: None,
            material: None,
        }
    }

    pub fn from_name(name: impl Into<String>) -> Self {
        Self {
            mesh_uuid: None,
            mesh_name: Some(name.into()),
            material: None,
        }
    }

    pub fn with_material(mut self, material: SceneMaterial) -> Self {
        self.material = Some(material);
        self
    }
}
