"""
Helper functions for building xOpat shader configurations.
Each function returns a shader params dict that can be passed to Mask().
"""


def heatmap(color="#fff705", opacity=1.0, threshold=0.0, inverse=False, channel="r"):
    """
    Encode data values in opacity - high value = strong color, low = transparent.
    
    Args:
        color: Hex color string, e.g. "#ff0000"
        opacity: Float 0.0-1.0
        threshold: Float 0.0-1.0, minimum value to show
        inverse: Bool, if True low values are shown instead
        channel: Which channel to read from the mask ("r", "g", "b", "a")
    """
    return {
        "type": "heatmap",
        "params": {
            "use_channel0": channel,
            "color": {"type": "color", "default": color},
            "opacity": {"type": "range", "default": opacity, "min": 0, "max": 1},
            "threshold": {"type": "range", "default": threshold, "min": 0, "max": 1},
            "inverse": {"type": "bool", "default": inverse},
        },
    }


def highlight(color="#000000", opacity=0.7, channel="r"):
    """
    Highlight high values, occlude everything else in color (default black).
    
    Args:
        color: Hex color string for the occlusion
        opacity: Float 0.0-1.0
        channel: Which channel to read from the mask
    """
    return {
        "type": "heatmap",
        "params": {
            "use_channel0": channel,
            "color": {"type": "color", "default": color},
            "inverse": {"type": "bool", "default": True},
            "opacity": {"type": "range", "default": opacity, "min": 0, "max": 1},
        },
    }


def classify(classes=3, colors=None, scheme="qualitative", opacity=1.0,
             modifiable=False, channel="r"):
    """
    Render discrete classes with distinct colors.
    
    Args:
        classes: Number of classes (int, 2-8)
        colors: Either None (auto), a colormap name string (e.g. "Accent"),
                or a list of hex colors (e.g. ["#ff0000", "#00ff00", "#0000ff"])
        scheme: Color scheme type - "qualitative", "sequential", "diverging"
        opacity: Float 0.0-1.0
        modifiable: Bool, allow user to change class ranges in UI
        channel: Which channel to read from the mask
    """
    if colors is None:
        color_conf = {
            "type": "colormap",
            "steps": classes,
            "default": "Accent",
            "mode": scheme,
            "interactive": True,
            "title": "Colormap",
            "continuous": False,
        }
    elif isinstance(colors, str):
        color_conf = {
            "type": "colormap",
            "steps": classes,
            "default": colors,
            "mode": scheme,
            "interactive": True,
            "title": "Colormap",
            "continuous": False,
        }
    elif isinstance(colors, list):
        classes = len(colors)
        color_conf = {
            "type": "custom_colormap",
            "default": colors,
            "steps": classes,
            "mode": scheme,
            "interactive": True,
            "title": "Colormap",
            "continuous": False,
        }
    else:
        raise ValueError("colors must be None, a string colormap name, or a list of hex colors")

    # compute break points for uniform class distribution
    break_step = 1.0 / float(classes - 1)
    breaks = [x * break_step - (break_step / 2.0) for x in range(1, classes)]
    masks = [1] * classes
    masks[0] = 0
    
    return {
        "type": "colormap",
        "params": {
            "use_channel0": channel,
            "color": color_conf,
            "connect": {"type": "bool", "interactive": modifiable, "default": True},
            "opacity": {"type": "range", "default": opacity, "min": 0, "max": 1},
            "threshold": {
                "breaks": breaks,
                "mask": masks,
                "interactive": modifiable,
                "inverted": True,
                "maskOnly": True,
                "toggleMask": True,
                "title": "Categories",
                "min": 0,
                "max": 1,
                "minGap": break_step / 1.5,
                "step": None,
                "pips": {
                    "mode": "count",
                    "values": classes,
                    "density": 4,
                    "stepped": True,
                },
            },
        },
    }


def bipolar(color_high="#ff0000", color_low="#00ff00", opacity=0.7,
            threshold=0.0, channel="r"):
    """
    Two-class visualization - high values one color, low values another.
    Useful for binary predictions (e.g. tumor vs normal).
    
    Args:
        color_high: Hex color for high values
        color_low: Hex color for low values
        opacity: Float 0.0-1.0
        threshold: Float 0.0-1.0
        channel: Which channel to read from the mask
    """
    return {
        "type": "bipolar-heatmap",
        "params": {
            "use_channel0": channel,
            "colorHigh": {"type": "color", "default": color_high},
            "colorLow": {"type": "color", "default": color_low},
            "inverse": {"type": "bool", "default": True},
            "opacity": {"type": "range", "default": opacity, "min": 0, "max": 1},
            "threshold": {"type": "range", "default": threshold, "min": 0, "max": 1},
        },
    }


def edge(color="#fa0058", thickness=1.0, opacity=1.0, threshold=0.5, channel="r"):
    """
    Highlight edges/boundaries at threshold values.
    
    Args:
        color: Hex color for edges
        thickness: Edge thickness
        opacity: Float 0.0-1.0
        threshold: Float 0.0-1.0, where to detect edges
        channel: Which channel to read from the mask
    """
    return {
        "type": "edge",
        "params": {
            "use_channel0": channel,
            "color": {"type": "color", "default": color},
            "edgeThickness": {"type": "range", "default": thickness, "min": 0, "max": 10},
            "opacity": {"type": "range", "default": opacity, "min": 0, "max": 1},
            "threshold": {"type": "range", "default": threshold, "min": 0, "max": 1},
        },
    }


def identity(opacity=1.0):
    """
    Show data as-is without any transformation.
    Expects a 4-channel (RGBA) image.
    
    Args:
        opacity: Float 0.0-1.0
    """
    return {
        "type": "identity",
        "params": {
            "opacity": {"type": "range", "default": opacity, "min": 0, "max": 1},
        },
    }