//! Array operators and functions

use std::any::TypeId;

use anyhow::{anyhow, bail};

use crate::func::def::{FunctionDef, MatchTestFailure, ParameterBindingProvider, Parameters};
use crate::operator::Operator;
use crate::types::array::Array;
use crate::types::matcher::{ArrayMatcher, ExactMatcher, MapMatcher, NumericMatcher, OrMatcher};
use crate::types::{Type, BOOLEAN, INT, STRING, TIMESTAMP};

/// Array concatenation operator: `left + right` for arrays
#[derive(Default, Clone)]
pub struct ArrayConcat;

impl FunctionDef for ArrayConcat {
    fn name(&self) -> &'static str {
        Operator::Plus.str()
    }

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("left", ArrayMatcher::default())
            .with("right", ArrayMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        let left = bindings.get_by_index(0)?.typ().clone();
        let right = bindings.get_by_index(1)?.typ().clone();
        left.merge(right)
            .map_err(|e| anyhow!(MatchTestFailure::wrap(e)))
    }

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

/// filter_null(array) - Filters null elements from an array.
#[derive(Default, Clone)]
pub struct FilterNull;

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

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

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(bindings.get_by_index(0)?.typ().clone())
    }

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

/// len(array) or len(map) - Returns the length/cardinality.
#[derive(Default, Clone)]
pub struct ArrayOrMapLen;

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

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

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

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

/// array_distinct(array) - Returns distinct elements.
#[derive(Default, Clone)]
pub struct ArrayDistinct;

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

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

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(bindings.get_by_index(0)?.typ().clone())
    }

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

/// slice(array, start, end) - Returns a slice of the array.
#[derive(Default, Clone)]
pub struct Slice;

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

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

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        Ok(bindings.get_by_index(0)?.typ().clone())
    }

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

/// split(string, delimiter) - Splits a string into an array.
#[derive(Default, Clone)]
pub struct Split;

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

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

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

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

/// array_join(array, delimiter) - Joins array elements into a string.
#[derive(Default, Clone)]
pub struct ArrayJoin2;

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

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

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

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

/// array_join(array, delimiter, null_replacement) - Joins array elements with null replacement.
#[derive(Default, Clone)]
pub struct ArrayJoin3;

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

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

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

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

/// flatten(array<array<T>>) - Flattens nested arrays.
#[derive(Default, Clone)]
pub struct Flatten;

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

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

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        let array_of_arrays = match bindings.get_by_index(0)?.typ() {
            Type::Array(outer_array) => match &*outer_array.element_type {
                Type::Array(inner_array) => inner_array.element_type.clone(),
                _ => bail!("flatten requires an array of arrays"),
            },
            _ => bail!("flatten requires an array of arrays"),
        };
        Ok(Array::new(*array_of_arrays).into())
    }

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

/// any(array<boolean>) - Returns true if any element is true.
#[derive(Default, Clone)]
pub struct ArrayAny;

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

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", ArrayMatcher::of(ExactMatcher::of(BOOLEAN)))
    }

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

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

/// all(array<boolean>) - Returns true if all elements are true.
#[derive(Default, Clone)]
pub struct ArrayAll;

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

    fn parameters(&self) -> Parameters {
        Parameters::new().with("x", ArrayMatcher::of(ExactMatcher::of(BOOLEAN)))
    }

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

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

/// max(array) - Returns the maximum element.
#[derive(Default, Clone)]
pub struct ArrayMax;

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

    fn parameters(&self) -> Parameters {
        Parameters::new().with(
            "x",
            ArrayMatcher::of(
                OrMatcher::default()
                    .with(NumericMatcher::default())
                    .with(ExactMatcher::of(STRING))
                    .with(ExactMatcher::of(TIMESTAMP)),
            ),
        )
    }

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

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

/// min(array) - Returns the minimum element.
#[derive(Default, Clone)]
pub struct ArrayMin;

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

    fn parameters(&self) -> Parameters {
        Parameters::new().with(
            "x",
            ArrayMatcher::of(
                OrMatcher::default()
                    .with(NumericMatcher::default())
                    .with(ExactMatcher::of(STRING))
                    .with(ExactMatcher::of(TIMESTAMP)),
            ),
        )
    }

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

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

/// sum(array<numeric>) - Returns the sum of elements.
#[derive(Default, Clone)]
pub struct ArraySum;

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

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

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

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

/// avg(array<numeric>) - Returns the average of elements.
#[derive(Default, Clone)]
pub struct ArrayAvg;

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

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

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

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