use super::arg_num;
use crate::host::{with_host, JsObj};
use fusevm::Value;
use indexmap::IndexMap;
pub const TIMEOUT_METHODS: &[&str] = &[
"ref",
"unref",
"hasRef",
"refresh",
"close",
"toString",
"@@toPrimitive",
];
pub const IMMEDIATE_METHODS: &[&str] = &["ref", "unref", "hasRef", "close", "valueOf", "toString"];
pub fn new_handle(id: u64, tag: &'static str) -> Value {
with_host(|h| {
let mut m = IndexMap::new();
m.insert("@@native".into(), h.new_str(tag));
m.insert("@@timerId".into(), Value::Float(id as f64));
h.new_object(m)
})
}
pub fn handle_id(v: &Value) -> Option<u64> {
with_host(|h| match h.get(v) {
Some(JsObj::Object(p)) => p.get("@@timerId").map(|n| h.to_number(n) as u64),
_ => None,
})
}
pub fn instance_call(recv: &Value, method: &str, _args: &[Value]) -> Result<Value, String> {
let id = handle_id(recv).unwrap_or(0);
match method {
"ref" => {
with_host(|h| h.set_timer_refed(id, true));
Ok(recv.clone())
}
"unref" => {
with_host(|h| h.set_timer_refed(id, false));
Ok(recv.clone())
}
"hasRef" => Ok(Value::Bool(with_host(|h| h.timer_has_ref(id)))),
"refresh" => {
with_host(|h| h.refresh_timer(id));
Ok(recv.clone())
}
"close" => {
with_host(|h| h.cancel_timer(id));
Ok(recv.clone())
}
"@@toPrimitive" => Ok(Value::Float(id as f64)),
"toString" => Ok(with_host(|h| h.new_str(id.to_string()))),
_ => Err(crate::host::type_error(&format!(
"timeout.{method} is not a function"
))),
}
}
pub const METHODS: &[&str] = &[
"setTimeout",
"setInterval",
"setImmediate",
"clearTimeout",
"clearInterval",
"clearImmediate",
];
pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
let global = match method {
"setTimeout" | "setInterval" | "setImmediate" | "clearTimeout" | "clearInterval" => method,
"clearImmediate" => "clearTimeout",
_ => return None,
};
Some(crate::builtins::call_builtin_function(
global,
args.to_vec(),
))
}
pub const PROMISES_METHODS: &[&str] = &["setTimeout", "setImmediate", "setInterval"];
pub const INTERVAL_METHODS: &[&str] = &["next", "return", "@@asyncIterator"];
pub fn promises_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
match method {
"setTimeout" => {
let delay = arg_num(args, 0);
let value = args.get(1).cloned().unwrap_or(Value::Undef);
Some(Ok(schedule_promise("setTimeout", Some(delay), value)))
}
"setImmediate" => {
let value = args.first().cloned().unwrap_or(Value::Undef);
Some(Ok(schedule_promise("setImmediate", None, value)))
}
"setInterval" => {
let delay = arg_num(args, 0);
let value = args.get(1).cloned().unwrap_or(Value::Undef);
Some(Ok(interval_iterator(delay, value)))
}
_ => None,
}
}
fn interval_iterator(delay: f64, value: Value) -> Value {
with_host(|h| {
let mut m = IndexMap::new();
m.insert("@@native".into(), h.new_str("IntervalIterator"));
m.insert("@@delay".into(), Value::Float(delay));
m.insert("@@value".into(), value);
m.insert("@@stopped".into(), Value::Bool(false));
h.new_object(m)
})
}
pub fn interval_call(recv: &Value, method: &str, _args: &[Value]) -> Result<Value, String> {
let slot = |k: &str| {
with_host(|h| match h.get(recv) {
Some(JsObj::Object(p)) => p.get(k).cloned(),
_ => None,
})
};
match method {
"@@asyncIterator" => Ok(recv.clone()),
"next" => {
let stopped = slot("@@stopped").is_some_and(|v| with_host(|h| h.truthy(&v)));
let value = slot("@@value").unwrap_or(Value::Undef);
if stopped {
let done = iter_result(Value::Undef, true);
return Ok(resolved_promise(done));
}
let delay = slot("@@delay")
.map(|v| with_host(|h| h.to_number(&v)))
.unwrap_or(0.0);
let result = iter_result(value, false);
Ok(schedule_promise("setTimeout", Some(delay), result))
}
"return" => {
with_host(|h| {
if let Some(JsObj::Object(p)) = h.get_mut(recv) {
p.insert("@@stopped".into(), Value::Bool(true));
}
});
let done = iter_result(Value::Undef, true);
Ok(resolved_promise(done))
}
_ => Err(crate::host::type_error(&format!(
"intervalIterator.{method} is not a function"
))),
}
}
fn iter_result(value: Value, done: bool) -> Value {
with_host(|h| {
let mut m = IndexMap::new();
m.insert("value".into(), value);
m.insert("done".into(), Value::Bool(done));
h.new_object(m)
})
}
fn resolved_promise(v: Value) -> Value {
let (promise, id) = with_host(|h| {
let p = h.new_promise();
let id = h.promise_id(&p).unwrap_or(0);
(p, id)
});
crate::host::resolve_promise_val(id, v);
promise
}
fn schedule_promise(kind: &str, delay: Option<f64>, value: Value) -> Value {
let (promise, id) = with_host(|h| {
let p = h.new_promise();
let id = h.promise_id(&p).unwrap_or(0);
(p, id)
});
let resolver = with_host(|h| h.alloc(JsObj::Builtin(format!("@@presolve:{id}"))));
let timer_args = match delay {
Some(d) => vec![resolver, Value::Float(d), value],
None => vec![resolver, value],
};
let _ = crate::builtins::call_builtin_function(kind, timer_args);
promise
}