//! JSON conversion helpers for runtime Rex values.
//!
//! These functions are useful when a host chooses a Rex type at runtime and
//! needs to cross the embedding boundary through JSON [`Value`](serde_json::Value)
//! rather than through Rust generics and [`IntoRex`](crate::engine::IntoRex) /
//! [`FromRex`](crate::engine::FromRex).

use crate::engine::{EngineError, Handle, Heap};
use rex_ast::Symbol;
use rex_typesystem::{
    types::{AdtDecl, BuiltinTypeId, Type, TypeKind},
    typesystem::TypeSystem,
};
use serde_json::{Map, Number, Value};
use std::collections::BTreeMap;

fn local_name(name: &Symbol) -> &str {
    name.as_ref().rsplit('.').next().unwrap_or(name.as_ref())
}

fn local_name_matches(name: &Symbol, expected: &str) -> bool {
    local_name(name) == expected
}

fn runtime_ctor(name: &Symbol) -> Symbol {
    Symbol::intern(local_name(name))
}

/// Convert a JSON [`Value`] into a typed Rex runtime value ([`Handle`]) allocated on `heap`.
///
/// The conversion is compatible with serde-style JSON representations for Rex-compatible Rust
/// types, but operates entirely via runtime Rex types (`Type`) plus the evaluator heap.
///
/// This is intended for cases where Rust compile-time types are not available and only runtime
/// type information exists.
///
/// Important behavior:
/// - JSON arrays targeting `Array a` become Rex runtime arrays.
/// - JSON arrays targeting tuple types become tuples.
/// - JSON arrays targeting `List a` become list ADTs (`Cons`/`Empty`).
pub fn json_to_rex(
    heap: &Heap,
    json: &Value,
    want: &Type,
    ts: &TypeSystem,
) -> Result<Handle, EngineError> {
    match want.as_ref() {
        TypeKind::Var(tv) => Err(error(format!(
            "cannot decode JSON into unresolved type variable t{}",
            tv.id
        ))),
        TypeKind::Con(con) => json_to_handle_for_con(heap, json, &con.name(), &[], ts),
        TypeKind::App(_, _) => {
            let (head, args) = decompose_type_app(want);
            if let TypeKind::Con(con) = head.as_ref() {
                json_to_handle_for_con(heap, json, &con.name(), &args, ts)
            } else {
                Err(error(format!("unsupported applied type {}", want)))
            }
        }
        TypeKind::Fun(_, _) => Err(error("cannot decode JSON into function type".to_string())),
        TypeKind::Tuple(items) => match json {
            Value::Array(values) if values.len() == items.len() => {
                let mut out = Vec::with_capacity(values.len());
                for (value, item_ty) in values.iter().zip(items.iter()) {
                    out.push(json_to_rex(heap, value, item_ty, ts)?);
                }
                heap.alloc_tuple(out)
            }
            _ => Err(type_mismatch_json(json, want)),
        },
        TypeKind::Record(fields) => match json {
            Value::Object(entries) => {
                let mut out = BTreeMap::new();
                for (k, t) in fields {
                    let j = entries.get(k.as_ref()).unwrap_or(&Value::Null);
                    out.insert(k.clone(), json_to_rex(heap, j, t, ts)?);
                }
                heap.alloc_dict(out)
            }
            _ => Err(type_mismatch_json(json, want)),
        },
    }
}

/// Convert a typed Rex runtime value ([`Handle`]) to a JSON [`Value`].
///
/// The conversion is compatible with serde-style JSON representations for Rex-compatible Rust
/// types, but operates on runtime values stored in the evaluator heap.
pub fn rex_to_json(handle: &Handle, want: &Type, ts: &TypeSystem) -> Result<Value, EngineError> {
    match want.as_ref() {
        TypeKind::Var(tv) => Err(error(format!(
            "cannot encode unresolved type variable t{} to JSON",
            tv.id
        ))),
        TypeKind::Con(con) => handle_to_json_for_con(handle, &con.name(), &[], ts),
        TypeKind::App(_, _) => {
            let (head, args) = decompose_type_app(want);
            if let TypeKind::Con(con) = head.as_ref() {
                handle_to_json_for_con(handle, &con.name(), &args, ts)
            } else {
                Err(error(format!("unsupported applied type {}", want)))
            }
        }
        TypeKind::Fun(_, _) => Err(error("cannot encode function value to JSON".to_string())),
        TypeKind::Tuple(item_types) => {
            let values = handle.as_tuple()?;
            if values.len() != item_types.len() {
                return Err(type_mismatch_handle(handle, want));
            }
            let mut out = Vec::with_capacity(values.len());
            for (p, t) in values.iter().zip(item_types.iter()) {
                out.push(rex_to_json(p, t, ts)?);
            }
            Ok(Value::Array(out))
        }
        TypeKind::Record(fields) => {
            let entries = handle.as_dict()?;
            if entries.len() != fields.len() {
                return Err(type_mismatch_handle(handle, want));
            }
            let mut out = Map::new();
            for (k, t) in fields {
                let p = entries
                    .get(k)
                    .ok_or_else(|| type_mismatch_handle(handle, want))?;
                out.insert(k.to_string(), rex_to_json(p, t, ts)?);
            }
            Ok(Value::Object(out))
        }
    }
}

