//! Effect layer type for Lightningbeam
//!
//! An EffectLayer applies visual effects to the composition below it.
//! Effect instances are stored as `ClipInstance` objects where `clip_id`
//! references an `EffectDefinition`.

use crate::clip::ClipInstance;
use crate::layer::{Layer, LayerTrait, LayerType};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// Layer type that applies visual effects to the composition
///
/// Effect instances are represented as `ClipInstance` objects.
/// The `clip_id` field references an `EffectDefinition` rather than a clip.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EffectLayer {
    /// Base layer properties
    pub layer: Layer,
    /// Effect instances (as ClipInstances referencing EffectDefinitions)
    pub clip_instances: Vec<ClipInstance>,
}

impl LayerTrait for EffectLayer {
    fn id(&self) -> Uuid {
        self.layer.id
    }

    fn name(&self) -> &str {
        &self.layer.name
    }

    fn set_name(&mut self, name: String) {
        self.layer.name = name;
    }

    fn has_custom_name(&self) -> bool {
        self.layer.has_custom_name
    }

    fn set_has_custom_name(&mut self, custom: bool) {
        self.layer.has_custom_name = custom;
    }

    fn visible(&self) -> bool {
        self.layer.visible
    }

    fn set_visible(&mut self, visible: bool) {
        self.layer.visible = visible;
    }

    fn opacity(&self) -> f64 {
        self.layer.opacity
    }

    fn set_opacity(&mut self, opacity: f64) {
        self.layer.opacity = opacity;
    }

    fn volume(&self) -> f64 {
        self.layer.volume
    }

    fn set_volume(&mut self, volume: f64) {
        self.layer.volume = volume;
    }

    fn muted(&self) -> bool {
        self.layer.muted
    }

    fn set_muted(&mut self, muted: bool) {
        self.layer.muted = muted;
    }

    fn soloed(&self) -> bool {
        self.layer.soloed
    }

    fn set_soloed(&mut self, soloed: bool) {
        self.layer.soloed = soloed;
    }

    fn locked(&self) -> bool {
        self.layer.locked
    }

    fn set_locked(&mut self, locked: bool) {
        self.layer.locked = locked;
    }
}

