use crate::ir::ast::{Array, Node, Object, Value, ValueData};
use std::collections::HashMap;

pub fn bind(node: &Node, target: &mut dyn std::any::Any) -> Result<(), String> {
    match node {
        Node::Object(obj) => bind_object(obj, target),
        Node::Array(arr) => bind_array(arr, target),
        Node::Value(val) => bind_value(val, target),
    }
}

fn bind_object(obj: &Object, target: &mut dyn std::any::Any) -> Result<(), String> {
    if let Some(map) = target.downcast_mut::<HashMap<String, serde_json::Value>>() {
        for field in &obj.fields {
            let value = node_to_jsonvalue(&field.value)?;
            map.insert(field.key.clone(), value);
        }
        return Ok(());
    }

    Err("unsupported target type for object".to_string())
}

fn bind_array(arr: &Array, target: &mut dyn std::any::Any) -> Result<(), String> {
    if let Some(vec) = target.downcast_mut::<Vec<serde_json::Value>>() {
        for item in &arr.items {
            let value = node_to_jsonvalue(item)?;
            vec.push(value);
        }
        return Ok(());
    }

    Err("unsupported target type for array".to_string())
}

fn bind_value(val: &Value, target: &mut dyn std::any::Any) -> Result<(), String> {
    if let Some(s) = target.downcast_mut::<String>() {
        *s = val.text.clone();
        return Ok(());
    }

    if let Some(b) = target.downcast_mut::<bool>() {
        if let ValueData::Bool(v) = &val.data {
            *b = *v;
            return Ok(());
        }
    }

    if let Some(i) = target.downcast_mut::<i64>() {
        if let ValueData::Int(v) = &val.data {
            *i = *v;
            return Ok(());
        }
    }

    if let Some(f) = target.downcast_mut::<f64>() {
        if let ValueData::Float(v) = &val.data {
            *f = *v;
            return Ok(());
        }
    }

    Err("unsupported target type for value".to_string())
}

fn node_to_jsonvalue(node: &Node) -> Result<serde_json::Value, String> {
    match node {
        Node::Value(v) => value_to_jsonvalue(v),
        Node::Object(o) => {
            let mut map = serde_json::Map::new();
            for field in &o.fields {
                map.insert(field.key.clone(), node_to_jsonvalue(&field.value)?);
            }
            Ok(serde_json::Value::Object(map))
        }
        Node::Array(a) => {
            let mut vec = Vec::new();
            for item in &a.items {
                vec.push(node_to_jsonvalue(item)?);
            }
            Ok(serde_json::Value::Array(vec))
        }
    }
}

fn value_to_jsonvalue(val: &Value) -> Result<serde_json::Value, String> {
    match &val.data {
        ValueData::Bool(b) => Ok(serde_json::Value::Bool(*b)),
        ValueData::String(s) => Ok(serde_json::Value::String(s.clone())),
        ValueData::Int(i) => Ok(serde_json::Value::Number((*i).into())),
        ValueData::Uint(u) => Ok(serde_json::Value::Number((*u).into())),
        ValueData::Float(f) => Ok(serde_json::Number::from_f64(*f)
            .map(serde_json::Value::Number)
            .unwrap_or(serde_json::Value::Null)),
        ValueData::Bytes(b) => Ok(serde_json::Value::String(format!("{:?}", b))),
        ValueData::Null => Ok(serde_json::Value::Null),
    }
}