fn json_to_handle_for_con(
    heap: &Heap,
    json: &Value,
    con_name: &Symbol,
    con_args: &[Type],
    ts: &TypeSystem,
) -> Result<Handle, EngineError> {
    match (con_name.as_ref(), con_args) {
        ("bool", []) => match json {
            Value::Bool(v) => heap.alloc_bool(*v),
            _ => Err(type_mismatch_json(
                json,
                &Type::builtin(BuiltinTypeId::Bool),
            )),
        },

        ("u8", []) => {
            let v = json_u64(json)?;
            u8::try_from(v)
                .map_err(|_| error(format!("value {} out of range for u8", v)))
                .and_then(|x| heap.alloc_u8(x))
        }
        ("u16", []) => {
            let v = json_u64(json)?;
            u16::try_from(v)
                .map_err(|_| error(format!("value {} out of range for u16", v)))
                .and_then(|x| heap.alloc_u16(x))
        }
        ("u32", []) => {
            let v = json_u64(json)?;
            u32::try_from(v)
                .map_err(|_| error(format!("value {} out of range for u32", v)))
                .and_then(|x| heap.alloc_u32(x))
        }
        ("u64", []) => heap.alloc_u64(json_u64(json)?),

        ("i8", []) => {
            let v = json_i64(json)?;
            i8::try_from(v)
                .map_err(|_| error(format!("value {} out of range for i8", v)))
                .and_then(|x| heap.alloc_i8(x))
        }
        ("i16", []) => {
            let v = json_i64(json)?;
            i16::try_from(v)
                .map_err(|_| error(format!("value {} out of range for i16", v)))
                .and_then(|x| heap.alloc_i16(x))
        }
        ("i32", []) => {
            let v = json_i64(json)?;
            i32::try_from(v)
                .map_err(|_| error(format!("value {} out of range for i32", v)))
                .and_then(|x| heap.alloc_i32(x))
        }
        ("i64", []) => heap.alloc_i64(json_i64(json)?),

        ("f32", []) => heap.alloc_f32(json_f64(json)? as f32),
        ("f64", []) => heap.alloc_f64(json_f64(json)?),

        ("string", []) => match json {
            Value::String(s) => heap.alloc_string(s.clone()),
            _ => Err(type_mismatch_json(
                json,
                &Type::builtin(BuiltinTypeId::String),
            )),
        },
        ("uuid", []) => {
            let u = serde_json::from_value(json.clone())
                .map_err(|e| error(format!("invalid uuid JSON: {e}")))?;
            heap.alloc_uuid(u)
        }
        ("datetime", []) => {
            let dt = serde_json::from_value(json.clone())
                .map_err(|e| error(format!("invalid datetime JSON: {e}")))?;
            heap.alloc_datetime(dt)
        }

        ("Option", [inner]) => match json {
            Value::Null => heap.alloc_adt(Symbol::intern("None"), vec![]),
            _ => {
                let inner_handle = json_to_rex(heap, json, inner, ts)?;
                heap.alloc_adt(Symbol::intern("Some"), vec![inner_handle])
            }
        },

        ("Promise", [_inner]) => {
            let promise_id = json_to_rex(heap, json, &Type::builtin(BuiltinTypeId::Uuid), ts)?;
            heap.alloc_adt(Symbol::intern("Promise"), vec![promise_id])
        }

        // Internal argument order is Result err ok.
        ("Result", [err_t, ok_t]) => match json {
            Value::Object(obj) if obj.len() == 1 => {
                if let Some(v) = obj.get("Ok") {
                    let p = json_to_rex(heap, v, ok_t, ts)?;
                    heap.alloc_adt(Symbol::intern("Ok"), vec![p])
                } else if let Some(v) = obj.get("Err") {
                    let p = json_to_rex(heap, v, err_t, ts)?;
                    heap.alloc_adt(Symbol::intern("Err"), vec![p])
                } else {
                    Err(error(format!(
                        "expected {{Ok:..}} or {{Err:..}}, got {}",
                        json
                    )))
                }
            }
            _ => Err(error(format!("expected result object JSON, got {}", json))),
        },

        // IMPORTANT: JSON arrays map to runtime arrays, not lists.
        ("Array", [elem_t]) => match json {
            Value::Array(items) => {
                let mut out = Vec::with_capacity(items.len());
                for item in items {
                    out.push(json_to_rex(heap, item, elem_t, ts)?);
                }
                heap.alloc_array(out)
            }
            _ => Err(error(format!(
                "expected array JSON for Array, got {}",
                json
            ))),
        },

        ("List", [elem_t]) => match json {
            Value::Array(items) => {
                let mut out = Vec::with_capacity(items.len());
                for item in items {
                    out.push(json_to_rex(heap, item, elem_t, ts)?);
                }
                let mut list = heap.alloc_adt(Symbol::intern("Empty"), vec![])?;
                for p in out.into_iter().rev() {
                    list = heap.alloc_adt(Symbol::intern("Cons"), vec![p, list])?;
                }
                Ok(list)
            }
            _ => Err(error(format!("expected array JSON for List, got {}", json))),
        },

        ("Dict", [elem_t]) => match json {
            Value::Object(obj) => {
                let mut out = BTreeMap::new();
                for (k, v) in obj {
                    out.insert(Symbol::intern(k), json_to_rex(heap, v, elem_t, ts)?);
                }
                heap.alloc_dict(out)
            }
            _ => Err(error(format!(
                "expected object JSON for Dict, got {}",
                json
            ))),
        },

        _ => json_to_handle_for_adt(heap, json, con_name, con_args, ts),
    }
}

