//! JSON functions

use std::any::TypeId;

use crate::func::def::{FunctionDef, ParameterBindingProvider, Parameters};
use crate::types::matcher::{AnyMatcher, ExactMatcher};
use crate::types::struct_type::Struct;
use crate::types::{Type, STRING, VARIANT};

/// parse_json(json_string) - Parse JSON string to variant.
#[derive(Default, Clone)]
pub struct ParseJsonString;

impl FunctionDef for ParseJsonString {
    fn name(&self) -> &'static str {
        "parse_json"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("json", ExactMatcher::of(STRING))
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(VARIANT)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// parse_json(variant) - Pass-through for already-parsed JSON.
#[derive(Default, Clone)]
pub struct ParseJsonVariant;

impl FunctionDef for ParseJsonVariant {
    fn name(&self) -> &'static str {
        "parse_json"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("json", ExactMatcher::of(VARIANT))
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(VARIANT)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// to_json_string(variant) - Convert variant to JSON string.
#[derive(Default, Clone)]
pub struct ToJsonString;

impl FunctionDef for ToJsonString {
    fn name(&self) -> &'static str {
        "to_json_string"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("json", ExactMatcher::of(VARIANT))
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(STRING)
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}

/// typeof(x) - Get the type of any expression.
#[derive(Default, Clone)]
pub struct Typeof;

impl FunctionDef for Typeof {
    fn name(&self) -> &'static str {
        "typeof"
    }

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", AnyMatcher::default())
    }

    fn return_type(&self, _bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(Struct::default()
            .with("hamelin_type".parse().unwrap(), STRING.into())
            .with("sql_type".parse().unwrap(), STRING.into())
            .into())
    }

    fn type_id(&self) -> TypeId {
        TypeId::of::<Self>()
    }
}
