"""
Douglas-Rachford based solver for constraint projection.
"""
import numpy as np
import logging

logger = logging.getLogger("DANCEST.Solver")

def project_box_constraints(f_int: np.ndarray,
                            lower_bound: float,
                            upper_bound: float,
                            max_iters: int = 50,
                            tolerance: float = 1e-5) -> np.ndarray:
    """
    Projects the input array onto a simple box constraint set [lower, upper].

    For simple box constraints, the projection is equivalent to clipping.
    A simple iterative refinement loop is shown here for consistency with the
    Douglas-Rachford concept, though it converges in one step for this problem.
    
    Parameters
    ----------
    f_int : np.ndarray
        The intermediate prediction array from the fusion phase.
    lower_bound : float
        The minimum allowable value for any element in the array.
    upper_bound : float
        The maximum allowable value for any element in the array.
    max_iters : int, optional
        Maximum number of iterations, by default 50.
    tolerance : float, optional
        Tolerance for early stopping, by default 1e-5.

    Returns
    -------
    np.ndarray
        The projected array that satisfies the box constraints.
    """
    f_k = f_int.copy()
    
    for i in range(max_iters):
        # The proximal operator for a box constraint is just clipping.
        f_k_plus_1 = np.clip(f_k, lower_bound, upper_bound)
        
        # Check for convergence
        if np.linalg.norm(f_k_plus_1 - f_k) < tolerance:
            logger.info(f"Projection converged in {i+1} iterations.")
            return f_k_plus_1
            
        f_k = f_k_plus_1

    logger.warning(f"Projection did not converge within {max_iters} iterations.")
    return f_k