use std::error::Error;

use crate::format;
use crate::map::Map;
use crate::value::{Value, ValueKind};

#[derive(Debug)]
pub(crate) enum Val {
    Null,
    Boolean(bool),
    Integer(i64),
    Float(f64),
    String(String),
    Array(Vec<Self>),
    Object(Map<String, Self>),
}

impl<'de> serde_core::de::Deserialize<'de> for Val {
    fn deserialize<D>(d: D) -> Result<Self, D::Error>
    where
        D: serde_core::de::Deserializer<'de>,
    {
        serde_untagged::UntaggedEnumVisitor::new()
            .bool(|value| Ok(Self::Boolean(value)))
            .i64(|value| Ok(Self::Integer(value)))
            .f64(|value| Ok(Self::Float(value)))
            .string(|value| Ok(Val::String(value.to_owned())))
            .unit(|| Ok(Self::Null))
            .seq(|value| value.deserialize().map(Val::Array))
            .map(|value| value.deserialize().map(Val::Object))
            .deserialize(d)
    }
}

pub(crate) fn parse(
    uri: Option<&String>,
    text: &str,
) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
    let value = from_json5_value(uri, json5::from_str::<Val>(text)?);
    format::extract_root_table(uri, value)
}

fn from_json5_value(uri: Option<&String>, value: Val) -> Value {
    let vk = match value {
        Val::Null => ValueKind::Nil,
        Val::String(v) => ValueKind::String(v),
        Val::Integer(v) => ValueKind::I64(v),
        Val::Float(v) => ValueKind::Float(v),
        Val::Boolean(v) => ValueKind::Boolean(v),
        Val::Object(table) => {
            let m = table
                .into_iter()
                .map(|(k, v)| (k, from_json5_value(uri, v)))
                .collect();

            ValueKind::Table(m)
        }

        Val::Array(array) => {
            let l = array
                .into_iter()
                .map(|v| from_json5_value(uri, v))
                .collect();

            ValueKind::Array(l)
        }
    };

    Value::new(uri, vk)
}
