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

//! Gradient rendering example scenes.
//! Scenes demonstrating gradient rendering with different extend modes. Taken from Vello Classic
//! test scenes:
//! - `GradientExtendScene`:
//!     - `gradient_extend` method from `https://github.com/linebender/vello/blob/0f3ef03a823eb10b0d7a60164e286cde77ffa222/examples/scenes/src/test_scenes.rs#L815`
//! - `RadialScene`:
//!     - `two_point_radial` method from `https://github.com/linebender/vello/blob/0f3ef03a823eb10b0d7a60164e286cde77ffa222/examples/scenes/src/test_scenes.rs#L882`

use crate::{ExampleScene, RenderingContext};
use smallvec::smallvec;
use vello_common::color::palette::css::{BLACK, BLUE, LIME, RED, WHITE, YELLOW};
use vello_common::kurbo::{Affine, Ellipse, Point, Rect, Shape, Stroke};
use vello_common::peniko::{Color, ColorStop, ColorStops, Extend, Gradient, color::DynamicColor};
use vello_common::peniko::{LinearGradientPosition, RadialGradientPosition, SweepGradientPosition};

/// Gradient scene state
#[derive(Debug, Default)]
pub struct GradientExtendScene {}

impl GradientExtendScene {
    /// Create a new gradient extend scene
    pub fn new() -> Self {
        Self {}
    }
}

impl ExampleScene for GradientExtendScene {
    fn render<T: RenderingContext>(
        &mut self,
        ctx: &mut T,
        _resources: &mut T::Resources,
        root_transform: Affine,
    ) {
        enum Kind {
            Linear,
            Radial,
            Sweep,
        }

        /// Helper function to create color stops
        fn create_color_stops(colors: &[Color]) -> ColorStops {
            ColorStops(smallvec![
                ColorStop {
                    offset: 0.0,
                    color: DynamicColor::from_alpha_color(colors[0]),
                },
                ColorStop {
                    offset: 0.5,
                    color: DynamicColor::from_alpha_color(colors[1]),
                },
                ColorStop {
                    offset: 1.0,
                    color: DynamicColor::from_alpha_color(colors[2]),
                },
            ])
        }

        /// Helper function to create a square with a specific gradient type and extend mode
        fn square(ctx: &mut impl RenderingContext, kind: Kind, transform: Affine, extend: Extend) {
            let colors = [RED, LIME, BLUE];
            let width = 300.0;
            let height = 300.0;

            let gradient = match kind {
                Kind::Linear => {
                    let start_x = width * 0.35;
                    let start_y = height * 0.5;
                    let end_x = width * 0.65;
                    let end_y = height * 0.5;

                    Gradient {
                        kind: LinearGradientPosition {
                            start: Point::new(start_x, start_y),
                            end: Point::new(end_x, end_y),
                        }
                        .into(),
                        stops: create_color_stops(&colors),
                        extend,
                        ..Default::default()
                    }
                }
                Kind::Radial => {
                    let center_x = width * 0.5;
                    let center_y = height * 0.5;
                    #[expect(
                        clippy::cast_possible_truncation,
                        reason = "Width is always positive and bounded"
                    )]
                    let radius = (width * 0.25) as f32;

                    Gradient {
                        kind: RadialGradientPosition {
                            start_center: Point::new(center_x, center_y),
                            start_radius: radius * 0.25,
                            end_center: Point::new(center_x, center_y),
                            end_radius: radius,
                        }
                        .into(),
                        stops: create_color_stops(&colors),
                        extend,
                        ..Default::default()
                    }
                }
                Kind::Sweep => {
                    let center_x = width * 0.5;
                    let center_y = height * 0.5;

                    Gradient {
                        kind: SweepGradientPosition {
                            center: Point::new(center_x, center_y),
                            start_angle: 30.0_f32.to_radians(),
                            end_angle: 150.0_f32.to_radians(),
                        }
                        .into(),
                        stops: create_color_stops(&colors),
                        extend,
                        ..Default::default()
                    }
                }
            };

            ctx.set_transform(transform);
            ctx.set_paint(gradient);
            ctx.fill_rect(&Rect::new(0.0, 0.0, width, height));
        }

        let extend_modes = [Extend::Pad, Extend::Repeat, Extend::Reflect];
        for (x, extend) in extend_modes.iter().enumerate() {
            for (y, kind) in [Kind::Linear, Kind::Radial, Kind::Sweep]
                .into_iter()
                .enumerate()
            {
                let transform = root_transform
                    * Affine::translate((x as f64 * 350.0 + 50.0, y as f64 * 350.0 + 100.0));
                square(ctx, kind, transform, *extend);
            }
        }
    }
}

/// Two-point radial gradient scene
#[derive(Debug, Default)]
pub struct RadialScene;

impl RadialScene {
    /// Create a new radial scene
    pub fn new() -> Self {
        Self {}
    }
}

