import numpy as np
import sympy  as sp
from sympy              import latex
from IPython.display    import Math, display as ipy_display

def display(*args, evalf=False, prec=5, mathrm=False):
    r"""
    Displays one or more objects horizontally in LaTeX.

    Usage:
        display(x, "x", y, "y")                            # no evalf
        display(x, "x", True, y, "y")                      # True applies to x only
        display(x, "x", False, y, "y", True)               # trailing True → all, False overrides x
        display(deltaDred, r"\delta D_{red}", R_red, True)  # trailing bool → all
        display(x, "x", y, "y", evalf=True)                # keyword → all

    Parameters:
        *args   : Alternating (obj, name) pairs, with optional bool per object
                  A trailing bool applies universally; per-object bools override
        evalf   : Global evalf flag (default: False)
        prec    : Precision digits (default: 5)
        mathrm  : Boolean to display as Roman text
    """

    # Safe Rounding Helper
    def _safe_round(x):
        try:
            x = sp.sympify(x)
            val = x.evalf(prec)
            return val.replace(
                lambda a: isinstance(a, sp.Float),
                lambda a: sp.Float(round(float(a), prec))
            ).evalf(prec)
        except (TypeError, ValueError):
            return x

    # Object Preparation
    def _prepare_object(raw_obj, local_evalf):
        if isinstance(raw_obj, np.ndarray):
            sym_obj = sp.Matrix(raw_obj)
        elif isinstance(raw_obj, (sp.Matrix, list)):
            sym_obj = sp.Matrix(raw_obj)
        elif isinstance(raw_obj, sp.NDimArray):
            sym_obj = raw_obj
        else:
            sym_obj = sp.sympify(raw_obj)

        if local_evalf:
            if hasattr(sym_obj, 'applyfunc'):
                return sym_obj.applyfunc(_safe_round)
            return _safe_round(sym_obj)

        return sym_obj

    # --- Step 1: First pass — parse pairs with per-object bools ---
    pairs = []
    i = 0
    while i < len(args):
        if isinstance(args[i], bool):
            i += 1
            continue
        obj = args[i]
        name = None
        local_evalf = evalf
        i += 1

        # Check if next arg is a string name
        if i < len(args) and isinstance(args[i], str):
            name = args[i]
            i += 1

        # Check if next arg is a per-object bool (not the last arg)
        if i < len(args) and isinstance(args[i], bool) and i != len(args) - 1:
            local_evalf = args[i]
            i += 1

        pairs.append((obj, name, local_evalf))

    # --- Step 2: If last arg is bool, apply universally ---
    if len(args) > 0 and isinstance(args[-1], bool):
        trailing_bool = args[-1]
        pairs = [(obj, name, trailing_bool) for obj, name, _ in pairs]

    # --- Step 3: Re-apply per-object bools to override universal ---
    rebuilt = []
    i = 0
    pair_idx = 0
    while i < len(args) and pair_idx < len(pairs):
        if isinstance(args[i], bool):
            i += 1
            continue
        obj, name, universal_evalf = pairs[pair_idx]
        i += 1  # skip obj

        # Skip name
        if i < len(args) and isinstance(args[i], str):
            i += 1

        # Per-object bool (not the last arg) overrides universal
        if i < len(args) and isinstance(args[i], bool) and i != len(args) - 1:
            rebuilt.append((obj, name, args[i]))
            i += 1
        else:
            rebuilt.append((obj, name, universal_evalf))

        pair_idx += 1
    pairs = rebuilt

    # --- Step 4: Build LaTeX string ---
    parts = []
    for obj, name, local_evalf in pairs:
        processed = _prepare_object(obj, local_evalf)
        expr = latex(processed).replace(r'\\', r'\\[12pt]')
        expr = expr.replace(r'\frac', r'\dfrac')

        if mathrm:
            expr = rf"\mathrm{{{expr}}}"

        if name is not None:
            parts.append(rf"{name} = {expr}")
        else:
            parts.append(expr)

    # Join horizontally with spacing
    combined = r",\,{}".join(parts)
    ipy_display(Math(rf"\large {combined}"))