use std::cell::Cell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;

use crate::webview::{FromBase64, HANDLER_NAME, ToBase64};

fn get_ipc() -> Option<js_sys::Function> {
    let window = web_sys::window()?;
    let ipc = js_sys::Reflect::get(&window, &"ipc".into()).ok()?;
    ipc.dyn_ref::<js_sys::Object>()
        .and_then(|object| js_sys::Reflect::get(object, &"postMessage".into()).ok())?
        .dyn_ref::<js_sys::Function>()
        .cloned()
}

pub fn send<Cmd: ToBase64>(cmd: &Cmd) {
    if let (Some(data), Some(post_message)) = (cmd.to_base64(), get_ipc()) {
        let ipc = js_sys::Reflect::get(&web_sys::window().unwrap(), &"ipc".into()).unwrap();
        let _ = post_message.call1(&ipc, &JsValue::from_str(&data));
    }
}

pub fn connect<Cmd, Evt>(ready: Cmd, handler: impl Fn(Evt) + 'static)
where
    Cmd: ToBase64 + 'static,
    Evt: FromBase64 + 'static,
{
    let window = web_sys::window().unwrap();
    let interval_id: Rc<Cell<Option<i32>>> = Rc::new(Cell::new(None));

    let closure = Closure::wrap(Box::new({
        let window = window.clone();
        let interval_id = interval_id.clone();
        move |data: String| {
            if let Some(event) = Evt::from_base64(&data) {
                if let Some(id) = interval_id.get() {
                    window.clear_interval_with_handle(id);
                    interval_id.set(None);
                }
                handler(event);
            }
        }
    }) as Box<dyn Fn(String)>);
    js_sys::Reflect::set(&window, &HANDLER_NAME.into(), closure.as_ref()).unwrap();
    closure.forget();

    let poll = Closure::wrap(Box::new({
        let interval_id = interval_id.clone();
        move || {
            if interval_id.get().is_some() && get_ipc().is_some() {
                send(&ready);
            }
        }
    }) as Box<dyn Fn()>);
    let id = window
        .set_interval_with_callback_and_timeout_and_arguments_0(poll.as_ref().unchecked_ref(), 50)
        .unwrap();
    interval_id.set(Some(id));
    poll.forget();
}
