use dioxus::prelude::*;
use freya_elements::{
    elements as dioxus_elements,
    events::{
        KeyboardEvent,
        PointerEvent,
        PointerType,
    },
};
use freya_hooks::{
    use_applied_theme,
    use_focus,
    use_platform,
    ButtonTheme,
    ButtonThemeWith,
};
use winit::{
    event::{
        MouseButton,
        TouchPhase,
    },
    window::CursorIcon,
};
pub enum PressEvent {
    Pointer(PointerEvent),
    Key(KeyboardEvent),
}
impl PressEvent {
    pub fn stop_propagation(&self) {
        match &self {
            Self::Pointer(ev) => ev.stop_propagation(),
            Self::Key(ev) => ev.stop_propagation(),
        }
    }
}
#[derive(Props, Clone, PartialEq)]
pub struct ButtonProps {
    pub theme: Option<ButtonThemeWith>,
    pub children: Element,
    pub onpress: Option<EventHandler<PressEvent>>,
    pub onclick: Option<EventHandler<()>>,
}
#[derive(Debug, Default, PartialEq, Clone, Copy)]
pub enum ButtonStatus {
    #[default]
    Idle,
    Hovering,
}
#[allow(non_snake_case)]
pub fn Button(
    ButtonProps {
        onpress,
        children,
        theme,
        onclick,
    }: ButtonProps,
) -> Element {
    let mut focus = use_focus();
    let mut status = use_signal(ButtonStatus::default);
    let platform = use_platform();
    let focus_id = focus.attribute();
    let ButtonTheme {
        background,
        hover_background,
        border_fill,
        focus_border_fill,
        padding,
        margin,
        corner_radius,
        width,
        height,
        font_theme,
        shadow,
    } = use_applied_theme!(&theme, button);
    let onpointerup = {
        to_owned![onpress, onclick];
        move |ev: PointerEvent| {
            focus.focus();
            if let Some(onpress) = &onpress {
                let is_valid = match ev.data.pointer_type {
                    PointerType::Mouse {
                        trigger_button: Some(MouseButton::Left),
                    } => true,
                    PointerType::Touch { phase, .. } => phase == TouchPhase::Ended,
                    _ => false,
                };
                if is_valid {
                    onpress.call(PressEvent::Pointer(ev))
                }
            } else if let Some(onclick) = onclick {
                if let PointerType::Mouse {
                    trigger_button: Some(MouseButton::Left),
                    ..
                } = ev.data.pointer_type
                {
                    onclick.call(())
                }
            }
        }
    };
    use_drop(move || {
        if *status.read() == ButtonStatus::Hovering {
            platform.set_cursor(CursorIcon::default());
        }
    });
    let onmouseenter = move |_| {
        platform.set_cursor(CursorIcon::Pointer);
        status.set(ButtonStatus::Hovering);
    };
    let onmouseleave = move |_| {
        platform.set_cursor(CursorIcon::default());
        status.set(ButtonStatus::default());
    };
    let onkeydown = move |ev: KeyboardEvent| {
        if focus.validate_keydown(&ev) {
            if let Some(onpress) = &onpress {
                onpress.call(PressEvent::Key(ev))
            }
        }
    };
    let background = match *status.read() {
        ButtonStatus::Hovering => hover_background,
        ButtonStatus::Idle => background,
    };
    let border = if focus.is_selected() {
        format!("2 solid {focus_border_fill}")
    } else {
        format!("1 solid {border_fill}")
    };
    rsx!(
        rect {
            onpointerup,
            onmouseenter,
            onmouseleave,
            onkeydown,
            focus_id,
            width: "{width}",
            height: "{height}",
            padding: "{padding}",
            margin: "{margin}",
            focusable: "true",
            overflow: "clip",
            role: "button",
            color: "{font_theme.color}",
            shadow: "{shadow}",
            border: "{border}",
            corner_radius: "{corner_radius}",
            background: "{background}",
            text_align: "center",
            main_align: "center",
            cross_align: "center",
            {&children}
        }
    )
}
#[cfg(test)]
mod test {
    use freya::prelude::*;
    use freya_testing::prelude::*;
    #[tokio::test]
    pub async fn button() {
        fn button_app() -> Element {
            let mut state = use_signal(|| false);
            rsx!(
                Button {
                    onpress: move |_| state.toggle(),
                    label {
                        "{state}"
                    }
                }
            )
        }
        let mut utils = launch_test(button_app);
        let root = utils.root();
        let label = root.get(0).get(0);
        utils.wait_for_update().await;
        assert_eq!(label.get(0).text(), Some("false"));
        utils.push_event(PlatformEvent::Mouse {
            name: EventName::Click,
            cursor: (5.0, 5.0).into(),
            button: Some(MouseButton::Left),
        });
        utils.wait_for_update().await;
        assert_eq!(label.get(0).text(), Some("true"));
        utils.push_event(PlatformEvent::Touch {
            name: EventName::TouchEnd,
            location: (5.0, 5.0).into(),
            finger_id: 1,
            phase: TouchPhase::Ended,
            force: None,
        });
        utils.wait_for_update().await;
        assert_eq!(label.get(0).text(), Some("false"));
    }
}