"""
    ModAB()

ModAB (Modified Anderson-Bjork)

Use the [ModAB method](https://iopscience.iop.org/article/10.1088/1757-899X/1276/1/012010/) to find a root of a bracketed
function, with a convergence rate between 1.7 and 1.8.

This method was introduced in the paper "Modified Anderson-Bjork’s method for solving non-linear equations 
in structural mechanics" (https://doi.org/10.1088/1757-899X/1276/1/012010) by
N Ganchovski and A Traykov.
"""
struct ModAB <: AbstractBracketingAlgorithm
end

function SciMLBase.__solve(
        prob::IntervalNonlinearProblem, alg::ModAB, args...;
        maxiters = 1000, abstol = nothing, verbose::NonlinearVerbosity = NonlinearVerbosity(), kwargs...
    )
    @assert !SciMLBase.isinplace(prob) "`ModAB` only supports out-of-place problems."

    f = Base.Fix2(prob.f, prob.p)
    x1, x2 = minmax(promote(prob.tspan...)...)
    y1, y2 = f(x1), f(x2)

    abstol = NonlinearSolveBase.get_tolerance(
        x1, abstol, promote_type(eltype(x1), eltype(x2))
    )

    if iszero(y1)
        return build_exact_solution(prob, alg, x1, y1, ReturnCode.ExactSolutionLeft)
    end

    if iszero(y2)
        return build_exact_solution(prob, alg, x2, y2, ReturnCode.ExactSolutionRight)
    end

    if sign(y1) == sign(y2)
        @SciMLMessage(
            "The interval is not an enclosing interval, opposite signs at the \
        boundaries are required.",
            verbose, :non_enclosing_interval
        )
        return build_bracketing_solution(prob, alg, x1, y1, x1, x2, ReturnCode.InitialFailure)
    end

    bisecting = true
    side = 0 # tracks the side that has moved at the previous iteration
    ϵ = abstol
    i = 1
    threshold = x2 - x1  # Threshold to fall back to bisection if AB fails to shrink the interval enough
    C = 16 # safety factor for threshold corresponding to 4 iterations = 2^4
    while i < maxiters
        local x3, y3
        if bisecting # Bisection method is used
            x3 = (x1 + x2) / 2
            y3 = f(x3) # Function value at midpoint
            ym = (y1 + y2) / 2 # Ordinate of chord at midpoint
            # calculate k on each bisection step with account for local function properties and symmetry
            r = 1 - abs(ym / (y2 - y1)) # Symmetry factor
            k = r * r # Deviation factor
            # Check if the function is close enough to linear
            if abs(ym - y3) < k * (abs(ym) + abs(y3))
                bisecting = false
                threshold = (x2 - x1) * C
            end
        else # Anderson-Bjork method is used
            x3 = (x1 * y2 - y1 * x2) / (y2 - y1)
            x3 = clamp(x3, nextfloat(x1), prevfloat(x2))
            y3 = f(x3)
            threshold /= 2
        end
        if iszero(y3)
            return build_exact_solution(prob, alg, x3, y3, ReturnCode.Success)
        elseif (x2 - x1) < 2ϵ
            return build_bracketing_solution(prob, alg, x3, y3, x1, x2, ReturnCode.Success)
        end
        if sign(y1) == sign(y3)
            if side == 1  # Apply Anderson-Bjork correction on the right side
                m = 1 - y3 / y1
                y2 *= m <= 0 ? inv(2 * one(y1)) : m
            elseif !bisecting
                side = 1
            end
            x1, y1 = x3, y3
        else
            if side == -1  # Apply Anderson-Bjork correction on the left side
                m = 1 - y3 / y2
                y1 *= m <= 0 ? inv(2 * one(y1)) : m
            elseif !bisecting
                side = -1
            end
            x2, y2 = x3, y3
        end
        if nextfloat(x1) == x2
            return build_bracketing_solution(prob, alg, x2, f(x2), x1, x2, ReturnCode.FloatingPointLimit)
        end
        i += 1
        if x2 - x1 > threshold # Ff AB fails to shrink the interval enough
            bisecting = true   # reset to bisection
            side = 0
        end
    end
    return build_bracketing_solution(prob, alg, x1, y1, x1, x2, ReturnCode.MaxIters)
end
