use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
use crate::value::Value;
use crate::js::*;
use imbl::Vector;
use std::rc::Rc;

const OK: Value = Value::Keyword("ok");
const ERR: Value = Value::Keyword("err");

#[wasm_bindgen(module = "/pkg/worker.js")]
extern "C" {
    #[wasm_bindgen(catch)]
    async fn io (output: String) -> Result<JsValue, JsValue>;
}

type Url = Value; // expect a string
type Commands = Value; // expect a list of command tuples

#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(tag = "verb", content = "data")]
pub enum MsgOut {
    Console(Value), 
    Commands(Commands), 
    Fetch(Url), 
    Complete(Value),
    Error(String),
    Ready
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "verb", content = "data")]
pub enum MsgIn {
    Input(String),
    Fetch(String, f64, String),
    Kill,
    Keys(Vec<String>),
}

impl std::fmt::Display for MsgIn {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            MsgIn::Input(str) => write!(f, "Input: {str}"),
            MsgIn::Kill => write!(f, "Kill"),
            MsgIn::Fetch(url, code, text) => write!(f, "Fetch: {url} :: {code} ::\n{text}"),
            MsgIn::Keys(keys) => write!(f, "Keys: {:?}", keys),
        }
    }
}

impl MsgIn {
    pub fn into_value(self) -> Value {
        match self {
            MsgIn::Input(str) => Value::from_string(str),
            MsgIn::Fetch(url, status_f64, string) => {
                let url = Value::from_string(url);
                let status = Value::from_f64(status_f64);
                let text = Value::from_string(string);
                let result_tuple = if status_f64 == 200.0 {
                    Value::tuple(vec![OK, text])
                } else {
                    Value::tuple(vec![ERR, status])
                };
                Value::tuple(vec![url, result_tuple])
            }
            MsgIn::Kill => Value::Nothing,
            MsgIn::Keys(downkeys) => {
                let mut vector = Vector::new();
                for key in downkeys {
                    vector.push_back(Value::String(Rc::new(key)));
                }
                Value::list(vector)
            }
        }
    }
}

pub async fn send_err_to_ludus_console(msg: String) {
    console_log!("{msg}");
    do_io(vec![MsgOut::Ready, MsgOut::Error(msg)]).await;
}

pub async fn do_io (msgs: Vec<MsgOut>) -> Vec<MsgIn> {
    let json = serde_json::to_string(&msgs).unwrap();
    let inbox = io(json).await;
    let inbox = match inbox {
        Ok(msgs) => msgs,
        Err(errs) => {
            // console_log!("error receiving messages in io; {:?}", errs);
            return vec![];
        }
    };
    let inbox = inbox.as_string().expect("response should be a string");
    let inbox: Vec<MsgIn> = serde_json::from_str(inbox.as_str()).expect("response from js should be valid");
    if !inbox.is_empty() {
        // console_log!("ludus received messages");
        for msg in inbox.iter() {
            console_log!("{}", msg);
        }
    }
    inbox
}