fn handle_to_json_for_con(
    handle: &Handle,
    con_name: &Symbol,
    con_args: &[Type],
    ts: &TypeSystem,
) -> Result<Value, EngineError> {
    match (con_name.as_ref(), con_args) {
        ("bool", []) => Ok(Value::Bool(handle.as_bool()?)),
        ("u8", []) => Ok(Value::Number(u64::from(handle.as_u8()?).into())),
        ("u16", []) => Ok(Value::Number(u64::from(handle.as_u16()?).into())),
        ("u32", []) => Ok(Value::Number(u64::from(handle.as_u32()?).into())),
        ("u64", []) => Ok(Value::Number(handle.as_u64()?.into())),
        ("i8", []) => Ok(Value::Number(i64::from(handle.as_i8()?).into())),
        ("i16", []) => Ok(Value::Number(i64::from(handle.as_i16()?).into())),
        ("i32", []) => Ok(Value::Number(i64::from(handle.as_i32()?).into())),
        ("i64", []) => Ok(Value::Number(handle.as_i64()?.into())),
        ("f32", []) => Number::from_f64(f64::from(handle.as_f32()?))
            .map(Value::Number)
            .ok_or_else(|| error("invalid f32 value for JSON".to_string())),
        ("f64", []) => Number::from_f64(handle.as_f64()?)
            .map(Value::Number)
            .ok_or_else(|| error("invalid f64 value for JSON".to_string())),
        ("string", []) => Ok(Value::String(handle.as_string()?)),
        ("uuid", []) => serde_json::to_value(handle.as_uuid()?)
            .map_err(|e| error(format!("failed to serialize uuid: {e}"))),
        ("datetime", []) => serde_json::to_value(handle.as_datetime()?)
            .map_err(|e| error(format!("failed to serialize datetime: {e}"))),

        ("Option", [inner_t]) => {
            let (tag, args) = handle.as_adt()?;
            match (tag.as_ref(), args.as_slice()) {
                ("None", []) => Ok(Value::Null),
                ("Some", [x]) => rex_to_json(x, inner_t, ts),
                _ => Err(type_mismatch_handle(
                    handle,
                    &Type::app(Type::builtin(BuiltinTypeId::Option), inner_t.clone()),
                )),
            }
        }

        ("Promise", [inner_t]) => {
            let (tag, args) = handle.as_adt()?;
            match (tag.as_ref(), args.as_slice()) {
                ("Promise", [promise_id]) => {
                    rex_to_json(promise_id, &Type::builtin(BuiltinTypeId::Uuid), ts)
                }
                _ => Err(type_mismatch_handle(
                    handle,
                    &Type::app(Type::builtin(BuiltinTypeId::Promise), inner_t.clone()),
                )),
            }
        }

        // Internal argument order is Result err ok.
        ("Result", [err_t, ok_t]) => {
            let (tag, args) = handle.as_adt()?;
            match (tag.as_ref(), args.as_slice()) {
                ("Ok", [x]) => {
                    let mut out = Map::new();
                    out.insert("Ok".to_string(), rex_to_json(x, ok_t, ts)?);
                    Ok(Value::Object(out))
                }
                ("Err", [x]) => {
                    let mut out = Map::new();
                    out.insert("Err".to_string(), rex_to_json(x, err_t, ts)?);
                    Ok(Value::Object(out))
                }
                _ => Err(type_mismatch_handle(
                    handle,
                    &Type::app(
                        Type::app(Type::builtin(BuiltinTypeId::Result), err_t.clone()),
                        ok_t.clone(),
                    ),
                )),
            }
        }

        ("Array", [elem_t]) => {
            let items = handle.as_array()?;
            let mut out = Vec::with_capacity(items.len());
            for item in &items {
                out.push(rex_to_json(item, elem_t, ts)?);
            }
            Ok(Value::Array(out))
        }

        ("List", [elem_t]) => {
            let items = list_to_vec(handle)?;
            let mut out = Vec::with_capacity(items.len());
            for item in &items {
                out.push(rex_to_json(item, elem_t, ts)?);
            }
            Ok(Value::Array(out))
        }

        ("Dict", [elem_t]) => {
            let entries = handle.as_dict()?;
            let mut out = Map::new();
            for (k, v) in &entries {
                out.insert(k.to_string(), rex_to_json(v, elem_t, ts)?);
            }
            Ok(Value::Object(out))
        }

        _ => handle_to_json_for_adt(handle, con_name, con_args, ts),
    }
}

