//! Array and map access/indexing functions

use std::any::TypeId;

use anyhow::bail;

use crate::func::def::{FunctionDef, ParameterBindingProvider, Parameters};
use crate::types::matcher::{AnyMatcher, ArrayMatcher, ExactMatcher, MapMatcher};
use crate::types::{Type, INT};

/// get(array, index) - Returns the element at the given index.
#[derive(Default, Clone)]
pub struct GetArray;

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

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("array", ArrayMatcher::default())
            .with("index", ExactMatcher::of(INT))
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        match bindings.get_by_name("array")?.typ() {
            Type::Array(arr) => Ok((*arr.element_type).clone()),
            _ => bail!("array parameter must be an array"),
        }
    }

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

/// get(map, key) - Returns the value for the given key.
#[derive(Default, Clone)]
pub struct GetMap;

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

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

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        match bindings.get_by_name("map")?.typ() {
            Type::Map(map) => Ok((*map.value_type).clone()),
            _ => bail!("map parameter must be a map"),
        }
    }

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