/*
 * 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.
 */

#pragma once

/// \file iceberg/expression/aggregate.h
/// Aggregate expression definitions.

#include <memory>
#include <span>
#include <string>
#include <vector>

#include "iceberg/expression/expression.h"
#include "iceberg/expression/term.h"
#include "iceberg/result.h"
#include "iceberg/type_fwd.h"

namespace iceberg {

/// \brief Base aggregate holding an operation and a term.
template <TermType T>
class ICEBERG_EXPORT Aggregate : public virtual Expression {
 public:
  ~Aggregate() override = default;

  Expression::Operation op() const override { return operation_; }

  const std::shared_ptr<T>& term() const { return term_; }

  std::string ToString() const override;

 protected:
  Aggregate(Expression::Operation op, std::shared_ptr<T> term)
      : operation_(op), term_(std::move(term)) {}

  static constexpr bool IsSupportedOp(Expression::Operation op) {
    return op == Expression::Operation::kCount ||
           op == Expression::Operation::kCountNull ||
           op == Expression::Operation::kCountStar || op == Expression::Operation::kMax ||
           op == Expression::Operation::kMin;
  }

  Expression::Operation operation_;
  std::shared_ptr<T> term_;
};

/// \brief Base class for unbound aggregates.
class ICEBERG_EXPORT UnboundAggregate : public virtual Expression,
                                        public Unbound<Expression> {
 public:
  ~UnboundAggregate() override = default;

  bool is_unbound_aggregate() const override { return true; }
};

/// \brief Template for unbound aggregates that carry a term and operation.
template <typename B>
class ICEBERG_EXPORT UnboundAggregateImpl : public UnboundAggregate,
                                            public Aggregate<UnboundTerm<B>> {
  using BASE = Aggregate<UnboundTerm<B>>;

 public:
  static Result<std::shared_ptr<UnboundAggregateImpl<B>>> Make(
      Expression::Operation op, std::shared_ptr<UnboundTerm<B>> term);

  std::shared_ptr<const NamedReference> reference() const override {
    return BASE::term() ? BASE::term()->reference() : nullptr;
  }

  Result<std::shared_ptr<Expression>> Bind(const Schema& schema,
                                           bool case_sensitive) const override;

 private:
  UnboundAggregateImpl(Expression::Operation op, std::shared_ptr<UnboundTerm<B>> term)
      : BASE(op, std::move(term)) {
    ICEBERG_DCHECK(BASE::IsSupportedOp(op), "Unexpected aggregate operation");
    ICEBERG_DCHECK(op == Expression::Operation::kCountStar || BASE::term() != nullptr,
                   "Aggregate term cannot be null except for COUNT(*)");
  }
};

/// \brief Base class for bound aggregates.
class ICEBERG_EXPORT BoundAggregate : public Aggregate<BoundTerm>, public Bound {
 public:
  using Aggregate<BoundTerm>::op;
  using Aggregate<BoundTerm>::term;

  /// \brief Base class for aggregators.
  class Aggregator {
   public:
    virtual ~Aggregator() = default;

    virtual Status Update(const StructLike& data) = 0;

    virtual Status Update(const DataFile& file) = 0;

    /// \brief Whether the aggregator is still valid.
    virtual bool IsValid() const = 0;

    /// \brief Get the result of the aggregation.
    /// \return The result of the aggregation.
    /// \note It is an undefined behavior to call this method if any previous Update call
    /// has returned an error or if IsValid() returns false.
    virtual Literal GetResult() const = 0;
  };

  std::shared_ptr<BoundReference> reference() override {
    ICEBERG_DCHECK(term() != nullptr || op() == Expression::Operation::kCountStar,
                   "Bound aggregate term should not be null except for COUNT(*)");
    return term() ? term()->reference() : nullptr;
  }

  Result<Literal> Evaluate(const StructLike& data) const override = 0;

  virtual Result<Literal> Evaluate(const DataFile& file) const = 0;

  /// \brief Whether metrics in the data file are sufficient to evaluate.
  virtual bool HasValue(const DataFile& file) const = 0;

  bool is_bound_aggregate() const override { return true; }

  /// \brief Create a new aggregator for this aggregate.
  /// \note The returned aggregator cannot outlive the BoundAggregate that creates it.
  virtual std::unique_ptr<Aggregator> NewAggregator() const = 0;

 protected:
  BoundAggregate(Expression::Operation op, std::shared_ptr<BoundTerm> term)
      : Aggregate<BoundTerm>(op, std::move(term)) {}
};

/// \brief Base class for COUNT aggregates.
class ICEBERG_EXPORT CountAggregate : public BoundAggregate {
 public:
  Result<Literal> Evaluate(const StructLike& data) const override;
  Result<Literal> Evaluate(const DataFile& file) const override;