impl EffectLayer {
    /// Create a new effect layer
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            layer: Layer::new(LayerType::Effect, name),
            clip_instances: Vec::new(),
        }
    }

    /// Create with a specific ID
    pub fn with_id(id: Uuid, name: impl Into<String>) -> Self {
        Self {
            layer: Layer::with_id(id, LayerType::Effect, name),
            clip_instances: Vec::new(),
        }
    }

    /// Add a clip instance (effect) to this layer
    pub fn add_clip_instance(&mut self, instance: ClipInstance) -> Uuid {
        let id = instance.id;
        self.clip_instances.push(instance);
        id
    }

    /// Insert a clip instance at a specific index
    pub fn insert_clip_instance(&mut self, index: usize, instance: ClipInstance) -> Uuid {
        let id = instance.id;
        let index = index.min(self.clip_instances.len());
        self.clip_instances.insert(index, instance);
        id
    }

    /// Remove a clip instance by ID
    pub fn remove_clip_instance(&mut self, id: &Uuid) -> Option<ClipInstance> {
        if let Some(index) = self.clip_instances.iter().position(|e| &e.id == id) {
            Some(self.clip_instances.remove(index))
        } else {
            None
        }
    }

    /// Get a clip instance by ID
    pub fn get_clip_instance(&self, id: &Uuid) -> Option<&ClipInstance> {
        self.clip_instances.iter().find(|e| &e.id == id)
    }

    /// Get a mutable clip instance by ID
    pub fn get_clip_instance_mut(&mut self, id: &Uuid) -> Option<&mut ClipInstance> {
        self.clip_instances.iter_mut().find(|e| &e.id == id)
    }

    /// Get all clip instances (effects) that are active at a given time
    ///
    /// Uses `EFFECT_DURATION` to calculate effective duration for each instance.
    pub fn active_clip_instances_at(&self, time: f64) -> Vec<&ClipInstance> {
        use crate::effect::EFFECT_DURATION;
        self.clip_instances
            .iter()
            .filter(|e| {
                let end = e.timeline_start + e.effective_duration(EFFECT_DURATION);
                time >= e.timeline_start && time < end
            })
            .collect()
    }

    /// Get the index of a clip instance
    pub fn clip_instance_index(&self, id: &Uuid) -> Option<usize> {
        self.clip_instances.iter().position(|e| &e.id == id)
    }

    /// Move a clip instance to a new position in the layer
    pub fn move_clip_instance(&mut self, id: &Uuid, new_index: usize) -> bool {
        if let Some(current_index) = self.clip_instance_index(id) {
            let instance = self.clip_instances.remove(current_index);
            let new_index = new_index.min(self.clip_instances.len());
            self.clip_instances.insert(new_index, instance);
            true
        } else {
            false
        }
    }

    /// Reorder clip instances by providing a list of IDs in desired order
    pub fn reorder_clip_instances(&mut self, order: &[Uuid]) {
        let mut new_order = Vec::with_capacity(self.clip_instances.len());

        // Add instances in the specified order
        for id in order {
            if let Some(index) = self.clip_instances.iter().position(|e| &e.id == id) {
                new_order.push(self.clip_instances.remove(index));
            }
        }

        // Append any instances not in the order list
        new_order.append(&mut self.clip_instances);
        self.clip_instances = new_order;
    }

}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::effect::{EffectCategory, EffectDefinition, EffectParameterDef};

    fn create_test_effect_def() -> EffectDefinition {
        EffectDefinition::new(
            "Test Effect",
            EffectCategory::Color,
            "// shader code",
            vec![EffectParameterDef::float_range("intensity", "Intensity", 1.0, 0.0, 2.0)],
        )
    }

    #[test]
    fn test_effect_layer_creation() {
        let layer = EffectLayer::new("Effects");
        assert_eq!(layer.name(), "Effects");
        assert_eq!(layer.clip_instances.len(), 0);
    }

    #[test]
    fn test_add_effect() {
        let mut layer = EffectLayer::new("Effects");
        let def = create_test_effect_def();
        let effect = def.create_instance(0.0, 10.0);
        let effect_id = effect.id;

        let id = layer.add_clip_instance(effect);
        assert_eq!(id, effect_id);
        assert_eq!(layer.clip_instances.len(), 1);
        assert!(layer.get_clip_instance(&effect_id).is_some());
    }

    #[test]
    fn test_active_effects() {
        let mut layer = EffectLayer::new("Effects");
        let def = create_test_effect_def();

        // Effect 1: active from 0 to 5
        let effect1 = def.create_instance(0.0, 5.0);
        layer.add_clip_instance(effect1);

        // Effect 2: active from 3 to 10
        let effect2 = def.create_instance(3.0, 7.0); // 3.0 + 7.0 = 10.0 end
        layer.add_clip_instance(effect2);

        // At time 2: only effect1 active
        assert_eq!(layer.active_clip_instances_at(2.0).len(), 1);

        // At time 4: both effects active
        assert_eq!(layer.active_clip_instances_at(4.0).len(), 2);

        // At time 7: only effect2 active
        assert_eq!(layer.active_clip_instances_at(7.0).len(), 1);
    }

    #[test]
    fn test_effect_reordering() {
        let mut layer = EffectLayer::new("Effects");
        let def = create_test_effect_def();

        let effect1 = def.create_instance(0.0, 10.0);
        let id1 = effect1.id;
        layer.add_clip_instance(effect1);

        let effect2 = def.create_instance(0.0, 10.0);
        let id2 = effect2.id;
        layer.add_clip_instance(effect2);

        // Initially: [id1, id2]
        assert_eq!(layer.clip_instance_index(&id1), Some(0));
        assert_eq!(layer.clip_instance_index(&id2), Some(1));

        // Move id1 to index 1: [id2, id1]
        layer.move_clip_instance(&id1, 1);
        assert_eq!(layer.clip_instance_index(&id1), Some(1));
        assert_eq!(layer.clip_instance_index(&id2), Some(0));
    }
}
