#![allow(non_snake_case)]


use js_sys::Array;


use wasm_bindgen::{prelude::*, JsCast, JsValue};
use web_sys::{
    Blob, HtmlAnchorElement, HtmlDialogElement, HtmlElement,
    HtmlFormElement, HtmlInputElement, Url,
};
use yew::prelude::*;
use yewdux::prelude::*;

use crate::components::states::{CurrentTransfer, MainState};

use crate::state_to_csv;

type NoParamsCallback = Box<dyn Fn(())>;

pub fn create_close_button(close: &Closure<dyn FnMut(Event)>) -> HtmlElement {
    let document = web_sys::window().unwrap().document().unwrap();
    let close_button = document
        .create_element("button")
        .unwrap()
        .dyn_into::<HtmlElement>()
        .unwrap();
    close_button.set_text_content(Some("✖"));
    close_button.set_class_name("close_button");
    close_button.set_onclick(Some(close.as_ref().unchecked_ref()));

    close_button
}

pub fn toggle_in_transfer_hashes_callback(
    main_dispatch: Dispatch<MainState>,
) -> Callback<web_sys::MouseEvent> {
    let main_dispatch = main_dispatch.clone();
    Callback::from(move |_| {
        main_dispatch.reduce_mut(|state| {
            state.preferences.in_transfer_hashes ^= true;
        })
    })
}

pub fn new_plate_dialog_callback(
    new_plate_dialog_is_open: UseStateHandle<bool>,
) -> NoParamsCallback {
    let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
    Box::new(move |_| {
        new_plate_dialog_is_open.set(false);
    })
}

pub fn open_new_plate_dialog_callback(
    new_plate_dialog_is_open: UseStateHandle<bool>,
) -> NoParamsCallback {
    let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
    Box::new(move |_| {
        new_plate_dialog_is_open.set(true);
    })
}

pub fn new_button_callback(
    main_dispatch: Dispatch<MainState>,
    ct_dispatch: Dispatch<CurrentTransfer>,
) -> Callback<web_sys::MouseEvent> {
    Callback::from(move |_| {
        let window = web_sys::window().unwrap();
        let confirm =
            window.confirm_with_message("This will reset all plates and transfers. Proceed?");
        if let Ok(confirm) = confirm {
            if confirm {
                main_dispatch.set(MainState::default());
                ct_dispatch.set(CurrentTransfer::default());
            }
        }
    })
}

fn save_str(data: &str, name: &str) {
    let blob =
        Blob::new_with_str_sequence(&Array::from_iter(std::iter::once(JsValue::from_str(data))));
    if let Ok(blob) = blob {
        let url = Url::create_object_url_with_blob(&blob).expect("We have a blob, why not URL?");
        // Beneath is the cool hack to download files
        let window = web_sys::window().unwrap();
        let document = window.document().unwrap();
        let anchor = document
            .create_element("a")
            .unwrap()
            .dyn_into::<HtmlAnchorElement>()
            .unwrap();
        anchor.set_download(name);
        anchor.set_href(&url);
        anchor.click();
    }
}

pub fn export_csv_button_callback(main_state: std::rc::Rc<MainState>) -> Callback<MouseEvent> {
    Callback::from(move |_| {
        if main_state.transfers.is_empty() {
            web_sys::window()
                .unwrap()
                .alert_with_message("No transfers to export.")
                .unwrap();
            return;
        }
        web_sys::window().unwrap().alert_with_message("CSV export is currently not importable. Export as JSON if you'd like to back up your work!").unwrap();
        if let Ok(csv) = state_to_csv(&main_state) {
            save_str(&csv, "transfers.csv");
        }
    })
}

pub fn export_json_button_callback(main_state: std::rc::Rc<MainState>) -> Callback<MouseEvent> {
    Callback::from(move |_| {
        if let Ok(json) = serde_json::to_string(&main_state) {
            save_str(&json, "plate-tool-state.json");
        } else {
            web_sys::window()
                .unwrap()
                .alert_with_message("Failed to export.")
                .unwrap();
        }
    })
}

pub fn input_json_input_callback(
    main_dispatch: Dispatch<MainState>,
    modal: HtmlDialogElement,
) -> Closure<dyn FnMut(Event)> {
    Closure::<dyn FnMut(_)>::new(move |e: Event| {
        if let Some(input) = e.current_target() {
            let input = input
                .dyn_into::<HtmlInputElement>()
                .expect("We know this is an input.");
            if let Some(files) = input.files() {
                if let Some(file) = files.get(0) {
                    let fr = web_sys::FileReader::new().unwrap();
                    fr.read_as_text(&file).unwrap();
                    let fr1 = fr.clone(); // Clone to avoid outliving closure
                    let main_dispatch = main_dispatch.clone(); // Clone to satisfy FnMut
                                                               // trait
                    let modal = modal.clone();
                    let onload = Closure::<dyn FnMut(_)>::new(move |_: Event| {
                        if let Some(value) = &fr1.result().ok().and_then(|v| v.as_string()) {
                            let ms = serde_json::from_str::<MainState>(value);
                            match ms {
                                Ok(ms) => main_dispatch.set(ms),
                                Err(e) => log::debug!("{:?}", e),
                            };
                            modal.close();
                        }
                    });
                    fr.set_onload(Some(onload.as_ref().unchecked_ref()));
                    onload.forget(); // Magic (don't touch)
                }
            }
        }
    })
}

pub fn import_json_button_callback(main_dispatch: Dispatch<MainState>) -> Callback<MouseEvent> {
    Callback::from(move |_| {
        let window = web_sys::window().unwrap();
        let document = window.document().unwrap();
        let body = document.body().unwrap();
        let modal = document
            .create_element("dialog")
            .unwrap()
            .dyn_into::<HtmlDialogElement>()
            .unwrap();
        modal.set_text_content(Some("Import File:"));
        let onclose_callback = {
            let modal = modal.clone();
            Closure::<dyn FnMut(_)>::new(move |_: Event| {
                modal.remove();
            })
        };
        modal.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));

        let close_button = create_close_button(&onclose_callback);
        onclose_callback.forget();
        modal.append_child(&close_button).unwrap();

        let form = document
            .create_element("form")
            .unwrap()
            .dyn_into::<HtmlFormElement>()
            .unwrap();
        let input = document
            .create_element("input")
            .unwrap()
            .dyn_into::<HtmlInputElement>()
            .unwrap();
        input.set_type("file");
        input.set_accept(".json");
        form.append_child(&input).unwrap();

        let input_callback = {
            let main_dispatch = main_dispatch.clone();
            let modal = modal.clone();
            input_json_input_callback(main_dispatch, modal)
        };
        input.set_onchange(Some(input_callback.as_ref().unchecked_ref()));
        input_callback.forget(); // Magic straight from the docs, don't touch :(

        modal.append_child(&form).unwrap();
        body.append_child(&modal).unwrap();
        modal.show_modal().unwrap();
    })
}

pub use super::import_csv_callbacks::import_transfer_csv_callback;

pub use super::import_csv_callbacks::import_transfer_csv_submit_callback;

pub use super::import_csv_callbacks::import_transfer_csv_onload_callback;

pub use super::import_csv_callbacks::import_transfer_csv_input_callback;
