//! Object system for Lightningbeam
//!
//! An Object represents an instance of a Shape with transform properties.
//! Objects can be animated via the animation system.

use serde::{Deserialize, Serialize};
use uuid::Uuid;
use vello::kurbo::Shape as KurboShape;

/// 2D transform for an object
/// Contains only geometric transformations (position, rotation, scale, skew)
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Transform {
    /// X position
    pub x: f64,
    /// Y position
    pub y: f64,
    /// Rotation in degrees
    pub rotation: f64,
    /// X scale factor
    pub scale_x: f64,
    /// Y scale factor
    pub scale_y: f64,
    /// X skew in degrees
    pub skew_x: f64,
    /// Y skew in degrees
    pub skew_y: f64,
}

impl Default for Transform {
    fn default() -> Self {
        Self {
            x: 0.0,
            y: 0.0,
            rotation: 0.0,
            scale_x: 1.0,
            scale_y: 1.0,
            skew_x: 0.0,
            skew_y: 0.0,
        }
    }
}

impl Transform {
    /// Create a new default transform
    pub fn new() -> Self {
        Self::default()
    }

    /// Create a transform with position
    pub fn with_position(x: f64, y: f64) -> Self {
        Self {
            x,
            y,
            ..Default::default()
        }
    }

    /// Create a transform with rotation
    pub fn with_rotation(rotation: f64) -> Self {
        Self {
            rotation,
            ..Default::default()
        }
    }

    /// Set position
    pub fn set_position(&mut self, x: f64, y: f64) {
        self.x = x;
        self.y = y;
    }

    /// Set rotation
    pub fn set_rotation(&mut self, rotation: f64) {
        self.rotation = rotation;
    }

    /// Set scale
    pub fn set_scale(&mut self, scale_x: f64, scale_y: f64) {
        self.scale_x = scale_x;
        self.scale_y = scale_y;
    }

    /// Set uniform scale
    pub fn set_uniform_scale(&mut self, scale: f64) {
        self.scale_x = scale;
        self.scale_y = scale;
    }

    /// Convert to an affine transform matrix
    /// Note: Skew is applied in local space. For proper centering, the shape's
    /// bounding box center should be used (see renderer.rs for the full implementation).
    pub fn to_affine(&self) -> kurbo::Affine {
        use kurbo::Affine;

        // Build transform: translate * rotate * scale * skew
        let translate = Affine::translate((self.x, self.y));
        let rotate = Affine::rotate(self.rotation.to_radians());
        let scale = Affine::scale_non_uniform(self.scale_x, self.scale_y);

        // Skew transforms (applied in local space)
        let skew_x = if self.skew_x != 0.0 {
            let tan_skew = self.skew_x.to_radians().tan();
            Affine::new([1.0, 0.0, tan_skew, 1.0, 0.0, 0.0])
        } else {
            Affine::IDENTITY
        };

        let skew_y = if self.skew_y != 0.0 {
            let tan_skew = self.skew_y.to_radians().tan();
            Affine::new([1.0, tan_skew, 0.0, 1.0, 0.0, 0.0])
        } else {
            Affine::IDENTITY
        };

        translate * rotate * scale * skew_x * skew_y
    }
}

/// A shape instance (shape with transform)
/// Represents an instance of a Shape with its own transform properties.
/// Multiple instances can reference the same shape.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ShapeInstance {
    /// Unique identifier
    pub id: Uuid,

    /// Reference to the shape this object uses
    pub shape_id: Uuid,

    /// Transform properties (position, rotation, scale, skew)
    pub transform: Transform,

    /// Opacity (0.0 to 1.0, separate from geometric transform)
    pub opacity: f64,

    /// Name for display in UI
    pub name: Option<String>,
}

impl ShapeInstance {
    /// Create a new shape instance for a shape
    pub fn new(shape_id: Uuid) -> Self {
        Self {
            id: Uuid::new_v4(),
            shape_id,
            transform: Transform::default(),
            opacity: 1.0,
            name: None,
        }
    }

    /// Create a new shape instance with a specific ID
    pub fn with_id(id: Uuid, shape_id: Uuid) -> Self {
        Self {
            id,
            shape_id,
            transform: Transform::default(),
            opacity: 1.0,
            name: None,
        }
    }

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

    /// Set the transform
    pub fn with_transform(mut self, transform: Transform) -> Self {
        self.transform = transform;
        self
    }

    /// Set position
    pub fn with_position(mut self, x: f64, y: f64) -> Self {
        self.transform.set_position(x, y);
        self
    }

    /// Convert shape instance transform to affine matrix
    pub fn to_affine(&self) -> kurbo::Affine {
        self.transform.to_affine()
    }

    /// Get the bounding box of this shape instance given its shape
    ///
    /// Returns the bounding box in the instance's parent coordinate space
    /// (i.e., with the instance's transform applied).
    pub fn bounding_box(&self, shape: &crate::shape::Shape) -> kurbo::Rect {
        let path_bbox = shape.path().bounding_box();
        self.to_affine().transform_rect_bbox(path_bbox)
    }
}

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

    #[test]
    fn test_transform_default() {
        let transform = Transform::default();
        assert_eq!(transform.x, 0.0);
        assert_eq!(transform.y, 0.0);
        assert_eq!(transform.scale_x, 1.0);
        assert_eq!(transform.scale_y, 1.0);
    }

    #[test]
    fn test_transform_affine() {
        let mut transform = Transform::default();
        transform.set_position(100.0, 200.0);
        transform.set_rotation(45.0);

        let affine = transform.to_affine();
        // Just verify it doesn't panic
        let _ = affine.as_coeffs();
    }

    #[test]
    fn test_shape_instance_creation() {
        let shape_id = Uuid::new_v4();
        let shape_instance = ShapeInstance::new(shape_id);

        assert_eq!(shape_instance.shape_id, shape_id);
        assert_eq!(shape_instance.transform.x, 0.0);
    }
}
