use std::collections::HashSet;

use lazy_static::lazy_static;
use regex::Regex;
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::{
    FileReader, HtmlButtonElement, HtmlDialogElement, HtmlFormElement, HtmlInputElement,
    HtmlOptionElement, HtmlSelectElement,
};
use yew::prelude::*;
use yewdux::prelude::*;

use crate::components::states::MainState;
use plate_tool_lib::util::letters_to_num;

use plate_tool_lib::transfer::Transfer;
use plate_tool_lib::transfer_region::{Region, TransferRegion};

use plate_tool_lib::csv::TransferRecord;

use super::main_window_callbacks::create_close_button;

pub fn import_transfer_csv_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 = import_transfer_csv_onload_callback(main_dispatch, fr1, modal);
                    fr.set_onload(Some(onload.as_ref().unchecked_ref()));
                    onload.forget(); // Magic (don't touch)
                }
            }
        }
    })
}

pub fn import_transfer_csv_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(".csv");
        form.append_child(&input).unwrap();

        let input_callback = {
            let main_dispatch = main_dispatch.clone();
            let modal = modal.clone();
            import_transfer_csv_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 fn import_transfer_csv_onload_callback(
    main_dispatch: Dispatch<MainState>,
    file_reader: FileReader,
    modal: HtmlDialogElement,
) -> Closure<dyn FnMut(Event)> {
    Closure::<dyn FnMut(_)>::new(move |_: Event| {
        if let Some(value) = &file_reader.result().ok().and_then(|v| v.as_string()) {
            let records = plate_tool_lib::csv::read_csv(value);

            let mut sources: HashSet<String> = HashSet::new();
            let mut destinations: HashSet<String> = HashSet::new();
            for record in records.iter() {
                sources.insert(record.source_plate.clone());
                destinations.insert(record.destination_plate.clone());
            }

            let window = web_sys::window().unwrap();
            let document = window.document().unwrap();
            let form = document
                .create_element("form")
                .unwrap()
                .dyn_into::<HtmlFormElement>()
                .unwrap();
            let from_source = document
                .create_element("select")
                .unwrap()
                .dyn_into::<HtmlSelectElement>()
                .unwrap();
            for source in sources {
                let option = document
                    .create_element("option")
                    .unwrap()
                    .dyn_into::<HtmlOptionElement>()
                    .unwrap();
                option.set_value(&source);
                option.set_text(&source);
                from_source.append_child(&option).unwrap();
            }
            let to_source = document
                .create_element("select")
                .unwrap()
                .dyn_into::<HtmlSelectElement>()
                .unwrap();
            for source in &main_dispatch.get().source_plates {
                let option = document
                    .create_element("option")
                    .unwrap()
                    .dyn_into::<HtmlOptionElement>()
                    .unwrap();
                option.set_value(&source.name);
                option.set_text(&source.name);
                to_source.append_child(&option).unwrap();
            }
            let from_dest = document
                .create_element("select")
                .unwrap()
                .dyn_into::<HtmlSelectElement>()
                .unwrap();
            for dest in destinations {
                let option = document
                    .create_element("option")
                    .unwrap()
                    .dyn_into::<HtmlOptionElement>()
                    .unwrap();
                option.set_value(&dest);
                option.set_text(&dest);
                from_dest.append_child(&option).unwrap();
            }
            let to_dest = document
                .create_element("select")
                .unwrap()
                .dyn_into::<HtmlSelectElement>()
                .unwrap();
            for dest in &main_dispatch.get().destination_plates {
                let option = document
                    .create_element("option")
                    .unwrap()
                    .dyn_into::<HtmlOptionElement>()
                    .unwrap();
                option.set_value(&dest.name);
                option.set_text(&dest.name);
                to_dest.append_child(&option).unwrap();
            }
            let submit = document
                .create_element("button")
                .unwrap()
                .dyn_into::<HtmlButtonElement>()
                .unwrap();
            submit.set_value("Submit");
            submit.set_inner_text("Submit");
            let submit_callback = {
                let main_dispatch = main_dispatch.clone();
                let from_source = from_source.clone();
                let to_source = to_source.clone();
                let from_dest = from_dest.clone();
                let to_dest = to_dest.clone();
                import_transfer_csv_submit_callback(
                    main_dispatch,
                    from_source,
                    to_source,
                    from_dest,
                    to_dest,
                    records,
                )
            };
            submit.set_onclick(Some(submit_callback.as_ref().unchecked_ref()));
            submit_callback.forget();

            form.append_child(&from_source).unwrap();
            form.append_child(&to_source).unwrap();
            form.append_child(&from_dest).unwrap();
            form.append_child(&to_dest).unwrap();
            modal.append_child(&submit).unwrap();
            modal.append_child(&form).unwrap();
        }
    })
}

pub fn import_transfer_csv_submit_callback(
    main_dispatch: Dispatch<MainState>,
    from_source: HtmlSelectElement,
    to_source: HtmlSelectElement,
    from_dest: HtmlSelectElement,
    to_dest: HtmlSelectElement,
    records: Vec<TransferRecord>,
) -> Closure<dyn FnMut(Event)> {
    Closure::<dyn FnMut(_)>::new(move |_: Event| {
        let from_source = from_source.value();
        let to_source = to_source.value();
        let from_dest = from_dest.value();
        let to_dest = to_dest.value();

        lazy_static! {
            static ref REGEX: Regex = Regex::new(r"([A-Z,a-z]+)(\d+)").unwrap();
        }
        let records: Vec<((u8, u8), (u8, u8))> = records
            .iter()
            .filter(|record| record.source_plate == from_source)
            .filter(|record| record.destination_plate == from_dest)
            .map(|record| {
                let c1 = REGEX.captures(&record.source_well).unwrap();
                let c2 = REGEX.captures(&record.destination_well).unwrap();
                log::debug!("{} {}", &record.source_well, &record.destination_well);
                log::debug!("{},{}  {},{}", &c1[1], &c1[2], &c2[1], &c2[2]);

                (
                    (
                        letters_to_num(&c1[1]).unwrap(),
                        c1[2].parse::<u8>().unwrap(),
                    ),
                    (
                        letters_to_num(&c2[1]).unwrap(),
                        c2[2].parse::<u8>().unwrap(),
                    ),
                )
            })
            .collect();

        let spi = main_dispatch
            .get()
            .source_plates
            .iter()
            .find(|src| src.name == to_source)
            .unwrap()
            .clone();
        let dpi = main_dispatch
            .get()
            .destination_plates
            .iter()
            .find(|dest| dest.name == to_dest)
            .unwrap()
            .clone();

        let custom_region = Region::new_custom(&records);
        let transfer_region = TransferRegion {
            source_region: custom_region.clone(),
            dest_region: custom_region,
            interleave_source: (1, 1),
            interleave_dest: (1, 1),
            source_plate: spi.plate,
            dest_plate: dpi.plate,
        };

        let transfer = Transfer::new(spi, dpi, transfer_region, "Custom Transfer".to_string());
        main_dispatch.reduce_mut(|state| {
            state.transfers.push(transfer);
            state.selected_transfer = state
                .transfers
                .last()
                .expect("An element should have just been added")
                .get_uuid();
        });
    })
}
