Preview App
use log::LevelFilter;
use std::f64::consts::PI;

use ranim::{
    animation::{creation::WritingAnim, fading::FadingAnim, transform::TransformAnim},
    color::palettes::manim,
    components::ScaleHint,
    items::{
        Group,
        vitem::{
            VItem,
            geometry::{Circle, Square},
            svg::SvgItem,
            typst::typst_svg,
        },
    },
    prelude::*,
    render::primitives::{Extract, vitem::VItemPrimitive},
    timeline::TimeMark,
};

use glam::DVec3;

#[derive(Clone)]
pub struct VisualVItem(VItem);

impl Interpolatable for VisualVItem {
    fn lerp(&self, target: &Self, t: f64) -> Self {
        Self(self.0.lerp(&target.0, t))
    }
}

impl Alignable for VisualVItem {
    fn is_aligned(&self, other: &Self) -> bool {
        self.0.is_aligned(&other.0)
    }
    fn align_with(&mut self, other: &mut Self) {
        self.0.align_with(&mut other.0);
    }
}

impl Partial for VisualVItem {
    fn get_partial(&self, range: std::ops::Range<f64>) -> Self {
        Self(self.0.get_partial(range))
    }
    fn get_partial_closed(&self, range: std::ops::Range<f64>) -> Self {
        Self(self.0.get_partial_closed(range))
    }
}

impl Opacity for VisualVItem {
    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
        self.0.set_opacity(opacity);
        self
    }
}

impl StrokeColor for VisualVItem {
    fn set_stroke_color(&mut self, color: color::AlphaColor<color::Srgb>) -> &mut Self {
        self.0.set_stroke_color(color);
        self
    }
    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
        self.0.set_stroke_opacity(opacity);
        self
    }
    fn stroke_color(&self) -> color::AlphaColor<color::Srgb> {
        self.0.stroke_color()
    }
}

impl FillColor for VisualVItem {
    fn set_fill_color(&mut self, color: color::AlphaColor<color::Srgb>) -> &mut Self {
        self.0.set_fill_color(color);
        self
    }
    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
        self.0.set_fill_opacity(opacity);
        self
    }
    fn fill_color(&self) -> color::AlphaColor<color::Srgb> {
        self.0.fill_color()
    }
}

impl StrokeWidth for VisualVItem {
    fn apply_stroke_func(
        &mut self,
        f: impl for<'a> Fn(&'a mut [ranim::components::width::Width]),
    ) -> &mut Self {
        self.0.apply_stroke_func(f);
        self
    }
    fn set_stroke_width(&mut self, width: f32) -> &mut Self {
        self.0.set_stroke_width(width);
        self
    }
}

impl Extract for VisualVItem {
    type Target = Vec<VItemPrimitive>;
    fn extract(&self) -> Self::Target {
        let mut points = Vec::with_capacity(self.0.vpoints.len());

        let subpaths = self.0.vpoints.get_subpaths();

        subpaths.iter().for_each(|subpath| {
            let subpath_len = subpath.len();

            subpath.iter().enumerate().for_each(|(idx, p)| {
                if idx == subpath_len - 1 && idx % 2 != 0 {
                    return;
                }
                let point = if idx % 2 == 0 {
                    Circle::new(0.06).with(|circle| {
                        circle
                            .set_color(if idx == 0 {
                                manim::GREEN_C
                            } else if idx / 2 == (subpath_len - 1) / 2 {
                                manim::RED_C
                            } else {
                                manim::BLUE_C
                            })
                            .set_fill_opacity(0.6);
                    })
                } else {
                    Circle::new(0.04).with(|circle| {
                        circle
                            .set_color(manim::WHITE)
                            .set_stroke_opacity(0.8)
                            .set_fill_opacity(0.4);
                    })
                }
                .with(|circle| {
                    circle.put_center_on(*p);
                });
                points.push(point);
            });
        });
        let mut lines = Vec::with_capacity(self.0.vpoints.len());
        self.0
            .vpoints
            .iter()
            .step_by(2)
            .zip(self.0.vpoints.iter().skip(1).step_by(2))
            .zip(self.0.vpoints.iter().skip(2).step_by(2))
            .for_each(|((p0, p1), p2)| {
                if p0 != p1 {
                    lines.extend_from_slice(&[
                        VItem::from_vpoints(vec![*p0, (p0 + p1) / 2.0, *p1]),
                        VItem::from_vpoints(vec![*p1, (p1 + p2) / 2.0, *p2]),
                    ]);
                }
            });
        [self.0.extract()]
            .into_iter()
            .chain(lines.into_iter().map(|x| {
                x.with(|item| {
                    item.set_stroke_width(0.015);
                })
                .extract()
            }))
            .chain(points.into_iter().map(|x| x.extract()))
            .collect()
    }
}