fn json_to_handle_for_adt(
    heap: &Heap,
    json: &Value,
    adt_name: &Symbol,
    type_args: &[Type],
    ts: &TypeSystem,
) -> Result<Handle, EngineError> {
    let adt = ts
        .adts
        .get(adt_name)
        .ok_or_else(|| error(format!("unknown ADT `{}`", adt_name)))?;
    let subst = adt_subst(adt, type_args)?;

    if adt.variants.len() == 1 {
        let v = &adt.variants[0];
        let arg_types = instantiate_types(&v.args, &subst);
        return decode_direct_variant(heap, json, &v.name, &arg_types, ts);
    }

    let enum_name = adt.name.to_string();
    let enum_like = adt.variants.iter().all(|v| v.args.is_empty());
    if enum_like {
        if let Value::String(tag) = json {
            if let Some(v) = adt
                .variants
                .iter()
                .find(|v| local_name_matches(&v.name, tag))
            {
                return heap.alloc_adt(runtime_ctor(&v.name), vec![]);
            }
            return Err(error(format!(
                "unknown enum tag `{}` for `{}`",
                tag, enum_name
            )));
        }
        return Err(error(format!(
            "expected enum string JSON for `{}`, got {}",
            enum_name, json
        )));
    }

    if let Value::String(tag) = json
        && let Some(v) = adt
            .variants
            .iter()
            .find(|v| v.args.is_empty() && local_name_matches(&v.name, tag))
    {
        return heap.alloc_adt(runtime_ctor(&v.name), vec![]);
    }

    if let Value::Object(obj) = json
        && obj.len() == 1
    {
        let Some((tag, payload)) = obj.iter().next() else {
            return Err(error(format!(
                "expected ADT JSON representation for `{}`; got {}",
                adt_name, json
            )));
        };
        if let Some(v) = adt
            .variants
            .iter()
            .find(|v| local_name_matches(&v.name, tag))
        {
            let arg_types = instantiate_types(&v.args, &subst);
            return decode_wrapped_variant(heap, payload, &v.name, &arg_types, ts);
        }
    }

    Err(error(format!(
        "expected ADT JSON representation for `{}`; got {}",
        adt_name, json
    )))
}

