"""Morphological operations (opening, closing, gradient)

These operations are combinations of basic erosion and dilation:
- Opening: Erosion followed by dilation (removes noise)
- Closing: Dilation followed by erosion (fills holes)
- Gradient: Dilation minus erosion (edge detection)
"""

import cupy as cp

from .dilate import dilate
from .erode import erode


def morphology_open(
    image: cp.ndarray, ksize: int, kernel: cp.ndarray | None = None
) -> cp.ndarray:
    """Morphological opening (erosion followed by dilation)

    Opening removes small white noise while preserving large features.
    It also smooths object boundaries and separates barely touching objects.

    Parameters
    ----------
    image : cp.ndarray (float32)
        Input RGB image (HxWx3), value range [0, 1]
    ksize : int
        Kernel size
    kernel : cp.ndarray | None
        Structuring element (kernel). Binary 2D array

    Returns
    -------
    cp.ndarray
        RGB image after opening operation

    Notes
    -----
    Opening = Erosion → Dilation
    - Removes small white objects (noise)
    - Preserves large white objects
    - Smooths object boundaries
    - Separates touching objects
    """
    # Step 1: Erosion (shrinks white regions, removes noise)
    eroded = erode(image, ksize=ksize, kernel=kernel)

    # Step 2: Dilation (expands remaining white regions back)
    opened = dilate(eroded, ksize=ksize, kernel=kernel)

    return opened


def morphology_close(
    image: cp.ndarray, ksize: int, kernel: cp.ndarray | None = None
) -> cp.ndarray:
    """Morphological closing (dilation followed by erosion)

    Closing fills small black holes (pepper noise) while preserving large features.
    It also smooths object boundaries and connects nearby objects.

    Parameters
    ----------
    image : cp.ndarray (float32)
        Input RGB image (HxWx3), value range [0, 1]
    ksize : int
        Kernel size
    kernel : cp.ndarray | None
        Structuring element (kernel). Binary 2D array

    Returns
    -------
    cp.ndarray
        RGB image after closing operation

    Notes
    -----
    Closing = Dilation → Erosion
    - Fills small black holes (pepper noise)
    - Preserves large black regions
    - Smooths object boundaries
    - Connects nearby objects
    """
    # Step 1: Dilation (expands white regions, fills small holes)
    dilated = dilate(image, ksize=ksize, kernel=kernel)

    # Step 2: Erosion (shrinks white regions back)
    closed = erode(dilated, ksize=ksize, kernel=kernel)

    return closed


def morphology_gradient(
    image: cp.ndarray, ksize: int, kernel: cp.ndarray | None = None
) -> cp.ndarray:
    """Morphological gradient (dilation minus erosion)

    Gradient detects edges and boundaries of objects in the image.
    It highlights transitions between different intensity regions.

    Parameters
    ----------
    image : cp.ndarray (float32)
        Input RGB image (HxWx3), value range [0, 1]
    ksize : int
        Kernel size
    kernel : cp.ndarray | None
        Structuring element (kernel). Binary 2D array

    Returns
    -------
    cp.ndarray
        RGB image showing morphological gradient (edges)

    Notes
    -----
    Gradient = Dilation - Erosion
    - Detects object boundaries and edges
    - Highlights transitions between regions
    - Larger kernel produces thicker edge detection
    - Uniform regions have near-zero gradient
    """
    # Step 1: Dilation (expands white regions)
    dilated = dilate(image, ksize=ksize, kernel=kernel)

    # Step 2: Erosion (shrinks white regions)
    eroded = erode(image, ksize=ksize, kernel=kernel)

    # Step 3: Difference (edge detection)
    gradient = dilated - eroded

    return gradient
