// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

//! Per-field aggregators used by `merge-engine=aggregation`.
//!
//! Each [`FieldAggregator`] accumulates values for one output column across
//! the rows that share a primary key and produces a single-row [`ArrayRef`].
//! The accumulator is reused across PK groups by calling [`reset`] between
//! groups.
//!
//! Reference: Java `org.apache.paimon.mergetree.compact.aggregate.FieldAggregator`
//! and the per-function factories under
//! `org.apache.paimon.mergetree.compact.aggregate.factory`.
//!
//! [`reset`]: FieldAggregator::reset

use std::collections::HashMap;

use arrow_array::{Array, ArrayRef};

use crate::spec::DataType;

mod bool_agg;
mod listagg;
mod numeric;
mod value;

pub(crate) use bool_agg::{BoolAndAgg, BoolOrAgg};
pub(crate) use listagg::ListaggAgg;
pub(crate) use numeric::{MaxAgg, MinAgg, ProductAgg, SumAgg};
pub(crate) use value::{FirstNonNullValueAgg, FirstValueAgg, LastNonNullValueAgg, LastValueAgg};

/// Per-field aggregator.
///
/// The merge function calls [`reset`] once at the start of each primary-key
/// group, then [`agg`] once per row in the group (in user-sequence order),
/// and finally [`result`] to materialize the single-row output column.
///
/// `agg` receives the source Arrow array plus the row index to read; the
/// implementation is expected to downcast to the appropriate typed array.
///
/// [`reset`]: FieldAggregator::reset
/// [`agg`]: FieldAggregator::agg
/// [`result`]: FieldAggregator::result
pub(crate) trait FieldAggregator: Send + Sync + std::fmt::Debug {
    /// Aggregator identifier, e.g. `"sum"`. Matches the
    /// `fields.<col>.aggregate-function` option value.
    fn name(&self) -> &'static str;

    /// Reset internal state at the start of a new primary-key group.
    fn reset(&mut self);

    /// Accumulate one input cell.
    fn agg(&mut self, array: &dyn Array, row_idx: usize) -> crate::Result<()>;

    /// Accumulate an input that sorts before the current accumulator.
    ///
    /// This mirrors Java `FieldAggregator#aggReversed(accumulator, input)`,
    /// whose default semantics are `agg(input, accumulator)`. Implementations
    /// must define this explicitly so order-sensitive aggregators cannot
    /// silently fall back to forward accumulation.
    fn agg_reversed(&mut self, array: &dyn Array, row_idx: usize) -> crate::Result<()>;

    /// Materialize the current accumulator as a 1-row Arrow array.
    fn result(&self) -> crate::Result<ArrayRef>;
}

/// Construct an aggregator by `name` for a column of type `data_type`.
///
/// `field_name` and `table_options` are forwarded for per-field configuration
/// (e.g. `fields.<col>.list-agg-delimiter` for `listagg`).
///
/// Returns [`Error::ConfigInvalid`] when the name is unknown or the column
/// type is incompatible with the requested aggregator — both indicate a user
/// configuration error and should fail at table creation rather than at read
/// time.
///
/// [`Error::ConfigInvalid`]: crate::Error::ConfigInvalid
pub(crate) fn new_aggregator(
    name: &str,
    field_name: &str,
    data_type: &DataType,
    table_options: &HashMap<String, String>,
) -> crate::Result<Box<dyn FieldAggregator>> {
    match name {
        "sum" => Ok(Box::new(SumAgg::new(field_name, data_type)?)),
        "product" => Ok(Box::new(ProductAgg::new(field_name, data_type)?)),
        "min" => Ok(Box::new(MinAgg::new(field_name, data_type)?)),
        "max" => Ok(Box::new(MaxAgg::new(field_name, data_type)?)),
        "last_value" => Ok(Box::new(LastValueAgg::new(field_name, data_type)?)),
        "first_value" => Ok(Box::new(FirstValueAgg::new(field_name, data_type)?)),
        "last_non_null_value" => Ok(Box::new(LastNonNullValueAgg::new(field_name, data_type)?)),
        "first_non_null_value" => Ok(Box::new(FirstNonNullValueAgg::new(field_name, data_type)?)),
        "bool_and" => Ok(Box::new(BoolAndAgg::new(field_name, data_type)?)),
        "bool_or" => Ok(Box::new(BoolOrAgg::new(field_name, data_type)?)),
        "listagg" => Ok(Box::new(ListaggAgg::new(
            field_name,
            data_type,
            table_options,
        )?)),
        other => Err(crate::Error::ConfigInvalid {
            message: format!(
                "Unknown aggregate function '{other}' for field '{field_name}'; \
                 supported: sum, product, min, max, last_value, first_value, \
                 last_non_null_value, first_non_null_value, bool_and, bool_or, listagg"
            ),
        }),
    }
}

/// Helper: build a `ConfigInvalid` error for an unsupported (aggregator, type)
/// pair so every concrete aggregator emits the same phrasing.
pub(crate) fn unsupported_type_error(
    agg_name: &str,
    field_name: &str,
    data_type: &DataType,
) -> crate::Error {
    crate::Error::ConfigInvalid {
        message: format!(
            "Aggregate function '{agg_name}' does not support data type {data_type:?} \
             for field '{field_name}'"
        ),
    }
}