fn handle_to_json_for_adt(
    handle: &Handle,
    adt_name: &Symbol,
    type_args: &[Type],
    ts: &TypeSystem,
) -> Result<Value, EngineError> {
    let adt = ts
        .adts
        .get(adt_name)
        .ok_or_else(|| error(format!("unknown ADT `{}`", adt_name)))?;
    let subst = adt_subst(adt, type_args)?;

    let (tag, args) = handle.as_adt()?;
    let v = adt
        .variants
        .iter()
        .find(|v| local_name_matches(&v.name, local_name(&tag)))
        .ok_or_else(|| {
            error(format!(
                "constructor `{}` is not in ADT `{}`",
                tag, adt_name
            ))
        })?;
    let arg_types = instantiate_types(&v.args, &subst);
    if args.len() != arg_types.len() {
        return Err(error(format!(
            "constructor `{}` expected {} args, got {}",
            tag,
            arg_types.len(),
            args.len()
        )));
    }

    if adt.variants.len() == 1 {
        return encode_direct_variant(&tag, &args, &arg_types, ts);
    }

    let enum_like = adt.variants.iter().all(|v| v.args.is_empty());
    if enum_like && args.is_empty() {
        return Ok(Value::String(local_name(&tag).to_string()));
    }

    if args.is_empty() {
        return Ok(Value::String(local_name(&tag).to_string()));
    }

    let payload = encode_wrapped_variant(&args, &arg_types, ts)?;
    let mut out = Map::new();
    out.insert(local_name(&tag).to_string(), payload);
    Ok(Value::Object(out))
}

fn decode_direct_variant(
    heap: &Heap,
    json: &Value,
    ctor: &Symbol,
    arg_types: &[Type],
    ts: &TypeSystem,
) -> Result<Handle, EngineError> {
    match arg_types {
        [] => match json {
            Value::Null => heap.alloc_adt(runtime_ctor(ctor), vec![]),
            Value::String(tag) if tag == local_name(ctor) => {
                heap.alloc_adt(runtime_ctor(ctor), vec![])
            }
            _ => Err(error(format!(
                "expected null or `{}` for unit constructor, got {}",
                local_name(ctor),
                json
            ))),
        },
        [t0] => {
            let p = json_to_rex(heap, json, t0, ts)?;
            heap.alloc_adt(runtime_ctor(ctor), vec![p])
        }
        _ => match json {
            Value::Array(items) if items.len() == arg_types.len() => {
                let mut args = Vec::with_capacity(items.len());
                for (item, t) in items.iter().zip(arg_types.iter()) {
                    args.push(json_to_rex(heap, item, t, ts)?);
                }
                heap.alloc_adt(runtime_ctor(ctor), args)
            }
            _ => Err(error(format!(
                "expected array payload for constructor `{}`, got {}",
                local_name(ctor),
                json
            ))),
        },
    }
}

fn decode_wrapped_variant(
    heap: &Heap,
    payload: &Value,
    ctor: &Symbol,
    arg_types: &[Type],
    ts: &TypeSystem,
) -> Result<Handle, EngineError> {
    match arg_types {
        [] => heap.alloc_adt(runtime_ctor(ctor), vec![]),
        [t0] => {
            let p = json_to_rex(heap, payload, t0, ts)?;
            heap.alloc_adt(runtime_ctor(ctor), vec![p])
        }
        _ => match payload {
            Value::Array(items) if items.len() == arg_types.len() => {
                let mut args = Vec::with_capacity(items.len());
                for (item, t) in items.iter().zip(arg_types.iter()) {
                    args.push(json_to_rex(heap, item, t, ts)?);
                }
                heap.alloc_adt(runtime_ctor(ctor), args)
            }
            _ => Err(error(format!(
                "expected array payload for constructor `{}`, got {}",
                local_name(ctor),
                payload
            ))),
        },
    }
}

fn encode_direct_variant(
    ctor: &Symbol,
    args: &[Handle],
    arg_types: &[Type],
    ts: &TypeSystem,
) -> Result<Value, EngineError> {
    match arg_types {
        [] => Ok(Value::String(local_name(ctor).to_string())),
        [t0] => rex_to_json(&args[0], t0, ts),
        _ => {
            let mut out = Vec::with_capacity(args.len());
            for (arg, t) in args.iter().zip(arg_types.iter()) {
                out.push(rex_to_json(arg, t, ts)?);
            }
            Ok(Value::Array(out))
        }
    }
}

