// Copyright (c) 2025 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#ifndef _MathRoot_MultipleUtils_HeaderFile
#define _MathRoot_MultipleUtils_HeaderFile

#include <MathUtils_Types.hxx>
#include <MathUtils_Config.hxx>
#include <MathRoot_Brent.hxx>
#include <math_Vector.hxx>

#include <NCollection_DynamicArray.hxx>

#include <algorithm>
#include <cmath>

//! @file MathRoot_MultipleUtils.hxx
//! @brief Internal utilities for FindAllRoots / FindAllRootsWithDerivative.
//!
//! Contains result/config types, helper functions, functor adapters and the
//! shared core implementation used by MathRoot_Multiple.hxx.

namespace MathRoot
{
using namespace MathUtils;

// ============================================================================
//  Result and configuration types
// ============================================================================

//! Result for multiple root finding.
//! Contains all found roots sorted in ascending order.
struct MultipleResult
{
  MathUtils::Status                Status = MathUtils::Status::NotConverged; //!< Computation status
  size_t                           NbIterations = 0; //!< Total iterations across all roots
  NCollection_DynamicArray<double> Roots;            //!< Found roots (sorted)
  NCollection_DynamicArray<double> Values;           //!< Function values at roots
  bool IsAllNull = false; //!< True if function is essentially zero in range

  //! Returns true if computation succeeded.
  bool IsDone() const { return Status == MathUtils::Status::OK; }

  //! Conversion to bool for convenient checking.
  explicit operator bool() const { return IsDone(); }

  //! Returns the number of roots found.
  int NbRoots() const { return Roots.Length(); }

  //! Access root by index (0-based).
  double operator[](int theIndex) const { return Roots.Value(theIndex); }
};

//! Configuration for multiple root finding.
struct MultipleConfig
{
  int    NbSamples     = 100;   //!< Number of sample points for initial search
  double XTolerance    = 1e-10; //!< Tolerance on X for convergence
  double FTolerance    = 1e-10; //!< Tolerance on F(X) for convergence
  double NullTolerance = 1e-12; //!< Tolerance to consider function as null
  int    MaxIterations = 100;   //!< Max iterations per root refinement
  double Offset        = 0.0;   //!< Find roots of f(x) - Offset = 0
};

// ============================================================================
//  Helper functions
// ============================================================================

//! In-place insertion sort of roots and corresponding values by ascending root value.
inline void SortRoots(MultipleResult& theResult)
{
  for (int i = 1; i < theResult.Roots.Length(); ++i)
  {
    const double aKeyRoot = theResult.Roots[i];
    const double aKeyVal  = theResult.Values[i];
    int          j        = i - 1;
    while (j >= 0 && theResult.Roots[j] > aKeyRoot)
    {
      theResult.Roots[j + 1]  = theResult.Roots[j];
      theResult.Values[j + 1] = theResult.Values[j];
      --j;
    }
    theResult.Roots[j + 1]  = aKeyRoot;
    theResult.Values[j + 1] = aKeyVal;
  }
}

//! Helper to add a root if it is not a duplicate of an already found root.
inline void AddRoot(MultipleResult& theResult, double theEpsX, double theRoot, double theValue)
{
  for (int k = 0; k < theResult.Roots.Length(); ++k)
  {
    if (std::abs(theRoot - theResult.Roots.Value(k)) < theEpsX)
    {
      return;
    }
  }
  theResult.Roots.Append(theRoot);
  theResult.Values.Append(theValue);
}

//! Compute the minimal X tolerance compatible with the established multi-root behavior.
inline double EffectiveXTolerance(double theLower, double theUpper, double theXTolerance)
{
  const double aMinEpsX = 1.0e-10 * (std::abs(theLower) + std::abs(theUpper));
  return std::max(theXTolerance, aMinEpsX);
}

// ============================================================================
//  Functor adapters for Value-only interface
// ============================================================================

//! Samples a Value-only function and stores f(x)-offset into a math_Vector.
//! @tparam Function type with Value(double theX, double& theF) method
template <typename Function>
struct MultipleSampleValueFn
{
  Function&    myFunc;
  math_Vector& mySamples;
  const double myOffset;

