//! Conditional functions: if, case, coalesce

use std::any::TypeId;

use anyhow::bail;

use crate::func::def::{FunctionDef, MatchTestFailure, ParameterBindingProvider, Parameters};
use crate::func::utils::{bindings_must_have_same_type, must_be_same_type};
use crate::types::matcher::{AnyMatcher, ExactMatcher, TupleMatcher};
use crate::types::{Type, BOOLEAN};

/// If with 2 arguments: if(condition, then)
/// Returns then value if condition is true, NULL otherwise.
#[derive(Default, Clone)]
pub struct If2;

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

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("condition", ExactMatcher::of(BOOLEAN))
            .with("then", AnyMatcher::default())
    }

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

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

/// If with 3 arguments: if(condition, then, else)
/// Returns then value if condition is true, else value otherwise.
#[derive(Default, Clone)]
pub struct If3;

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

    fn parameters(&self) -> Parameters {
        Parameters::new()
            .with("condition", ExactMatcher::of(BOOLEAN))
            .with("then", AnyMatcher::default())
            .with("else", AnyMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        must_be_same_type(
            vec![
                bindings.get_by_index(1)?.typ(),
                bindings.get_by_index(2)?.typ(),
            ]
            .into_iter(),
        )
    }

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

/// Case expression: case((cond1, val1), (cond2, val2), ...)
/// Returns the value corresponding to the first true condition.
#[derive(Default, Clone)]
pub struct Case;

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

    fn parameters(&self) -> Parameters {
        Parameters::new().with_var_args(TupleMatcher::of(
            ExactMatcher::of(BOOLEAN),
            AnyMatcher::default(),
        ))
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        if bindings.len() == 0 {
            bail!(MatchTestFailure::msg("At least one argument is required"));
        }

        must_be_same_type(bindings.iter().map(|b| match b.typ() {
            Type::Tuple(t) => &t.elements[1],
            _ => unreachable!(),
        }))
    }

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

/// Coalesce: coalesce(val1, val2, ...)
/// Returns the first non-null value.
#[derive(Default, Clone)]
pub struct Coalesce;

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

    fn parameters(&self) -> Parameters {
        Parameters::new().with_var_args(AnyMatcher::default())
    }

    fn return_type(&self, bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
        bindings_must_have_same_type(bindings)
    }

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