// Copyright 2026 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Shared structures for holding different transforms.

use crate::kurbo::Affine;
use smallvec::{SmallVec, smallvec};

/// Context for holding transforms for paths and paints.
#[derive(Debug, Clone)]
pub struct Transforms {
    transform: Affine,
    paint_transform: Affine,
}

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

impl Transforms {
    /// Create a new transforms context.
    pub fn new() -> Self {
        Self {
            transform: Affine::IDENTITY,
            paint_transform: Affine::IDENTITY,
        }
    }

    /// Return the current scene transform.
    pub fn transform(&self) -> &Affine {
        &self.transform
    }

    /// Return the transform used for rendering scene geometry.
    pub fn scene_transform(&self) -> Affine {
        self.transform
    }

    /// Set the current scene transform.
    pub fn set_transform(&mut self, transform: Affine) {
        self.transform = transform;
    }

    /// Reset the current scene transform.
    pub fn reset_transform(&mut self) {
        self.transform = Affine::IDENTITY;
    }

    /// Return the current paint transform.
    pub fn paint_transform(&self) -> &Affine {
        &self.paint_transform
    }

    /// Return the transform used for rendering scene paints.
    pub fn scene_paint_transform(&self) -> Affine {
        self.scene_transform() * self.paint_transform
    }

    /// Set the current paint transform.
    pub fn set_paint_transform(&mut self, paint_transform: Affine) {
        self.paint_transform = paint_transform;
    }

    /// Reset the current paint transform.
    pub fn reset_paint_transform(&mut self) {
        self.paint_transform = Affine::IDENTITY;
    }

    // Unlike [`Self::effective_path_transform`], this intentionally does not apply
    // the root transform because clipping handles root viewport shifts separately.
    /// Return the transform used for non-isolated clip paths.
    pub fn clip_path_transform(&self) -> Affine {
        self.transform
    }
}

/// Stack of root transforms.
#[derive(Debug)]
pub struct RootTransforms {
    transforms: SmallVec<[Affine; 3]>,
}

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

impl RootTransforms {
    /// Create a new root transform stack.
    pub fn new() -> Self {
        Self {
            transforms: smallvec![Affine::IDENTITY],
        }
    }

    /// Return the root transform of the currently active root viewport, including
    /// shifts inherited from nested filters.
    pub fn root_transform(&self) -> Affine {
        *self
            .transforms
            .last()
            .expect("root transform stack should never be empty")
    }

    /// Return the transform used for rendering paths.
    pub fn effective_path_transform(&self, transforms: &Transforms) -> Affine {
        self.root_transform() * transforms.transform
    }

    /// Return the transform used for rendering paints.
    pub fn effective_paint_transform(&self, transforms: &Transforms) -> Affine {
        self.effective_path_transform(transforms) * transforms.paint_transform
    }

    /// Push a new root transform relative to the currently active root transform.
    pub fn push_root(&mut self, relative_transform: Affine) {
        self.transforms
            .push(relative_transform * self.root_transform());
    }

    /// Pop the last root layer.
    pub fn pop_root(&mut self) {
        self.transforms.pop();
    }

    /// Reset the root transform stack.
    pub fn reset(&mut self) {
        self.transforms.clear();
        self.transforms.push(Affine::IDENTITY);
    }
}

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

    #[test]
    fn root_transforms_accumulate_relative_transforms() {
        let mut roots = RootTransforms::new();
        let parent = Affine::translate((10.0, 20.0));
        let child = Affine::scale(2.0);

        roots.push_root(parent);
        roots.push_root(child);

        assert_eq!(roots.root_transform(), child * parent);
    }

    #[test]
    fn pop_root_restores_parent_transform() {
        let mut roots = RootTransforms::new();
        let parent = Affine::translate((10.0, 20.0));

        roots.push_root(parent);
        roots.push_root(Affine::scale(2.0));
        roots.pop_root();

        assert_eq!(roots.root_transform(), parent);
    }

    #[test]
    fn effective_transforms_include_root_scene_and_paint_transforms() {
        let mut roots = RootTransforms::new();
        let root = Affine::translate((10.0, 20.0));
        let scene = Affine::scale(2.0);
        let paint = Affine::translate((3.0, 4.0));
        let mut transforms = Transforms::new();
        transforms.set_transform(scene);
        transforms.set_paint_transform(paint);
        roots.push_root(root);

        assert_eq!(roots.effective_path_transform(&transforms), root * scene);
        assert_eq!(
            roots.effective_paint_transform(&transforms),
            root * scene * paint
        );
    }
}
