use std::collections::HashMap;

use dioxus::html::geometry::euclid::Vector3D;
use dioxus::prelude::*;
use dioxus_desktop::DesktopContext;

#[path = "./utils.rs"]
mod utils;

pub fn main() {
    #[cfg(not(windows))]
    utils::check_app_exits(app);
}

static RECEIVED_EVENTS: GlobalSignal<usize> = Signal::global(|| 0);

fn app() -> Element {
    let desktop_context: DesktopContext = consume_context();

    let received = RECEIVED_EVENTS();
    let expected = utils::EXPECTED_EVENTS();

    use_memo(move || {
        println!("expecting {} events", utils::EXPECTED_EVENTS());
        println!("received {} events", RECEIVED_EVENTS());
    });

    if expected != 0 && received == expected {
        println!("all events received");
        desktop_context.close();
    }

    rsx! {
        div {
            test_mounted {}
            test_button {}
            test_mouse_move_div {}
            test_mouse_click_div {}
            test_mouse_dblclick_div {}
            test_mouse_down_div {}
            test_mouse_up_div {}
            test_mouse_scroll_div {}
            test_key_down_div {}
            test_key_up_div {}
            test_key_press_div {}
            test_focus_in_div {}
            test_focus_out_div {}
            test_form_input {}
            test_form_submit {}
            test_select_multiple_options {}
            test_unicode {}
        }
    }
}

fn test_mounted() -> Element {
    use_hook(|| utils::EXPECTED_EVENTS.with_mut(|x| *x += 1));
    let mut onmounted_triggered = use_signal(|| false);

    rsx! {
        div {
            width: "100px",
            height: "100px",
            onmounted: move |evt| async move {
                let rect = evt.get_client_rect().await.unwrap();
                println!("rect: {:?}", rect);
                assert_eq!(rect.width(), 100.0);
                assert_eq!(rect.height(), 100.0);
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
                // Onmounted should only be called once
                let mut onmounted_triggered_write = onmounted_triggered.write();
                assert!(!*onmounted_triggered_write);
                *onmounted_triggered_write = true;
            }
        }
    }
}