impl Empty for VisualVItem {
    fn empty() -> Self {
        Self(VItem::empty())
    }
}

// MARK: ranim_text
#[scene]
#[preview]
#[output(dir = "extract_vitem_visualize")]
fn ranim_text(r: &mut RanimScene) {
    let r_cam = r.insert_and_show(CameraFrame::default());

    let text = SvgItem::new(typst_svg("Ranim")).with(|item| {
        item.set_fill_color(manim::WHITE)
            .set_fill_opacity(0.5)
            .scale_to_with_stroke(ScaleHint::PorportionalY(3.6))
            .put_center_on(DVec3::ZERO);
    });
    let _r_texts = Group::<VItem>::from(text)
        .into_iter()
        .map(VisualVItem)
        .map(|item| r.insert_and_show(item))
        .collect::<Vec<_>>();
    let default_cam = r.timeline(&r_cam).state().clone();
    r.timelines_mut().forward(1.0);
    r.timeline_mut(&r_cam).play_with(|cam| {
        cam.transform(|cam| {
            cam.scale = 0.3;
            cam.up = DVec3::NEG_X;
            cam.pos.shift(DVec3::NEG_X * 6.0);
        })
    });
    r.timelines_mut().forward(1.0);
    r.timeline_mut(&r_cam).play_with(|cam| {
        cam.transform(|cam| {
            cam.pos.shift(DVec3::X * 12.0);
        })
        .with_duration(7.0)
    });
    r.timelines_mut().forward(1.0);
    r.timeline_mut(&r_cam)
        .play_with(|cam| cam.transform_to(default_cam));

    // r.timelines_mut().forward(1.0);
    r.insert_time_mark(5.0, TimeMark::Capture("preview-ranim_text.png".to_string()));
}

#[scene(name = "extract_vitem_visualize")]
#[preview]
#[output(dir = "extract_vitem_visualize")]
pub fn hello_ranim(r: &mut RanimScene) {
    let _r_cam = r.insert_and_show(CameraFrame::default());

    let square = VisualVItem(VItem::from(Square::new(2.0).with(|square| {
        square.set_color(manim::BLUE_C);
    })));
    let r_vitem = r.insert(square);

    let circle = VisualVItem(VItem::from(Circle::new(2.0).with(|circle| {
        circle
            .set_color(manim::GREEN_C)
            .rotate(-PI / 4.0 + PI, DVec3::Z);
    })));

    {
        let timeline = r.timeline_mut(&r_vitem);
        timeline
            .play_with(|item| item.transform_to(circle))
            .forward(1.0);
        let circle = timeline.state().clone();
        timeline
            .play_with(|circle| circle.unwrite().with_duration(2.0))
            .play(circle.write().with_duration(2.0))
            .play_with(|circle| circle.fade_out());
    }

    r.timelines_mut().sync();
    r.insert_time_mark(3.2, TimeMark::Capture("preview-hello_ranim.png".to_string()));
}

fn main() {
    #[cfg(not(target_arch = "wasm32"))]
    {
        #[cfg(debug_assertions)]
        pretty_env_logger::formatted_timed_builder()
            .filter(Some("ranim"), LevelFilter::Trace)
            .init();
        #[cfg(not(debug_assertions))]
        pretty_env_logger::formatted_timed_builder()
            .filter(Some("ranim"), LevelFilter::Info)
            .init();
    }

    #[cfg(feature = "app")]
    preview(hello_ranim_scene);
    #[cfg(not(feature = "app"))]
    {
        render_scene(hello_ranim_scene);
        render_scene(ranim_text_scene);
    }
}