//! Match aggregate functions (used in MATCH AGG clause)
//!
//! These aggregate over matched rows in pattern matching.
//! Only functions that can be implemented as array operations are supported
//! by the CTE-based MATCH strategy.

use std::any::TypeId;

use crate::func::def::{FunctionDef, ParameterBindingProvider, Parameters, SpecialPosition};
use crate::types::array::Array;
use crate::types::matcher::{AnyMatcher, ExactMatcher, NumericMatcher, OrMatcher};
use crate::types::{Type, DOUBLE, INT, STRING, TIMESTAMP};

/// first(expression) - Returns the first value in a match.
#[derive(Default, Clone)]
pub struct MatchFirst;

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

    fn parameters(&self) -> Parameters {
        Parameters::new().with("expression", AnyMatcher::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>()
    }

    fn special_position(&self) -> Option<SpecialPosition> {
        Some(SpecialPosition::Match)
    }
}

/// last(expression) - Returns the last value in a match.
#[derive(Default, Clone)]
pub struct MatchLast;

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

    fn parameters(&self) -> Parameters {
        Parameters::new().with("expression", AnyMatcher::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>()
    }

    fn special_position(&self) -> Option<SpecialPosition> {
        Some(SpecialPosition::Match)
    }
}

/// count() - Count all rows in a match (COUNT(*)).
#[derive(Default, Clone)]
pub struct MatchCountStar;

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

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

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

    fn special_position(&self) -> Option<SpecialPosition> {
        Some(SpecialPosition::Match)
    }
}

/// count(x) - Count non-null values in a match.
#[derive(Default, Clone)]
pub struct MatchCountAny;

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

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

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

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

    fn special_position(&self) -> Option<SpecialPosition> {
        Some(SpecialPosition::Match)
    }
}

/// sum(x) - Sum numeric values in a match.
#[derive(Default, Clone)]
pub struct MatchSum;

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

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

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

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

    fn special_position(&self) -> Option<SpecialPosition> {
        Some(SpecialPosition::Match)
    }
}

/// avg(x) - Average numeric values in a match.
#[derive(Default, Clone)]
pub struct MatchAvg;

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

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

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

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

    fn special_position(&self) -> Option<SpecialPosition> {
        Some(SpecialPosition::Match)
    }
}

/// min(x) - Minimum value in a match (numeric, string, or timestamp).
#[derive(Default, Clone)]
pub struct MatchMin;

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

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

    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>()
    }

    fn special_position(&self) -> Option<SpecialPosition> {
        Some(SpecialPosition::Match)
    }
}

/// max(x) - Maximum value in a match (numeric, string, or timestamp).
#[derive(Default, Clone)]
pub struct MatchMax;

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

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

    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>()
    }

    fn special_position(&self) -> Option<SpecialPosition> {
        Some(SpecialPosition::Match)
    }
}

/// array_agg(x) - Collect values from matched rows into an array.
#[derive(Default, Clone)]
pub struct MatchArrayAgg;

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

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

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

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

    fn special_position(&self) -> Option<SpecialPosition> {
        Some(SpecialPosition::Match)
    }
}

/// count_distinct(x) - Count distinct values from matched rows.
#[derive(Default, Clone)]
pub struct MatchCountDistinct;

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

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

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

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

    fn special_position(&self) -> Option<SpecialPosition> {
        Some(SpecialPosition::Match)
    }
}
