import sympy as sp
from sympy import Piecewise, simplify

def symbolic_BSpline(t, c=None, k=None, xi_symbol=None):
    """
    Constructs a symbolic B-spline expression with explicit zero constraints outside the knot domain

    Parameters:
        t          : Knot vector defining the spline support (list or array)
        c          : Coefficients for each B-spline basis function (default=None → all ones)
        k          : Polynomial degree of the B-spline
        xi_symbol  : Symbol used as the spline parameter (default=None → xi)

    Returns:
        sympy.Expr
            The simplified symbolic B-spline expression
    """

    # Setup Defaults
    if xi_symbol is None:
        xi_symbol = sp.Symbol('xi')
    
    K = list(t)
    # Validate degree k
    if k is None:
        raise ValueError("Polynomial degree 'k' must be provided.")
        
    n_basis = len(K) - k - 1
    if c is None:
        c = [1] * n_basis

    # Recursive Basis Function (Cox-de Boor)
    def _basis(i, p):
        # Base Case: Degree 0 (Step functions)
        if p == 0:
            # Standard interval: [K[i], K[i+1])
            cond = (xi_symbol >= K[i]) & (xi_symbol < K[i+1])
            
            # Special case: Close the very last interval [..., K_last]
            if K[i+1] == K[-1]:
                cond = (xi_symbol >= K[i]) & (xi_symbol <= K[i+1])
                
            return Piecewise((1, cond), (0, True))

        # Recursive Step: Degree p
        else:
            left_denom = K[i+p] - K[i]
            right_denom = K[i+p+1] - K[i+1]
            
            left_term = 0
            right_term = 0
            
            # First term (avoid division by zero)
            if left_denom != 0:
                left_term = (xi_symbol - K[i]) / left_denom * _basis(i, p-1)
                
            # Second term (avoid division by zero)
            if right_denom != 0:
                right_term = (K[i+p+1] - xi_symbol) / right_denom * _basis(i+1, p-1)
            
            expr = left_term + right_term
            
            # Optimization: Explicitly restrict symbolic support to help simplification
            # This prevents SymPy from evaluating the polynomial far outside its domain
            support_cond = (xi_symbol >= K[i]) & (xi_symbol <= K[i+p+1])
            return Piecewise((expr, support_cond), (0, True))

    # Main Construction
    # Sum weighted basis functions: S(xi) = sum( c_i * N_i,k )
    spline_expr = sum(c[i] * _basis(i, k) for i in range(n_basis))

    # Final Safety: Zero out everything outside the global knot vector range
    # (This catches any lingering artifacts from symbolic simplification)
    global_domain = (xi_symbol >= K[0]) & (xi_symbol <= K[-1])
    final_expr = Piecewise((spline_expr, global_domain), (0, True))

    return simplify(final_expr)