use dioxus::prelude::*;
use freya_core::platform::CursorIcon;
use freya_elements::{
    self as dioxus_elements,
    events::{
        keyboard::Key,
        KeyboardEvent,
        MouseEvent,
        WheelEvent,
    },
};
use freya_hooks::{
    use_applied_theme,
    use_focus,
    use_node,
    use_platform,
    SliderThemeWith,
};
#[derive(Props, Clone, PartialEq)]
pub struct SliderProps {
    pub theme: Option<SliderThemeWith>,
    pub onmoved: EventHandler<f64>,
    #[props(into, default = "100%".to_string())]
    pub size: String,
    pub value: f64,
    #[props(default = "horizontal".to_string())]
    pub direction: String,
}
#[inline]
fn ensure_correct_slider_range(value: f64) -> f64 {
    if value < 0.0 {
        #[cfg(debug_assertions)]
        tracing::info!("Slider value is less than 0.0, setting to 0.0");
        0.0
    } else if value > 100.0 {
        #[cfg(debug_assertions)]
        tracing::info!("Slider value is greater than 100.0, setting to 100.0");
        100.0
    } else {
        value
    }
}
#[derive(Debug, Default, PartialEq, Clone, Copy)]
pub enum SliderStatus {
    #[default]
    Idle,
    Hovering,
}
#[cfg_attr(feature = "docs",
    doc = embed_doc_image::embed_image!("slider", "images/gallery_slider.png")
)]
#[allow(non_snake_case)]
pub fn Slider(
    SliderProps {
        value,
        onmoved,
        theme,
        size,
        direction,
    }: SliderProps,
) -> Element {
    let theme = use_applied_theme!(&theme, slider);
    let mut focus = use_focus();
    let mut status = use_signal(SliderStatus::default);
    let mut clicking = use_signal(|| false);
    let platform = use_platform();
    let (node_reference, node_size) = use_node();
    let direction_is_vertical = direction == "vertical";
    let value = ensure_correct_slider_range(value);
    let a11y_id = focus.attribute();
    use_drop(move || {
        if *status.peek() == SliderStatus::Hovering {
            platform.set_cursor(CursorIcon::default());
        }
    });
    let onkeydown = move |e: KeyboardEvent| match e.key {
        Key::ArrowLeft if !direction_is_vertical => {
            e.stop_propagation();
            let percentage = (value - 4.).clamp(0.0, 100.0);
            onmoved.call(percentage);
        }
        Key::ArrowRight if !direction_is_vertical => {
            e.stop_propagation();
            let percentage = (value + 4.).clamp(0.0, 100.0);
            onmoved.call(percentage);
        }
        Key::ArrowUp if direction_is_vertical => {
            e.stop_propagation();
            let percentage = (value + 4.).clamp(0.0, 100.0);
            onmoved.call(percentage);
        }
        Key::ArrowDown if direction_is_vertical => {
            e.stop_propagation();
            let percentage = (value - 4.).clamp(0.0, 100.0);
            onmoved.call(percentage);
        }
        _ => {}
    };
    let onmouseleave = move |e: MouseEvent| {
        e.stop_propagation();
        *status.write() = SliderStatus::Idle;
        platform.set_cursor(CursorIcon::default());
    };
    let onmouseenter = move |e: MouseEvent| {
        e.stop_propagation();
        *status.write() = SliderStatus::Hovering;
        platform.set_cursor(CursorIcon::Pointer);
    };
    let onmousemove = {
        to_owned![onmoved];
        move |e: MouseEvent| {
            e.stop_propagation();
            if *clicking.peek() {
                let coordinates = e.get_element_coordinates();
                let percentage = if direction_is_vertical {
                    let y = coordinates.y - node_size.area.min_y() as f64 - 6.0;
                    100. - (y / (node_size.area.height() as f64 - 15.0) * 100.0)
                } else {
                    let x = coordinates.x - node_size.area.min_x() as f64 - 6.0;
                    x / (node_size.area.width() as f64 - 15.0) * 100.0
                };
                let percentage = percentage.clamp(0.0, 100.0);
                onmoved.call(percentage);
            }
        }
    };
    let onmousedown = {
        to_owned![onmoved];
        move |e: MouseEvent| {
            e.stop_propagation();
            focus.focus();
            clicking.set(true);
            let coordinates = e.get_element_coordinates();
            let percentage = if direction_is_vertical {
                let y = coordinates.y - 6.0;
                100. - (y / (node_size.area.height() as f64 - 15.0) * 100.0)
            } else {
                let x = coordinates.x - 6.0;
                x / (node_size.area.width() as f64 - 15.0) * 100.0
            };
            let percentage = percentage.clamp(0.0, 100.0);
            onmoved.call(percentage);
        }
    };
    let onclick = move |_: MouseEvent| {
        clicking.set(false);
    };
    let onwheel = move |e: WheelEvent| {
        e.stop_propagation();
        let wheel_y = e.get_delta_y().clamp(-1.0, 1.0);
        let percentage = value + (wheel_y * 2.0);
        let percentage = percentage.clamp(0.0, 100.0);
        onmoved.call(percentage);
    };
    let border = if focus.is_focused_with_keyboard() {
        format!("2 inner {}", theme.border_fill)
    } else {
        "none".to_string()
    };
    let (
        width,
        height,
        container_width,
        container_height,
        inner_width,
        inner_height,
        main_align,
        offset_x,
        offset_y,
    ) = if direction_is_vertical {
        let inner_height = (node_size.area.height() - 15.0) * (value / 100.0) as f32;
        (
            "20",
            size.as_str(),
            "6",
            "100%",
            "100%".to_string(),
            inner_height.to_string(),
            "end",
            -6,
            3,
        )
    } else {
        let inner_width = (node_size.area.width() - 15.0) * (value / 100.0) as f32;
        (
            size.as_str(),
            "20",
            "100%",
            "6",
            inner_width.to_string(),
            "100%".to_string(),
            "start",
            -3,
            -6,
        )
    };
    let inner_fill = rsx!(rect {
        background: "{theme.thumb_inner_background}",
        width: "{inner_width}",
        height: "{inner_height}",
        corner_radius: "50"
    });
    let thumb = rsx!(
        rect {
            width: "fill",
            offset_x: "{offset_x}",
            offset_y: "{offset_y}",
            rect {
                background: "{theme.thumb_background}",
                width: "18",
                height: "18",
                corner_radius: "50",
                padding: "4",
                rect {
                    height: "100%",
                    width: "100%",
                    background: "{theme.thumb_inner_background}",
                    corner_radius: "50"
                }
            }
        }
    );
    rsx!(
        rect {
            reference: node_reference,
            width: "{width}",
            height: "{height}",
            onmousedown,
            onglobalclick: onclick,
            a11y_id,
            onmouseenter,
            onglobalmousemove: onmousemove,
            onmouseleave,
            onwheel: onwheel,
            onkeydown,
            main_align: "center",
            cross_align: "center",
            border: "{border}",
            corner_radius: "8",
            rect {
                background: "{theme.background}",
                width: "{container_width}",
                height: "{container_height}",
                main_align: "{main_align}",
                direction: "{direction}",
                corner_radius: "50",
                if direction_is_vertical {
                    {thumb}
                    {inner_fill}
                } else {
                    {inner_fill}
                    {thumb}
                }
            }
        }
    )
}
#[cfg(test)]
mod test {
    use dioxus::prelude::use_signal;
    use freya::prelude::*;
    use freya_testing::prelude::*;
    #[tokio::test]
    pub async fn slider() {
        fn slider_app() -> Element {
            let mut value = use_signal(|| 50.);
            rsx!(
                Slider {
                    value: *value.read(),
                    onmoved: move |p| {
                        value.set(p);
                    }
                }
                label {
                    "{value}"
                }
            )
        }
        let mut utils = launch_test(slider_app);
        let root = utils.root();
        let label = root.get(1);
        utils.wait_for_update().await;
        assert_eq!(label.get(0).text(), Some("50"));
        utils.push_event(TestEvent::Mouse {
            name: EventName::MouseMove,
            cursor: (250.0, 7.0).into(),
            button: Some(MouseButton::Left),
        });
        utils.push_event(TestEvent::Mouse {
            name: EventName::MouseDown,
            cursor: (250.0, 7.0).into(),
            button: Some(MouseButton::Left),
        });
        utils.push_event(TestEvent::Mouse {
            name: EventName::MouseMove,
            cursor: (500.0, 7.0).into(),
            button: Some(MouseButton::Left),
        });
        utils.wait_for_update().await;
        assert_eq!(label.get(0).text(), Some("100"));
    }
}