impl ExampleScene for RadialScene {
    fn render<T: RenderingContext>(
        &mut self,
        ctx: &mut T,
        _resources: &mut T::Resources,
        root_transform: Affine,
    ) {
        /// Helper function to create color stops
        fn create_color_stops(colors: &[Color]) -> ColorStops {
            ColorStops(smallvec![
                ColorStop {
                    offset: 0.0,
                    color: DynamicColor::from_alpha_color(colors[0]),
                },
                ColorStop {
                    offset: 0.5,
                    color: DynamicColor::from_alpha_color(colors[1]),
                },
                ColorStop {
                    offset: 1.0,
                    color: DynamicColor::from_alpha_color(colors[2]),
                },
            ])
        }

        /// Helper function to create a two-point radial gradient rectangle
        fn make(
            ctx: &mut impl RenderingContext,
            x0: f64,
            y0: f64,
            r0: f32,
            x1: f64,
            y1: f64,
            r1: f32,
            transform: Affine,
            extend: Extend,
        ) {
            let colors = [RED, YELLOW, Color::from_rgb8(6, 85, 186)];
            let width = 400.0;
            let height = 200.0;

            ctx.set_transform(transform);
            ctx.set_paint(WHITE);
            ctx.fill_rect(&Rect::new(0.0, 0.0, width, height));

            let gradient = Gradient {
                kind: RadialGradientPosition {
                    start_center: Point::new(x0, y0),
                    start_radius: r0,
                    end_center: Point::new(x1, y1),
                    end_radius: r1,
                }
                .into(),
                stops: create_color_stops(&colors),
                extend,
                ..Default::default()
            };

            ctx.set_paint(gradient);
            ctx.fill_rect(&Rect::new(0.0, 0.0, width, height));

            // Draw stroke circles showing the gradient extents
            let r0 = r0 as f64 - 1.0;
            let r1 = r1 as f64 - 1.0;
            ctx.set_paint(BLACK);
            ctx.set_stroke(Stroke::new(1.0));
            ctx.stroke_path(&Ellipse::new((x0, y0), (r0, r0), 0.0).to_path(0.1));
            ctx.stroke_path(&Ellipse::new((x1, y1), (r1, r1), 0.0).to_path(0.1));
        }

        // These demonstrate radial gradient patterns similar to the examples shown
        // at <https://learn.microsoft.com/en-us/typography/opentype/spec/colr#radial-gradients>

        // Row 1: Basic two-point radial gradient
        for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
            .iter()
            .enumerate()
        {
            let y = 100.0;
            let x0 = 140.0;
            let x1 = x0 + 140.0;
            let r0 = 20.0;
            let r1 = 50.0;
            make(
                ctx,
                x0,
                y,
                r0,
                x1,
                y,
                r1,
                root_transform * Affine::translate((i as f64 * 420.0 + 20.0, 20.0)),
                *mode,
            );
        }

        // Row 2: Reversed two-point radial gradient
        for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
            .iter()
            .enumerate()
        {
            let y = 100.0;
            let x0: f64 = 140.0;
            let x1 = x0 + 140.0;
            let r0 = 20.0;
            let r1 = 50.0;
            make(
                ctx,
                x1,
                y,
                r1,
                x0,
                y,
                r0,
                root_transform * Affine::translate((i as f64 * 420.0 + 20.0, 240.0)),
                *mode,
            );
        }

        // Row 3: Equal radii gradient
        for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
            .iter()
            .enumerate()
        {
            let y = 100.0;
            let x0 = 140.0;
            let x1 = x0 + 140.0;
            let r0 = 50.0;
            let r1 = 50.0;
            make(
                ctx,
                x0,
                y,
                r0,
                x1,
                y,
                r1,
                root_transform * Affine::translate((i as f64 * 420.0 + 20.0, 460.0)),
                *mode,
            );
        }

        // Row 4: Overlapping circles
        for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
            .iter()
            .enumerate()
        {
            let x0 = 140.0;
            let y0 = 125.0;
            let r0 = 20.0;
            let x1 = 190.0;
            let y1 = 100.0;
            let r1 = 95.0;
            make(
                ctx,
                x0,
                y0,
                r0,
                x1,
                y1,
                r1,
                root_transform * Affine::translate((i as f64 * 420.0 + 20.0, 680.0)),
                *mode,
            );
        }

        // Row 5: Touching circles
        for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
            .iter()
            .enumerate()
        {
            let x0 = 140.0;
            let y0 = 125.0;
            let r0: f32 = 20.0;
            let x1 = 190.0;
            let y1 = 100.0;
            let r1: f32 = 96.0;
            // Shift p0 so the outer edges of both circles touch
            let direction = Point::new(x0, y0) - Point::new(x1, y1);
            let normalized_direction = direction.normalize();
            let p0 = Point::new(x1, y1) + (normalized_direction * (r1 - r0) as f64);
            make(
                ctx,
                p0.x,
                p0.y,
                r0,
                x1,
                y1,
                r1,
                root_transform * Affine::translate((i as f64 * 420.0 + 20.0, 900.0)),
                *mode,
            );
        }
    }
}
