// Copyright (C) 2026 Ordinary Labs, LLC.
//
// SPDX-License-Identifier: AGPL-3.0-only

use ref_cast::RefCast;
use serde_json::{Map, Value as Json};
use valuable::{Listable, Mappable, Valuable, Value, Visit};

#[derive(RefCast)]
#[repr(transparent)]
pub struct JsonValuable(pub Json);

impl JsonValuable {
    fn new(json: &Json) -> &JsonValuable {
        JsonValuable::ref_cast(json)
    }
}

#[cfg(tracing_unstable)]
impl Valuable for JsonValuable {
    fn as_value(&self) -> Value<'_> {
        match self.0 {
            Json::Array(ref array) => JsonValuableArray::new(array).as_value(),
            Json::Bool(ref value) => value.as_value(),
            Json::Number(ref num) => {
                if num.is_f64()
                    && let Some(num) = num.as_f64()
                {
                    Value::F64(num)
                } else if num.is_i64()
                    && let Some(num) = num.as_i64()
                {
                    Value::I64(num)
                } else {
                    unreachable!()
                }
            }
            Json::Null => Value::Unit,
            Json::String(ref s) => s.as_value(),
            Json::Object(ref object) => JsonValuableMap::new(object).as_value(),
        }
    }

    fn visit(&self, visit: &mut dyn Visit) {
        match self.0 {
            Json::Array(ref array) => JsonValuableArray::new(array).visit(visit),
            Json::Bool(ref value) => value.visit(visit),
            Json::Number(ref num) => {
                if num.is_f64()
                    && let Some(num) = num.as_f64()
                {
                    num.visit(visit);
                } else if num.is_i64()
                    && let Some(num) = num.as_i64()
                {
                    num.visit(visit);
                } else {
                    unreachable!()
                }
            }
            Json::Null => Value::Unit.visit(visit),
            Json::String(ref s) => s.visit(visit),
            Json::Object(ref object) => JsonValuableMap::new(object).visit(visit),
        }
    }
}

#[derive(RefCast)]
#[repr(transparent)]
pub struct JsonValuableMap(pub Map<String, Json>);

impl JsonValuableMap {
    fn new(map: &Map<String, Json>) -> &JsonValuableMap {
        JsonValuableMap::ref_cast(map)
    }
}

#[cfg(tracing_unstable)]
impl Valuable for JsonValuableMap {
    fn as_value(&self) -> Value<'_> {
        Value::Mappable(self)
    }

    fn visit(&self, visit: &mut dyn Visit) {
        for (k, v) in &self.0 {
            visit.visit_entry(k.as_value(), JsonValuable::new(v).as_value());
        }
    }
}

#[cfg(tracing_unstable)]
impl Mappable for JsonValuableMap {
    fn size_hint(&self) -> (usize, Option<usize>) {
        let len = self.0.len();
        (len, Some(len))
    }
}

#[derive(RefCast)]
#[repr(transparent)]
pub struct JsonValuableArray(pub Vec<Json>);

impl JsonValuableArray {
    fn new(arr: &Vec<Json>) -> &JsonValuableArray {
        JsonValuableArray::ref_cast(arr)
    }
}

#[cfg(tracing_unstable)]
impl Valuable for JsonValuableArray {
    fn as_value(&self) -> Value<'_> {
        Value::Listable(self)
    }

    fn visit(&self, visit: &mut dyn Visit) {
        for v in &self.0 {
            visit.visit_value(JsonValuable::new(v).as_value());
        }
    }
}

#[cfg(tracing_unstable)]
impl Listable for JsonValuableArray {
    fn size_hint(&self) -> (usize, Option<usize>) {
        let len = self.0.len();
        (len, Some(len))
    }
}
