/*
 * 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/predicate.h
/// Predicate interface for boolean expressions that test terms.

#include <unordered_set>

#include "iceberg/expression/expression.h"
#include "iceberg/expression/literal.h"
#include "iceberg/expression/term.h"
#include "iceberg/iceberg_export.h"

namespace iceberg {

/// \brief A predicate is a boolean expression that tests a term against some criteria.
///
/// \tparam TermType The type of the term being tested
template <TermType T>
class ICEBERG_EXPORT Predicate : public virtual Expression {
 public:
  ~Predicate() override;

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

  /// \brief Returns the term this predicate tests.
  const std::shared_ptr<T>& term() const { return term_; }

 protected:
  /// \brief Create a predicate with an operation and term.
  ///
  /// \param op The operation this predicate performs
  /// \param term The term this predicate tests
  Predicate(Expression::Operation op, std::shared_ptr<T> term);

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

/// \brief Non-template base class for all UnboundPredicate instances.
///
/// This class enables type erasure for template-based UnboundPredicate classes,
/// allowing them to be used in non-template visitor interfaces.
class ICEBERG_EXPORT UnboundPredicate : public virtual Expression,
                                        public Unbound<Expression> {
 public:
  ~UnboundPredicate() override = default;

  /// \brief Returns the reference of this UnboundPredicate.
  std::shared_ptr<const NamedReference> reference() const override = 0;

  /// \brief Bind this UnboundPredicate.
  Result<std::shared_ptr<Expression>> Bind(const Schema& schema,
                                           bool case_sensitive) const override = 0;

  /// \brief Negate this UnboundPredicate.
  Result<std::shared_ptr<Expression>> Negate() const override = 0;

  bool is_unbound_predicate() const override { return true; }

  /// \brief Returns the term of this predicate as a base Term reference.
  virtual const Term& unbound_term() const = 0;

  /// \brief Returns the literals of this predicate.
  virtual std::span<const Literal> literals() const = 0;

 protected:
  UnboundPredicate() = default;
};

/// \brief Unbound predicates contain unbound terms and must be bound to a concrete schema
/// before they can be evaluated.
///
/// \tparam B The bound type this predicate produces when binding is successful
template <typename B>
class ICEBERG_EXPORT UnboundPredicateImpl : public UnboundPredicate,
                                            public Predicate<UnboundTerm<B>> {
  using BASE = Predicate<UnboundTerm<B>>;

 public:
  /// \brief Create an unbound predicate (unary operation).
  ///
  /// \param op The operation (kIsNull, kNotNull, kIsNan, kNotNan)
  /// \param term The unbound term
  /// \return Result containing the unbound predicate or an error
  static Result<std::unique_ptr<UnboundPredicateImpl<B>>> Make(
      Expression::Operation op, std::shared_ptr<UnboundTerm<B>> term);

  /// \brief Create an unbound predicate with a single value.
  ///
  /// \param op The operation
  /// \param term The unbound term
  /// \param value The literal value
  /// \return Result containing the unbound predicate or an error
  static Result<std::unique_ptr<UnboundPredicateImpl<B>>> Make(
      Expression::Operation op, std::shared_ptr<UnboundTerm<B>> term, Literal value);

  /// \brief Create an unbound predicate with multiple values.
  ///
  /// \param op The operation (typically kIn or kNotIn)
  /// \param term The unbound term
  /// \param values Vector of literal values
  /// \return Result containing the unbound predicate or an error
  static Result<std::unique_ptr<UnboundPredicateImpl<B>>> Make(
      Expression::Operation op, std::shared_ptr<UnboundTerm<B>> term,
      std::vector<Literal> values);

  ~UnboundPredicateImpl() override;

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

  std::string ToString() const override;

  bool Equals(const Expression& other) const override;

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

  Result<std::shared_ptr<Expression>> Negate() const override;

  const Term& unbound_term() const override { return *BASE::term(); }

  std::span<const Literal> literals() const override { return values_; }

 private:
  UnboundPredicateImpl(Expression::Operation op, std::shared_ptr<UnboundTerm<B>> term);
  UnboundPredicateImpl(Expression::Operation op, std::shared_ptr<UnboundTerm<B>> term,
                       Literal value);
  UnboundPredicateImpl(Expression::Operation op, std::shared_ptr<UnboundTerm<B>> term,
                       std::vector<Literal> values);

  Result<std::shared_ptr<Expression>> BindUnaryOperation(
      std::shared_ptr<B> bound_term) const;
  Result<std::shared_ptr<Expression>> BindLiteralOperation(
      std::shared_ptr<B> bound_term) const;
  Result<std::shared_ptr<Expression>> BindInOperation(
      std::shared_ptr<B> bound_term) const;

 private:
  std::vector<Literal> values_;
};

/// \brief Bound predicates contain bound terms and can be evaluated.
class ICEBERG_EXPORT BoundPredicate : public Predicate<BoundTerm>, public Bound {
 public:
  ~BoundPredicate() override;

  using Predicate<BoundTerm>::op;

  using Predicate<BoundTerm>::term;

  std::shared_ptr<BoundReference> reference() override { return term_->reference(); }

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

  bool is_bound_predicate() const override { return true; }

  /// \brief Test a value against this predicate.
  ///
  /// \param value The literal value to test
  /// \return true if the predicate passes, false otherwise
  virtual Result<bool> Test(const Literal& value) const = 0;

  enum class Kind : int8_t {
    // A unary predicate (tests for null, not-null, etc.).
    kUnary = 0,
    // A literal predicate (compares against a literal).
    kLiteral,
    // A set predicate (tests membership in a set).
    kSet,
  };

  /// \brief Returns the kind of this bound predicate.
  virtual Kind kind() const = 0;

 protected:
  BoundPredicate(Expression::Operation op, std::shared_ptr<BoundTerm> term);
};

/// \brief Bound unary predicate (null, not-null, etc.).
class ICEBERG_EXPORT BoundUnaryPredicate : public BoundPredicate {
 public:
  /// \brief Create a bound unary predicate.
  ///
  /// \param op The unary operation (kIsNull, kNotNull, kIsNan, kNotNan)
  /// \param term The bound term to test
  /// \return Result containing the bound unary predicate or an error
  static Result<std::unique_ptr<BoundUnaryPredicate>> Make(
      Expression::Operation op, std::shared_ptr<BoundTerm> term);

  ~BoundUnaryPredicate() override;

  Result<bool> Test(const Literal& value) const override;

  Kind kind() const override { return Kind::kUnary; }

  std::string ToString() const override;

  Result<std::shared_ptr<Expression>> Negate() const override;

  bool Equals(const Expression& other) const override;

 private:
  BoundUnaryPredicate(Expression::Operation op, std::shared_ptr<BoundTerm> term);
};

/// \brief Bound literal predicate (comparison against a single value).
class ICEBERG_EXPORT BoundLiteralPredicate : public BoundPredicate {
 public:
  /// \brief Create a bound literal predicate.
  ///
  /// \param op The comparison operation (kLt, kLtEq, kGt, kGtEq, kEq, kNotEq)
  /// \param term The bound term to compare
  /// \param literal The literal value to compare against
  /// \return Result containing the bound literal predicate or an error
  static Result<std::unique_ptr<BoundLiteralPredicate>> Make(
      Expression::Operation op, std::shared_ptr<BoundTerm> term, Literal literal);

  ~BoundLiteralPredicate() override;

  /// \brief Returns the literal being compared against.
  const Literal& literal() const { return literal_; }

  Result<bool> Test(const Literal& value) const override;

  Kind kind() const override { return Kind::kLiteral; }

  std::string ToString() const override;

  Result<std::shared_ptr<Expression>> Negate() const override;

  bool Equals(const Expression& other) const override;

 private:
  BoundLiteralPredicate(Expression::Operation op, std::shared_ptr<BoundTerm> term,
                        Literal literal);

  Literal literal_;
};

/// \brief Bound set predicate (membership testing against a set of values).
class ICEBERG_EXPORT BoundSetPredicate : public BoundPredicate {
 public:
  using LiteralSet = std::unordered_set<Literal, LiteralHash>;

  /// \brief Create a bound set predicate.
  ///
  /// \param op The set operation (kIn, kNotIn)
  /// \param term The bound term to test for membership
  /// \param literals The set of literal values to test against
  /// \return Result containing the bound set predicate or an error
  static Result<std::unique_ptr<BoundSetPredicate>> Make(
      Expression::Operation op, std::shared_ptr<BoundTerm> term,
      std::span<const Literal> literals);

  /// \brief Create a bound set predicate using a set of literals.
  ///
  /// \param op The set operation (kIn, kNotIn)
  /// \param term The bound term to test for membership
  /// \param value_set The set of literal values to test against
  /// \return Result containing the bound set predicate or an error
  static Result<std::unique_ptr<BoundSetPredicate>> Make(Expression::Operation op,
                                                         std::shared_ptr<BoundTerm> term,
                                                         LiteralSet value_set);

  ~BoundSetPredicate() override;

  /// \brief Returns the set of literals to test against.
  const LiteralSet& literal_set() const { return value_set_; }

  Result<bool> Test(const Literal& value) const override;

  Kind kind() const override { return Kind::kSet; }

  std::string ToString() const override;

  Result<std::shared_ptr<Expression>> Negate() const override;

  bool Equals(const Expression& other) const override;

 private:
  BoundSetPredicate(Expression::Operation op, std::shared_ptr<BoundTerm> term,
                    std::span<const Literal> literals);

  BoundSetPredicate(Expression::Operation op, std::shared_ptr<BoundTerm> term,
                    LiteralSet value_set);

  LiteralSet value_set_;
};

}  // namespace iceberg