  bool operator()(int theIndex, double theX) const
  {
    double aF = 0.0;
    if (!myFunc.Value(theX, aF))
      return false;
    mySamples(theIndex) = aF - myOffset;
    return true;
  }
};

//! Returns the sampled value at a given index from a math_Vector.
struct MultipleGetValueFn
{
  const math_Vector& mySamples;

  double operator()(int theIndex) const { return mySamples(theIndex); }
};

//! Brent wrapper that adapts a Value-only function for offset root finding.
//! @tparam Function type with Value(double theX, double& theF) method
template <typename Function>
struct MultipleBrentValueWrapper
{
  Function& myFunc;
  double    myOffset;

  bool Value(double theX, double& theY) const
  {
    if (!myFunc.Value(theX, theY))
      return false;
    theY -= myOffset;
    return true;
  }
};

//! Evaluates original (non-offset) function value at a root point via Value interface.
//! @tparam Function type with Value(double theX, double& theF) method
template <typename Function>
struct MultipleGetRootValueFn
{
  Function& myFunc;

  double operator()(double theX) const
  {
    double aF = 0.0;
    myFunc.Value(theX, aF);
    return aF;
  }
};

// ============================================================================
//  Functor adapters for Values (with derivative) interface
// ============================================================================

//! Wrapper exposing a function derivative through the Value() contract required by Brent.
//! @tparam Function type with Values(double theX, double& theF, double& theDF) method
template <typename Function>
struct MultipleDerivativeValueWrapper
{
  Function& myFunc;