  std::unique_ptr<Aggregator> NewAggregator() const override;

  /// \brief Count for a single row. Subclasses implement this.
  virtual Result<int64_t> CountFor(const StructLike& data) const = 0;
  /// \brief Count using metrics from a data file.
  virtual Result<int64_t> CountFor(const DataFile& file) const = 0;

 protected:
  CountAggregate(Expression::Operation op, std::shared_ptr<BoundTerm> term)
      : BoundAggregate(op, std::move(term)) {}
};

/// \brief COUNT(term) aggregate.
class ICEBERG_EXPORT CountNonNullAggregate : public CountAggregate {
 public:
  static Result<std::unique_ptr<CountNonNullAggregate>> Make(
      std::shared_ptr<BoundTerm> term);

  Result<int64_t> CountFor(const StructLike& data) const override;
  Result<int64_t> CountFor(const DataFile& file) const override;
  bool HasValue(const DataFile& file) const override;

 private:
  explicit CountNonNullAggregate(std::shared_ptr<BoundTerm> term);
};

/// \brief COUNT_NULL(term) aggregate.
class ICEBERG_EXPORT CountNullAggregate : public CountAggregate {
 public:
  static Result<std::unique_ptr<CountNullAggregate>> Make(
      std::shared_ptr<BoundTerm> term);

  Result<int64_t> CountFor(const StructLike& data) const override;
  Result<int64_t> CountFor(const DataFile& file) const override;
  bool HasValue(const DataFile& file) const override;

 private:
  explicit CountNullAggregate(std::shared_ptr<BoundTerm> term);
};

/// \brief COUNT(*) aggregate.
class ICEBERG_EXPORT CountStarAggregate : public CountAggregate {
 public:
  static Result<std::unique_ptr<CountStarAggregate>> Make();

  Result<int64_t> CountFor(const StructLike& data) const override;
  Result<int64_t> CountFor(const DataFile& file) const override;
  bool HasValue(const DataFile& file) const override;

 private:
  CountStarAggregate();
};

/// \brief Bound MAX aggregate.
class ICEBERG_EXPORT MaxAggregate : public BoundAggregate {
 public:
  static Result<std::unique_ptr<MaxAggregate>> Make(std::shared_ptr<BoundTerm> term);

  Result<Literal> Evaluate(const StructLike& data) const override;
  Result<Literal> Evaluate(const DataFile& file) const override;
  bool HasValue(const DataFile& file) const override;

  std::unique_ptr<Aggregator> NewAggregator() const override;

 private:
  explicit MaxAggregate(std::shared_ptr<BoundTerm> term);
};

/// \brief Bound MIN aggregate.
class ICEBERG_EXPORT MinAggregate : public BoundAggregate {
 public:
  static Result<std::unique_ptr<MinAggregate>> Make(std::shared_ptr<BoundTerm> term);

  Result<Literal> Evaluate(const StructLike& data) const override;
  Result<Literal> Evaluate(const DataFile& file) const override;
  bool HasValue(const DataFile& file) const override;

  std::unique_ptr<Aggregator> NewAggregator() const override;

 private:
  explicit MinAggregate(std::shared_ptr<BoundTerm> term);
};

/// \brief Evaluates bound aggregates over StructLike data.
class ICEBERG_EXPORT AggregateEvaluator {
 public:
  virtual ~AggregateEvaluator() = default;

  /// \brief Create an evaluator for a single bound aggregate.
  /// \param aggregate The bound aggregate to evaluate across rows.
  static Result<std::unique_ptr<AggregateEvaluator>> Make(
      std::shared_ptr<BoundAggregate> aggregate);

  /// \brief Create an evaluator for multiple bound aggregates.
  /// \param aggregates Aggregates to evaluate in one pass; order is preserved in
  /// Results().
  static Result<std::unique_ptr<AggregateEvaluator>> Make(
      std::vector<std::shared_ptr<BoundAggregate>> aggregates);

  /// \brief Update aggregates with a row.
  virtual Status Update(const StructLike& data) = 0;

  /// \brief Update aggregates using data file metrics.
  virtual Status Update(const DataFile& file) = 0;

  /// \brief Final aggregated value.
  virtual Result<std::span<const Literal>> GetResults() const = 0;

  /// \brief Convenience accessor when only one aggregate is evaluated.
  virtual Result<Literal> GetResult() const = 0;

  /// \brief Whether all aggregators are still valid (metrics present).
  virtual bool AllAggregatorsValid() const = 0;
};

}  // namespace iceberg