fn encode_wrapped_variant(
    args: &[Handle],
    arg_types: &[Type],
    ts: &TypeSystem,
) -> Result<Value, EngineError> {
    match arg_types {
        [] => Ok(Value::Null),
        [t0] => rex_to_json(&args[0], t0, ts),
        _ => {
            let mut out = Vec::with_capacity(args.len());
            for (arg, t) in args.iter().zip(arg_types.iter()) {
                out.push(rex_to_json(arg, t, ts)?);
            }
            Ok(Value::Array(out))
        }
    }
}

fn decompose_type_app(typ: &Type) -> (Type, Vec<Type>) {
    let mut args = Vec::new();
    let mut cur = typ.clone();
    while let TypeKind::App(f, a) = cur.as_ref() {
        args.push(a.clone());
        cur = f.clone();
    }
    args.reverse();
    (cur, args)
}

fn adt_subst(adt: &AdtDecl, type_args: &[Type]) -> Result<BTreeMap<usize, Type>, EngineError> {
    if adt.params.len() != type_args.len() {
        return Err(error(format!(
            "ADT `{}` expects {} type args, got {}",
            adt.name,
            adt.params.len(),
            type_args.len()
        )));
    }
    let mut subst = BTreeMap::new();
    for (param, arg) in adt.params.iter().zip(type_args.iter()) {
        subst.insert(param.var.id, arg.clone());
    }
    Ok(subst)
}

fn instantiate_types(ts: &[Type], subst: &BTreeMap<usize, Type>) -> Vec<Type> {
    ts.iter().map(|t| instantiate_type(t, subst)).collect()
}

fn instantiate_type(t: &Type, subst: &BTreeMap<usize, Type>) -> Type {
    match t.as_ref() {
        TypeKind::Var(tv) => subst.get(&tv.id).cloned().unwrap_or_else(|| t.clone()),
        TypeKind::Con(_) => t.clone(),
        TypeKind::App(f, a) => Type::app(instantiate_type(f, subst), instantiate_type(a, subst)),
        TypeKind::Fun(a, b) => Type::fun(instantiate_type(a, subst), instantiate_type(b, subst)),
        TypeKind::Tuple(xs) => Type::tuple(xs.iter().map(|x| instantiate_type(x, subst)).collect()),
        TypeKind::Record(fields) => Type::record(
            fields
                .iter()
                .map(|(k, v)| (k.clone(), instantiate_type(v, subst)))
                .collect(),
        ),
    }
}

fn list_to_vec(handle: &Handle) -> Result<Vec<Handle>, EngineError> {
    let mut out = Vec::new();
    let mut cur = handle.clone();
    loop {
        let (tag, args) = cur.as_adt()?;
        if tag.as_ref() == "Empty" && args.is_empty() {
            return Ok(out);
        }
        if tag.as_ref() == "Cons" && args.len() == 2 {
            out.push(args[0].clone());
            cur = args[1].clone();
            continue;
        }
        return Err(error(format!(
            "expected list ADT chain (Cons/Empty), found constructor `{}`",
            tag
        )));
    }
}

fn json_u64(json: &Value) -> Result<u64, EngineError> {
    match json {
        Value::Number(n) => n
            .as_u64()
            .ok_or_else(|| error(format!("expected unsigned integer JSON, got {}", json))),
        _ => Err(error(format!(
            "expected unsigned integer JSON, got {}",
            json
        ))),
    }
}

fn json_i64(json: &Value) -> Result<i64, EngineError> {
    match json {
        Value::Number(n) => n
            .as_i64()
            .ok_or_else(|| error(format!("expected signed integer JSON, got {}", json))),
        _ => Err(error(format!("expected signed integer JSON, got {}", json))),
    }
}

fn json_f64(json: &Value) -> Result<f64, EngineError> {
    match json {
        Value::Number(n) => n
            .as_f64()
            .ok_or_else(|| error(format!("expected floating-point JSON, got {}", json))),
        _ => Err(error(format!("expected floating-point JSON, got {}", json))),
    }
}

fn error(msg: String) -> EngineError {
    EngineError::Custom(msg)
}

fn type_mismatch_json(json: &Value, want: &Type) -> EngineError {
    error(format!(
        "JSON value {} does not match Rex type {}",
        json, want
    ))
}

fn type_mismatch_handle(handle: &Handle, want: &Type) -> EngineError {
    match handle.type_name() {
        Ok(got) => error(format!(
            "Rex value of runtime kind `{}` does not match Rex type {}",
            got, want
        )),
        Err(e) => e,
    }
}
