use mlua::{FromLua, Lua, UserData, UserDataMethods};

use crate::lua::LuaEnv;
pub use crate::renderer::prelude::*;

#[derive(Debug, Clone, Copy)]
pub struct Animation {
    pub start_element: Element,
    pub end_element: Element,
    pub duration: f64,
    pub easing: EasingFunction,
}

impl Animation {
    pub fn new(
        start_element: Element,
        end_element: Element,
        duration: f64,
        easing: EasingFunction,
    ) -> Self {
        Animation {
            start_element,
            end_element,
            duration,
            easing,
        }
    }

    pub fn interpolate_at(&self, time: f64) -> Element {
        let progress = (time / self.duration).clamp(0.0, 1.0);
        let eased_progress = self.easing.apply(progress);

        match (&self.start_element, &self.end_element) {
            (Element::Line(start), Element::Line(end)) => Element::Line(LineElement {
                start: Point::new(
                    start.start.x + (end.start.x - start.start.x) * eased_progress,
                    start.start.y + (end.start.y - start.start.y) * eased_progress,
                ),
                end: Point::new(
                    start.end.x + (end.end.x - start.end.x) * eased_progress,
                    start.end.y + (end.end.y - start.end.y) * eased_progress,
                ),
                color: interpolate_color(start.color, end.color, eased_progress),
                thickness: start.thickness + (end.thickness - start.thickness) * eased_progress,
            }),
            (Element::Rectangle(start), Element::Rectangle(end)) => {
                Element::Rectangle(RectangleElement {
                    position: Point::new(
                        start.position.x + (end.position.x - start.position.x) * eased_progress,
                        start.position.y + (end.position.y - start.position.y) * eased_progress,
                    ),
                    size: Size::new(
                        start.size.width + (end.size.width - start.size.width) * eased_progress,
                        start.size.height + (end.size.height - start.size.height) * eased_progress,
                    ),
                    color: interpolate_color(start.color, end.color, eased_progress),
                    filled: start.filled,
                    border_thickness: start.border_thickness
                        + (end.border_thickness - start.border_thickness) * eased_progress,
                })
            }
            (Element::Circle(start), Element::Circle(end)) => Element::Circle(CircleElement {
                center: Point::new(
                    start.center.x + (end.center.x - start.center.x) * eased_progress,
                    start.center.y + (end.center.y - start.center.y) * eased_progress,
                ),
                radius: start.radius + (end.radius - start.radius) * eased_progress,
                color: interpolate_color(start.color, end.color, eased_progress),
                filled: start.filled,
                border_thickness: start.border_thickness
                    + (end.border_thickness - start.border_thickness) * eased_progress,
            }),
            _ => self.start_element.clone(), // Fallback for mismatched types
        }
    }
}

fn interpolate_color(start: Color, end: Color, progress: f64) -> Color {
    Color::new(
        (start.r as f64 + (end.r as f64 - start.r as f64) * progress) as u8,
        (start.g as f64 + (end.g as f64 - start.g as f64) * progress) as u8,
        (start.b as f64 + (end.b as f64 - start.b as f64) * progress) as u8,
        (start.a as f64 + (end.a as f64 - start.a as f64) * progress) as u8,
    )
}

impl FromLua for Animation {
    fn from_lua(value: mlua::Value, _: &Lua) -> mlua::Result<Self> {
        match value {
            mlua::Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
            _ => unreachable!(),
        }
    }
}

impl UserData for Animation {
    fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
        fields.add_field_method_get("duration", |_, this| Ok(this.duration));
        fields.add_field_method_get("start_element", |_, this| Ok(this.start_element));
        fields.add_field_method_get("end_element", |_, this| Ok(this.end_element));
        fields.add_field_method_get("easing", |_, this| Ok(this.easing));
    }
}

pub fn add_constructor(lua: &LuaEnv) -> crate::Result<()> {
    lua.func(
        "Animation",
        |_, (start_element, end_element, duration, easing)| {
            Ok(Animation::new(start_element, end_element, duration, easing))
        },
    )?;
    Ok(())
}