  bool Value(double theX, double& theY) const
  {
    double aF = 0.0;
    return myFunc.Values(theX, aF, theY);
  }
};

//! Evaluate the original function value.
//! @tparam Function type with Value(double theX, double& theF) method
template <typename Function>
bool EvaluateValue(Function& theFunc, double theX, double& theValue)
{
  return theFunc.Value(theX, theValue);
}

//! Evaluate the function value shifted by the requested offset.
//! @tparam Function type with Value(double theX, double& theF) method
template <typename Function>
bool EvaluateShiftedValue(Function& theFunc, double theX, double theOffset, double& theValue)
{
  if (!theFunc.Value(theX, theValue))
  {
    return false;
  }

  theValue -= theOffset;
  return true;
}

//! Evaluate the function value and derivative, then shift the value by the requested offset.
//! @tparam Function type with Values(double theX, double& theF, double& theDF) method
template <typename Function>
bool EvaluateShiftedValues(Function& theFunc,
                           double    theX,
                           double    theOffset,
                           double&   theValue,
                           double&   theDerivative)
{
  if (!theFunc.Values(theX, theValue, theDerivative))
  {
    return false;
  }

  theValue -= theOffset;
  return true;
}

//! Refine a bracketed sign change using the same Brent/Newton sequence as math_FunctionRoots.
//! @tparam Function type with Value() and Values() methods
template <typename Function>
bool RefineBracketedRoot(Function&       theFunc,
                         double          theOffset,
                         double          theX1,
                         double          theY1,
                         double          theX2,
                         double          theY2,
                         double          theTolerance,
                         double          theEpsX,
                         MultipleResult& theResult)
{
  constexpr int    THE_MAX_ITERATIONS = 100;
  constexpr double THE_EPS2           = 2.0e-14;
  constexpr double THE_DERIV_EPS      = 1.0e-10;

  int    anIter = 0;
  double aTol2  = 0.5 * theTolerance;
  double aA     = theX1;
  double aB     = theX2;
  double aC     = theX2;
  double aD     = 0.0;
  double anE    = 0.0;
  double aFa    = theY1;
  double aFb    = theY2;
  double aFc    = theY2;

  for (anIter = 1; anIter <= THE_MAX_ITERATIONS; ++anIter)
  {
    if ((aFb > 0.0 && aFc > 0.0) || (aFb < 0.0 && aFc < 0.0))
    {
      aC  = aA;
      aFc = aFa;
      anE = aD = aB - aA;
    }

    if (std::abs(aFc) < std::abs(aFb))
    {
      const double aPrevA  = aA;
      const double aPrevFa = aFa;
      aA                   = aB;
      aB                   = aC;
      aC                   = aPrevA;
      aFa                  = aFb;
      aFb                  = aFc;
      aFc                  = aPrevFa;
    }

    const double aTol1 = THE_EPS2 * std::abs(aB) + aTol2;
    const double aXm   = 0.5 * (aC - aB);
    if (std::abs(aXm) < aTol1 || aFb == 0.0)
    {
      double aNewtonX = aB;
      for (int aNewtonIter = 0; aNewtonIter < 5; ++aNewtonIter)
      {
        double aY    = 0.0;
        double aDfdx = 0.0;
        if (!EvaluateShiftedValues(theFunc, aNewtonX, theOffset, aY, aDfdx))
        {
          return false;
        }

        if (std::abs(aDfdx) <= THE_DERIV_EPS)
        {
          break;
        }

        aNewtonX -= aY / aDfdx;
        if (aNewtonX < theX1 || aNewtonX > theX2)
        {
          break;
        }

        if (!EvaluateShiftedValue(theFunc, aNewtonX, theOffset, aY))
        {
          return false;
        }

        if (std::abs(aY) < std::abs(aFb))
        {
          aB  = aNewtonX;
          aFb = aY;
        }
      }

      double aRootValue = 0.0;
      if (!EvaluateValue(theFunc, aB, aRootValue))
      {
        return false;
      }

      theResult.NbIterations += static_cast<size_t>(anIter);
      AddRoot(theResult, theEpsX, aB, aRootValue);
      return true;
    }

    if (std::abs(anE) >= aTol1 && std::abs(aFa) > std::abs(aFb))
    {
      double       aP = 0.0;
      double       aQ = 0.0;
      const double aS = aFb / aFa;
      if (aA == aC)
      {
        aP = 2.0 * aXm * aS;
        aQ = 1.0 - aS;
      }
      else
      {
        aQ              = aFa / aFc;
        const double aR = aFb / aFc;
        aP              = aS * (2.0 * aXm * aQ * (aQ - aR) - (aB - aA) * (aR - 1.0));
        aQ              = (aQ - 1.0) * (aR - 1.0) * (aS - 1.0);
      }

      if (aP > 0.0)
      {
        aQ = -aQ;
      }

      aP                 = std::abs(aP);
      const double aMin1 = 3.0 * aXm * aQ - std::abs(aTol1 * aQ);
      const double aMin2 = std::abs(anE * aQ);
      if (2.0 * aP < std::min(aMin1, aMin2))
      {
        anE = aD;
        aD  = aP / aQ;
      }
      else
      {
        aD  = aXm;
        anE = aD;
      }
    }
    else
    {
      aD  = aXm;
      anE = aD;
    }

    aA  = aB;
    aFa = aFb;
    if (std::abs(aD) > aTol1)
    {
      aB += aD;
    }
    else
    {
      aB += (aXm >= 0.0) ? std::abs(aTol1) : -std::abs(aTol1);
    }

    if (!EvaluateShiftedValue(theFunc, aB, theOffset, aFb))
    {
      return false;
    }
  }

  theResult.NbIterations += THE_MAX_ITERATIONS;
  return true;
}

//! Derivative-aware implementation mirroring the proven math_FunctionRoots heuristics
//! while operating on the modern callable-based MathRoot API.
//! @tparam Function type with Value() and Values() methods
template <typename Function>
MultipleResult FindAllRootsWithDerivativeImpl(Function&             theFunc,
                                              double                theLower,
                                              double                theUpper,
                                              const MultipleConfig& theConfig)
{
  MultipleResult aResult;
  aResult.Status = MathUtils::Status::OK;

  const double aLower         = std::min(theLower, theUpper);
  const double aUpper         = std::max(theLower, theUpper);
  const int    aNbSamples     = std::max(2 * theConfig.NbSamples, 20);
  const double aDx            = (aUpper - aLower) / aNbSamples;
  const double aEpsX          = EffectiveXTolerance(aLower, aUpper, theConfig.XTolerance);
  const double aRawXTolerance = theConfig.XTolerance;
  const double aMajorDx       = 5.0 * aDx;

  math_Vector aValues(0, aNbSamples);
  double      aX = aLower;
  for (int i = 0; i <= aNbSamples; ++i, aX += aDx)
  {
    if (aX > aUpper)
    {
      aX = aUpper;
    }

    if (!EvaluateShiftedValue(theFunc, aX, theConfig.Offset, aValues(i)))
    {
      aResult.Status = MathUtils::Status::NumericalError;
      return aResult;
    }
  }

  aResult.IsAllNull = true;
  for (int i = 0; i <= aNbSamples; ++i)
  {
    if (aValues(i) > theConfig.NullTolerance || aValues(i) < -theConfig.NullTolerance)
    {
      aResult.IsAllNull = false;
      break;
    }
  }
  if (aResult.IsAllNull)
  {
    return aResult;
  }

  const double aTolerance = aEpsX;
  double       aX1        = aLower;
  for (int i = 0, anIp1 = 1; i < aNbSamples; ++i, ++anIp1, aX1 += aDx)
  {
    double aX2 = aX1 + aDx;
    if (aX2 > aUpper)
    {
      aX2 = aUpper;
    }

    if ((aValues(i) < 0.0 && aValues(anIp1) > 0.0) || (aValues(i) > 0.0 && aValues(anIp1) < 0.0))
    {
      if (!RefineBracketedRoot(theFunc,
                               theConfig.Offset,
                               aX1,
                               aValues(i),
                               aX2,
                               aValues(anIp1),
                               aTolerance,
                               aEpsX,
                               aResult))
      {
        aResult.Status = MathUtils::Status::NumericalError;
        return aResult;
      }
    }
  }

  for (int i = 0; i <= aNbSamples; ++i)
  {
    if (aValues(i) != 0.0)
    {
      continue;
    }

    const double aZeroX  = std::min(aLower + i * aDx, aUpper);
    double       aLeftX  = aZeroX - 0.5 * aDx;
    double       aRightX = aZeroX + 0.5 * aDx;
    if (aLeftX < aLower)
    {
      aLeftX = aLower;
    }
    if (aLeftX > aUpper)
    {
      aLeftX = aUpper;
    }
    if (aRightX < aLower)
    {
      aRightX = aLower;
    }
    if (aRightX > aUpper)
    {
      aRightX = aUpper;
    }

    double aLeftY  = 0.0;
    double aRightY = 0.0;
    if (!EvaluateShiftedValue(theFunc, aLeftX, theConfig.Offset, aLeftY)
        || !EvaluateShiftedValue(theFunc, aRightX, theConfig.Offset, aRightY))
    {
      aResult.Status = MathUtils::Status::NumericalError;
      return aResult;
    }

    if (aLeftY * aRightY < 0.0)
    {
      if (!RefineBracketedRoot(theFunc,
                               theConfig.Offset,
                               aLeftX,
                               aLeftY,
                               aRightX,
                               aRightY,
                               aTolerance,
                               aEpsX,
                               aResult))
      {
        aResult.Status = MathUtils::Status::NumericalError;
        return aResult;
      }
    }
    else if (aLeftY != 0.0 || aRightY != 0.0)
    {
      double aRootValue = 0.0;
      if (!EvaluateValue(theFunc, aZeroX, aRootValue))
      {
        aResult.Status = MathUtils::Status::NumericalError;
        return aResult;
      }
      AddRoot(aResult, aEpsX, aZeroX, aRootValue);
    }
  }

  if (aValues(0) <= theConfig.FTolerance && aValues(0) >= -theConfig.FTolerance)
  {
    double aRootValue = 0.0;
    if (!EvaluateValue(theFunc, aLower, aRootValue))
    {
      aResult.Status = MathUtils::Status::NumericalError;
      return aResult;
    }
    AddRoot(aResult, aEpsX, aLower, aRootValue);
  }

  if (aValues(aNbSamples) <= theConfig.FTolerance && aValues(aNbSamples) >= -theConfig.FTolerance)
  {
    double aRootValue = 0.0;
    if (!EvaluateValue(theFunc, aUpper, aRootValue))
    {
      aResult.Status = MathUtils::Status::NumericalError;
      return aResult;
    }
    AddRoot(aResult, aEpsX, aUpper, aRootValue);
  }

  int    anIm1 = 0;
  int    anIp1 = 2;
  double aMidX = aLower + aDx;
  for (int i = 1; i < aNbSamples; ++i, ++anIm1, ++anIp1, aMidX += aDx)
  {
    if (aMidX > aUpper)
    {
      aMidX = aUpper;
    }

    bool isRediscretize = false;
    if (aValues(i) > 0.0)
    {
      if (aValues(anIm1) > aValues(i) && aValues(anIp1) > aValues(i))
      {
        double aProbeX  = std::max(aLower, aMidX - aDx);
        double aProbeY  = 0.0;
        double aProbeDy = 0.0;
        if (!EvaluateShiftedValues(theFunc, aProbeX, theConfig.Offset, aProbeY, aProbeDy))
        {
          aResult.Status = MathUtils::Status::NumericalError;
          return aResult;
        }

        if (std::abs(aProbeDy) > 1.0e-10)
        {
          const double aStep = aProbeY / aProbeDy;
          if (aStep < aMajorDx && aStep > -aMajorDx)
          {
            isRediscretize = true;
          }
        }

        if (!isRediscretize)
        {
          aProbeX = std::min(aUpper, aMidX + aDx);
          if (!EvaluateShiftedValues(theFunc, aProbeX, theConfig.Offset, aProbeY, aProbeDy))
          {
            aResult.Status = MathUtils::Status::NumericalError;
            return aResult;
          }

          if (std::abs(aProbeDy) > 1.0e-10)
          {
            const double aStep = aProbeY / aProbeDy;
            if (aStep < aMajorDx && aStep > -aMajorDx)
            {
              isRediscretize = true;
            }
          }
        }
      }
    }
    else if (aValues(i) < 0.0)
    {
      if (aValues(anIm1) < aValues(i) && aValues(anIp1) < aValues(i))
      {
        double aProbeX  = std::max(aLower, aMidX - aDx);
        double aProbeY  = 0.0;
        double aProbeDy = 0.0;
        if (!EvaluateShiftedValues(theFunc, aProbeX, theConfig.Offset, aProbeY, aProbeDy))
        {
          aResult.Status = MathUtils::Status::NumericalError;
          return aResult;
        }

        if (std::abs(aProbeDy) > 1.0e-10)
        {
          const double aStep = aProbeY / aProbeDy;
          if (aStep < aMajorDx && aStep > -aMajorDx)
          {
            isRediscretize = true;
          }
        }

        if (!isRediscretize)
        {
          aProbeX = std::min(aUpper, aMidX + aDx);
          if (!EvaluateShiftedValues(theFunc, aProbeX, theConfig.Offset, aProbeY, aProbeDy))
          {
            aResult.Status = MathUtils::Status::NumericalError;
            return aResult;
          }

          if (std::abs(aProbeDy) > 1.0e-10)
          {
            const double aStep = aProbeY / aProbeDy;
            if (aStep < aMajorDx && aStep > -aMajorDx)
            {
              isRediscretize = true;
            }
          }
        }
      }
    }

    if (!isRediscretize)
    {
      continue;
    }

    double aX0      = std::max(aLower, aMidX - aDx);
    double aX3      = std::min(aUpper, aMidX + aDx);
    double aRoot1   = 0.0;
    double aRoot2   = 0.0;
    double aVal1    = 0.0;
    double aVal2    = 0.0;
    double aDer1    = 0.0;
    double aDer2    = 0.0;
    bool   hasRoot1 = false;
    bool   hasRoot2 = false;

    MultipleDerivativeValueWrapper<Function> aDerivativeWrapper{theFunc};
    MathUtils::Config                        aDerivativeConfig;
    aDerivativeConfig.XTolerance    = aRawXTolerance;
    aDerivativeConfig.FTolerance    = 0.0;
    aDerivativeConfig.MaxIterations = theConfig.MaxIterations;

    MathUtils::ScalarResult aDerivativeRoot =
      Brent(aDerivativeWrapper, aX0, aX3, aDerivativeConfig);
    aResult.NbIterations += aDerivativeRoot.NbIterations;
    if (aDerivativeRoot.IsDone() && aDerivativeRoot.Root.has_value())
    {
      aRoot1                = *aDerivativeRoot.Root;
      double aOriginalValue = 0.0;
      if (!EvaluateValue(theFunc, aRoot1, aOriginalValue))
      {
        aResult.Status = MathUtils::Status::NumericalError;
        return aResult;
      }
      aVal1 = std::abs(aOriginalValue - theConfig.Offset);
      if (aVal1 < theConfig.FTolerance)
      {
        hasRoot1 = true;
        if (!EvaluateShiftedValues(theFunc, aRoot1, theConfig.Offset, aOriginalValue, aDer1))
        {
          aResult.Status = MathUtils::Status::NumericalError;
          return aResult;
        }
      }
    }

    double           aXProbe1             = 0.0;
    double           aXProbe2             = 0.0;
    constexpr double THE_GOLDEN_INV_RATIO = 1.0 / MathUtils::THE_GOLDEN_RATIO;
    constexpr double THE_GOLDEN_INV_COMP  = 1.0 - THE_GOLDEN_INV_RATIO;
    const double     aTolCR               = aEpsX * 10.0;
    const double     aLocalTolX           = 0.001 * aEpsX;
    double           aF0                  = aValues(anIm1);
    double           aF3                  = aValues(anIp1);
    const bool       isSearchMinimum      = (aF0 > 0.0);

    if (std::abs(aX3 - aMidX) > std::abs(aX0 - aMidX))
    {
      aXProbe1 = aMidX;
      aXProbe2 = aMidX + THE_GOLDEN_INV_COMP * (aX3 - aMidX);
    }
    else
    {
      aXProbe2 = aMidX;
      aXProbe1 = aMidX - THE_GOLDEN_INV_COMP * (aMidX - aX0);
    }

    double aF1 = 0.0;
    double aF2 = 0.0;
    if (!EvaluateShiftedValue(theFunc, aXProbe1, theConfig.Offset, aF1)
        || !EvaluateShiftedValue(theFunc, aXProbe2, theConfig.Offset, aF2))
    {
      aResult.Status = MathUtils::Status::NumericalError;
      return aResult;
    }

    while (std::abs(aX3 - aX0) > aTolCR * (std::abs(aXProbe1) + std::abs(aXProbe2))
           && std::abs(aXProbe1 - aXProbe2) > aLocalTolX)
    {
      if (isSearchMinimum)
      {
        if (aF2 < aF1)
        {
          aX0      = aXProbe1;
          aXProbe1 = aXProbe2;
          aXProbe2 = THE_GOLDEN_INV_RATIO * aXProbe1 + THE_GOLDEN_INV_COMP * aX3;
          aF0      = aF1;
          aF1      = aF2;
          if (!EvaluateShiftedValue(theFunc, aXProbe2, theConfig.Offset, aF2))
          {
            aResult.Status = MathUtils::Status::NumericalError;
            return aResult;
          }
        }
        else
        {
          aX3      = aXProbe2;
          aXProbe2 = aXProbe1;
          aXProbe1 = THE_GOLDEN_INV_RATIO * aXProbe2 + THE_GOLDEN_INV_COMP * aX0;
          aF3      = aF2;
          aF2      = aF1;
          if (!EvaluateShiftedValue(theFunc, aXProbe1, theConfig.Offset, aF1))
          {
            aResult.Status = MathUtils::Status::NumericalError;
            return aResult;
          }
        }
      }
      else
      {
        if (aF2 > aF1)
        {
          aX0      = aXProbe1;
          aXProbe1 = aXProbe2;
          aXProbe2 = THE_GOLDEN_INV_RATIO * aXProbe1 + THE_GOLDEN_INV_COMP * aX3;
          aF0      = aF1;
          aF1      = aF2;
          if (!EvaluateShiftedValue(theFunc, aXProbe2, theConfig.Offset, aF2))
          {
            aResult.Status = MathUtils::Status::NumericalError;
            return aResult;
          }
        }
        else
        {
          aX3      = aXProbe2;
          aXProbe2 = aXProbe1;
          aXProbe1 = THE_GOLDEN_INV_RATIO * aXProbe2 + THE_GOLDEN_INV_COMP * aX0;
          aF3      = aF2;
          aF2      = aF1;
          if (!EvaluateShiftedValue(theFunc, aXProbe1, theConfig.Offset, aF1))
          {
            aResult.Status = MathUtils::Status::NumericalError;
            return aResult;
          }
        }
      }

      if (aF1 * aF0 < 0.0)
      {
        if (!RefineBracketedRoot(theFunc,
                                 theConfig.Offset,
                                 aX0,
                                 aF0,
                                 aXProbe1,
                                 aF1,
                                 aTolerance,
                                 aEpsX,
                                 aResult))
        {
          aResult.Status = MathUtils::Status::NumericalError;
          return aResult;
        }
      }

      if (aF2 * aF3 < 0.0)
      {
        if (!RefineBracketedRoot(theFunc,
                                 theConfig.Offset,
                                 aXProbe2,
                                 aF2,
                                 aX3,
                                 aF3,
                                 aTolerance,
                                 aEpsX,
                                 aResult))
        {
          aResult.Status = MathUtils::Status::NumericalError;
          return aResult;
        }
      }
    }

    if ((isSearchMinimum && aF1 < aF2) || (!isSearchMinimum && aF1 > aF2))
    {
      if (std::abs(aF1) < theConfig.FTolerance)
      {
        hasRoot2 = true;
        aRoot2   = aXProbe1;
        aVal2    = std::abs(aF1);
      }
    }
    else if (std::abs(aF2) < theConfig.FTolerance)
    {
      hasRoot2 = true;
      aRoot2   = aXProbe2;
      aVal2    = std::abs(aF2);
    }

    if (hasRoot1 && hasRoot2)
    {
      if (aVal2 - aVal1 > theConfig.FTolerance)
      {
        double aRootValue = 0.0;
        if (!EvaluateValue(theFunc, aRoot1, aRootValue))
        {
          aResult.Status = MathUtils::Status::NumericalError;
          return aResult;
        }
        AddRoot(aResult, aEpsX, aRoot1, aRootValue);
      }
      else if (aVal1 - aVal2 > theConfig.FTolerance)
      {
        double aRootValue = 0.0;
        if (!EvaluateValue(theFunc, aRoot2, aRootValue))
        {
          aResult.Status = MathUtils::Status::NumericalError;
          return aResult;
        }
        AddRoot(aResult, aEpsX, aRoot2, aRootValue);
      }
      else
      {
        double aShiftedValue = 0.0;
        if (!EvaluateShiftedValues(theFunc, aRoot2, theConfig.Offset, aShiftedValue, aDer2))
        {
          aResult.Status = MathUtils::Status::NumericalError;
          return aResult;
        }

        const double aChosenRoot = (std::abs(aDer1) < std::abs(aDer2)) ? aRoot1 : aRoot2;
        double       aRootValue  = 0.0;
        if (!EvaluateValue(theFunc, aChosenRoot, aRootValue))
        {
          aResult.Status = MathUtils::Status::NumericalError;
          return aResult;
        }
        AddRoot(aResult, aEpsX, aChosenRoot, aRootValue);
      }
    }
    else if (hasRoot1)
    {
      double aRootValue = 0.0;
      if (!EvaluateValue(theFunc, aRoot1, aRootValue))
      {
        aResult.Status = MathUtils::Status::NumericalError;
        return aResult;
      }
      AddRoot(aResult, aEpsX, aRoot1, aRootValue);
    }
    else if (hasRoot2)
    {
      double aRootValue = 0.0;
      if (!EvaluateValue(theFunc, aRoot2, aRootValue))
      {
        aResult.Status = MathUtils::Status::NumericalError;
        return aResult;
      }
      AddRoot(aResult, aEpsX, aRoot2, aRootValue);
    }
  }

  SortRoots(aResult);
  return aResult;
}

// ============================================================================
//  Interval handlers
// ============================================================================

//! No-op interval handler for functions without derivative.
struct MultipleNoExtraHandler
{
  void operator()(int, double, double, double, double, MultipleResult&, double) const {}
};

// ============================================================================
//  Core implementation
// ============================================================================

//! Core implementation for finding all roots in an interval.
//! Shared logic for both Value-only and Values (with derivative) interfaces.
//!
//! @tparam SampleFn callable (int theIndex, double theX) -> bool
//! @tparam GetValueFn callable (int theIndex) -> double, returns f(x)-offset at sample
//! @tparam BrentWrapperT type with Value(double, double&) for Brent root finding
//! @tparam GetRootValueFn callable (double theX) -> double, returns original f(x)
//! @tparam IntervalExtraFn callable (int, x0, x1, f0, f1, result, epsX) -> void
template <typename SampleFn,
          typename GetValueFn,
          typename BrentWrapperT,
          typename GetRootValueFn,
          typename IntervalExtraFn>
MultipleResult FindAllRootsImpl(double                theLower,
                                double                theUpper,
                                const MultipleConfig& theConfig,
                                SampleFn              theSampleFn,
                                GetValueFn            theGetValue,
                                BrentWrapperT&        theBrentWrapper,
                                GetRootValueFn        theGetRootValue,
                                IntervalExtraFn       theIntervalExtra)
{
  MultipleResult aResult;
  aResult.Status = MathUtils::Status::OK;

  // Ensure proper ordering
  const double aLower = std::min(theLower, theUpper);
  const double aUpper = std::max(theLower, theUpper);

  // Minimum samples
  const int    aNbSamples = std::max(2 * theConfig.NbSamples, 20);
  const double aDx        = (aUpper - aLower) / aNbSamples;

  // Ensure EpsX is not too small relative to interval
  const double aEpsX = EffectiveXTolerance(aLower, aUpper, theConfig.XTolerance);

  // Sample function values
  math_Vector aXValues(0, aNbSamples);
  for (int i = 0; i <= aNbSamples; ++i)
  {
    double aX = aLower + i * aDx;
    if (aX > aUpper)
      aX = aUpper;
    aXValues(i) = aX;

    if (!theSampleFn(i, aX))
    {
      aResult.Status = MathUtils::Status::NumericalError;
      return aResult;
    }
  }

  // Check if function is essentially null everywhere
  aResult.IsAllNull = true;
  for (int i = 0; i <= aNbSamples; ++i)
  {
    if (std::abs(theGetValue(i)) > theConfig.NullTolerance)
    {
      aResult.IsAllNull = false;
      break;
    }
  }

  if (aResult.IsAllNull)
  {
    return aResult;
  }

  // Find sign changes
  for (int i = 0; i < aNbSamples; ++i)
  {
    const double aF0 = theGetValue(i);
    const double aF1 = theGetValue(i + 1);
    const double aX0 = aXValues(i);
    const double aX1 = aXValues(i + 1);

    // Exact zero at sample point
    if (std::abs(aF0) < theConfig.FTolerance)
    {
      AddRoot(aResult, aEpsX, aX0, aF0 + theConfig.Offset);
      continue;
    }

    // Sign change detected
    if (aF0 * aF1 < 0.0)
    {
      MathUtils::Config aBrentConfig;
      aBrentConfig.XTolerance    = aEpsX;
      aBrentConfig.FTolerance    = theConfig.FTolerance;
      aBrentConfig.MaxIterations = theConfig.MaxIterations;

      MathUtils::ScalarResult aBrentResult = Brent(theBrentWrapper, aX0, aX1, aBrentConfig);
      aResult.NbIterations += aBrentResult.NbIterations;

      if (aBrentResult.IsDone() && aBrentResult.Root.has_value())
      {
        AddRoot(aResult, aEpsX, *aBrentResult.Root, theGetRootValue(*aBrentResult.Root));
      }
    }
    else
    {
      // Additional per-interval processing (e.g., tangential root detection)
      theIntervalExtra(i, aX0, aX1, aF0, aF1, aResult, aEpsX);
    }
  }

  // Check last sample point
  if (std::abs(theGetValue(aNbSamples)) < theConfig.FTolerance)
  {
    AddRoot(aResult, aEpsX, aXValues(aNbSamples), theGetValue(aNbSamples) + theConfig.Offset);
  }

  SortRoots(aResult);
  return aResult;
}

} // namespace MathRoot

#endif // _MathRoot_MultipleUtils_HeaderFile