fn test_button() -> Element {
    utils::mock_event(
        "button",
        r#"new MouseEvent("click", {
            view: window,
            bubbles: true,
            cancelable: true,
            button: 0,
        })"#,
    );

    rsx! {
        button {
            id: "button",
            onclick: move |event| {
                println!("{:?}", event.data);
                assert!(event.data.modifiers().is_empty());
                assert!(event.data.held_buttons().is_empty());
                assert_eq!(
                    event.data.trigger_button(),
                    Some(dioxus_html::input_data::MouseButton::Primary),
                );
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}

fn test_mouse_move_div() -> Element {
    utils::mock_event(
        "mouse_move_div",
        r#"new MouseEvent("mousemove", {
        view: window,
        bubbles: true,
        cancelable: true,
        buttons: 2,
        })"#,
    );

    rsx! {
        div {
            id: "mouse_move_div",
            onmousemove: move |event| {
                println!("{:?}", event.data);
                assert!(event.data.modifiers().is_empty());
                assert!(
                    event
                        .data
                        .held_buttons()
                        .contains(dioxus_html::input_data::MouseButton::Secondary),
                );
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}

fn test_mouse_click_div() -> Element {
    utils::mock_event(
        "mouse_click_div",
        r#"new MouseEvent("click", {
        view: window,
        bubbles: true,
        cancelable: true,
        buttons: 2,
        button: 2,
        })"#,
    );

    rsx! {
        div {
            id: "mouse_click_div",
            onclick: move |event| {
                println!("{:?}", event.data);
                assert!(event.data.modifiers().is_empty());
                assert!(
                    event
                        .data
                        .held_buttons()
                        .contains(dioxus_html::input_data::MouseButton::Secondary),
                );
                assert_eq!(
                    event.data.trigger_button(),
                    Some(dioxus_html::input_data::MouseButton::Secondary),
                );
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}

fn test_mouse_dblclick_div() -> Element {
    utils::mock_event(
        "mouse_dblclick_div",
        r#"new MouseEvent("dblclick", {
            view: window,
            bubbles: true,
            cancelable: true,
            buttons: 1|2,
            button: 2,
        })"#,
    );

    rsx! {
        div {
            id: "mouse_dblclick_div",
            ondoubleclick: move |event| {
                println!("{:?}", event.data);
                assert!(event.data.modifiers().is_empty());
                assert!(
                    event
                        .data
                        .held_buttons()
                        .contains(dioxus_html::input_data::MouseButton::Primary),
                );
                assert!(
                    event
                        .data
                        .held_buttons()
                        .contains(dioxus_html::input_data::MouseButton::Secondary),
                );
                assert_eq!(
                    event.data.trigger_button(),
                    Some(dioxus_html::input_data::MouseButton::Secondary),
                );
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}

fn test_mouse_down_div() -> Element {
    utils::mock_event(
        "mouse_down_div",
        r#"new MouseEvent("mousedown", {
            view: window,
            bubbles: true,
            cancelable: true,
            buttons: 2,
            button: 2,
        })"#,
    );

    rsx! {
        div {
            id: "mouse_down_div",
            onmousedown: move |event| {
                println!("{:?}", event.data);
                assert!(event.data.modifiers().is_empty());
                assert!(
                    event
                        .data
                        .held_buttons()
                        .contains(dioxus_html::input_data::MouseButton::Secondary),
                );
                assert_eq!(
                    event.data.trigger_button(),
                    Some(dioxus_html::input_data::MouseButton::Secondary),
                );
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}

fn test_mouse_up_div() -> Element {
    utils::mock_event(
        "mouse_up_div",
        r#"new MouseEvent("mouseup", {
            view: window,
            bubbles: true,
            cancelable: true,
            buttons: 0,
            button: 0,
        })"#,
    );

    rsx! {
        div {
            id: "mouse_up_div",
            onmouseup: move |event| {
                println!("{:?}", event.data);
                assert!(event.data.modifiers().is_empty());
                assert!(event.data.held_buttons().is_empty());
                assert_eq!(
                    event.data.trigger_button(),
                    Some(dioxus_html::input_data::MouseButton::Primary),
                );
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}

fn test_mouse_scroll_div() -> Element {
    utils::mock_event(
        "wheel_div",
        r#"new WheelEvent("wheel", {
            view: window,
            deltaX: 1.0,
            deltaY: 2.0,
            deltaZ: 3.0,
            deltaMode: 0x00,
            bubbles: true,
        })"#,
    );

    rsx! {
        div {
            id: "wheel_div",
            width: "100px",
            height: "100px",
            background_color: "red",
            onwheel: move |event| {
                println!("{:?}", event.data);
                let dioxus_html::geometry::WheelDelta::Pixels(delta) = event.data.delta() else {
                    panic!("Expected delta to be in pixels")
                };
                assert_eq!(delta, Vector3D::new(1.0, 2.0, 3.0));
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}

fn test_key_down_div() -> Element {
    utils::mock_event(
        "key_down_div",
        r#"new KeyboardEvent("keydown", {
        key: "a",
        code: "KeyA",
        location: 0,
        repeat: true,
        keyCode: 65,
        charCode: 97,
        char: "a",
        charCode: 0,
        altKey: false,
        ctrlKey: false,
        metaKey: false,
        shiftKey: false,
        isComposing: true,
        which: 65,
        bubbles: true,
        })"#,
    );
    rsx! {
        input {
            id: "key_down_div",
            onkeydown: move |event| {
                println!("{:?}", event.data);
                assert!(event.data.modifiers().is_empty());
                assert_eq!(event.data.key().to_string(), "a");
                assert_eq!(event.data.code().to_string(), "KeyA");
                assert_eq!(event.data.location(), Location::Standard);
                assert!(event.data.is_auto_repeating());
                assert!(event.data.is_composing());
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}
fn test_key_up_div() -> Element {
    utils::mock_event(
        "key_up_div",
        r#"new KeyboardEvent("keyup", {
        key: "a",
        code: "KeyA",
        location: 0,
        repeat: false,
        keyCode: 65,
        charCode: 97,
        char: "a",
        charCode: 0,
        altKey: false,
        ctrlKey: false,
        metaKey: false,
        shiftKey: false,
        isComposing: false,
        which: 65,
        bubbles: true,
        })"#,
    );

    rsx! {
        input {
            id: "key_up_div",
            onkeyup: move |event| {
                println!("{:?}", event.data);
                assert!(event.data.modifiers().is_empty());
                assert_eq!(event.data.key().to_string(), "a");
                assert_eq!(event.data.code().to_string(), "KeyA");
                assert_eq!(event.data.location(), Location::Standard);
                assert!(!event.data.is_auto_repeating());
                assert!(!event.data.is_composing());
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}
fn test_key_press_div() -> Element {
    utils::mock_event(
        "key_press_div",
        r#"new KeyboardEvent("keypress", {
        key: "a",
        code: "KeyA",
        location: 0,
        repeat: false,
        keyCode: 65,
        charCode: 97,
        char: "a",
        charCode: 0,
        altKey: false,
        ctrlKey: false,
        metaKey: false,
        shiftKey: false,
        isComposing: false,
        which: 65,
        bubbles: true,
        })"#,
    );
    rsx! {
        input {
            id: "key_press_div",
            onkeypress: move |event| {
                println!("{:?}", event.data);
                assert!(event.data.modifiers().is_empty());
                assert_eq!(event.data.key().to_string(), "a");
                assert_eq!(event.data.code().to_string(), "KeyA");
                assert_eq!(event.data.location(), Location::Standard);
                assert!(!event.data.is_auto_repeating());
                assert!(!event.data.is_composing());
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}

fn test_focus_in_div() -> Element {
    utils::mock_event(
        "focus_in_div",
        r#"new FocusEvent("focusin", {bubbles: true})"#,
    );

    rsx! {
        input {
            id: "focus_in_div",
            onfocusin: move |event| {
                println!("{:?}", event.data);
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}

fn test_focus_out_div() -> Element {
    utils::mock_event(
        "focus_out_div",
        r#"new FocusEvent("focusout",{bubbles: true})"#,
    );
    rsx! {
        input {
            id: "focus_out_div",
            onfocusout: move |event| {
                println!("{:?}", event.data);
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}

fn test_form_input() -> Element {
    let mut values = use_signal(HashMap::new);

    utils::mock_event_with_extra(
        "form-username",
        r#"new Event("input", { bubbles: true, cancelable: true, composed: true })"#,
        r#"element.value = "hello";"#,
    );

    let set_username = move |ev: FormEvent| {
        values.set(ev.values());

        // The value of the input should match
        assert_eq!(ev.value(), "hello");

        // And then the value the form gives us should also match
        values.with_mut(|x| {
            assert_eq!(x.get("username").unwrap(), "hello");
            assert_eq!(x.get("full-name").unwrap(), "lorem");
            assert_eq!(x.get("password").unwrap(), "ipsum");
            assert_eq!(x.get("color").unwrap(), "red");
        });
        RECEIVED_EVENTS.with_mut(|x| *x += 1);
    };

    rsx! {
        div {
            h1 { "Form" }
            form {
                id: "form",
                oninput: move |ev| {
                    values.set(ev.values());
                },
                onsubmit: move |ev| {
                    println!("{:?}", ev);
                },
                input {
                    r#type: "text",
                    name: "username",
                    id: "form-username",
                    oninput: set_username
                }
                input { r#type: "text", name: "full-name", value: "lorem" }
                input { r#type: "password", name: "password", value: "ipsum" }
                input {
                    r#type: "radio",
                    name: "color",
                    value: "red",
                    checked: true
                }
                input { r#type: "radio", name: "color", value: "blue" }
                button { r#type: "submit", value: "Submit", "Submit the form" }
            }
        }
    }
}

fn test_form_submit() -> Element {
    let mut values = use_signal(HashMap::new);

    utils::mock_event_with_extra(
        "form-submitter",
        r#"new Event("submit", { bubbles: true, cancelable: true, composed: true })"#,
        r#"element.submit();"#,
    );

    let set_values = move |ev: FormEvent| {
        values.set(ev.values());
        values.with_mut(|x| {
            assert_eq!(x.get("username").unwrap(), "goodbye");
            assert_eq!(x.get("full-name").unwrap(), "lorem");
            assert_eq!(x.get("password").unwrap(), "ipsum");
            assert_eq!(x.get("color").unwrap(), "red");
        });
        RECEIVED_EVENTS.with_mut(|x| *x += 1);
    };

    rsx! {
        div {
            h1 { "Form" }
            form { id: "form-submitter", onsubmit: set_values,
                input {
                    r#type: "text",
                    name: "username",
                    id: "username",
                    value: "goodbye"
                }
                input { r#type: "text", name: "full-name", value: "lorem" }
                input { r#type: "password", name: "password", value: "ipsum" }
                input {
                    r#type: "radio",
                    name: "color",
                    value: "red",
                    checked: true
                }
                input { r#type: "radio", name: "color", value: "blue" }
                button { r#type: "submit", value: "Submit", "Submit the form" }
            }
        }
    }
}

fn test_select_multiple_options() -> Element {
    utils::mock_event_with_extra(
        "select-many",
        r#"new Event("input", { bubbles: true, cancelable: true, composed: true })"#,
        r#"
            document.getElementById('usa').selected = true;
            document.getElementById('canada').selected = true;
            document.getElementById('mexico').selected = false;
        "#,
    );

    rsx! {
        select {
            id: "select-many",
            name: "country",
            multiple: true,
            oninput: move |ev| {
                let values = ev.value();
                let values = values.split(',').collect::<Vec<_>>();
                assert_eq!(values, vec!["usa", "canada"]);
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            },
            option { id: "usa", value: "usa", "USA" }
            option { id: "canada", value: "canada", "Canada" }
            option { id: "mexico", value: "mexico", selected: true, "Mexico" }
        }
    }
}

fn test_unicode() -> Element {
    // emulate an oninput event with a unicode character
    utils::mock_event_with_extra(
        "unicode",
        r#"new InputEvent("input", {
            inputType: 'insertText',
            bubbles: true,
            cancelable: true,
        })"#,
        r#"
            element.value = "🦀";
        "#,
    );

    rsx! {
        input {
            id: "unicode",
            oninput: move |event| {
                println!("{:?}", event.data);
                assert_eq!(event.data.value(), "🦀");
                RECEIVED_EVENTS.with_mut(|x| *x += 1);
            }
        }
    }
}
