"""Displacement field — distort images using displacement fields."""

from __future__ import annotations

import numpy as np
from scipy.ndimage import gaussian_filter, gaussian_filter1d, map_coordinates

from backend.node_registry import register_node
from backend.data_types import DataField


@register_node(display_name="Displacement Field")
class DisplacementField:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "field": ("DATA_FIELD",),
                "method": (["gaussian_1d", "gaussian_2d", "tear"], {"default": "gaussian_1d"}),
                "sigma": ("FLOAT", {"default": 5.0, "min": 0.1, "max": 100.0, "step": 0.1}),
                "tau": ("FLOAT", {"default": 20.0, "min": 1.0, "max": 500.0, "step": 1.0}),
                "density": ("FLOAT", {
                    "default": 0.02,
                    "min": 0.001,
                    "max": 0.25,
                    "step": 0.001,
                    "show_when_widget_value": {"method": ["tear"]},
                }),
                "seed": ("INT", {"default": 42, "min": 0, "max": 999999}),
            }
        }

    OUTPUTS = (
        ('DATA_FIELD', 'result'),
    )
    FUNCTION = "process"

    DESCRIPTION = (
        "Distort an image using synthetic displacement fields. "
        "Supports 1D Gaussian (row-correlated), 2D Gaussian (fully correlated), "
        "and tear (random horizontal tear lines) distortion modes. "
    )

    KEYWORDS = ("distortion", "warp", "tear")

    def process(
        self,
        field: DataField,
        method: str,
        sigma: float,
        tau: float,
        density: float,
        seed: int,
    ) -> tuple:
        data = np.asarray(field.data, dtype=np.float64)
        yres, xres = data.shape
        rng = np.random.default_rng(seed)

        y_grid, x_grid = np.mgrid[:yres, :xres].astype(np.float64)

        if method == "gaussian_1d":
            dx_1d = gaussian_filter1d(rng.standard_normal(xres), tau) * sigma
            dx = np.tile(dx_1d, (yres, 1))
            dy = np.zeros_like(dx)

        elif method == "gaussian_2d":
            dx = gaussian_filter(rng.standard_normal((yres, xres)), tau) * sigma
            dy = gaussian_filter(rng.standard_normal((yres, xres)), tau) * sigma

        elif method == "tear":
            dx = np.zeros((yres, xres), dtype=np.float64)
            dy = np.zeros((yres, xres), dtype=np.float64)

            # Select tear rows based on density
            tear_mask = rng.random(yres) < density
            tear_rows = np.nonzero(tear_mask)[0]

            for row in tear_rows:
                offset = rng.standard_normal() * sigma
                # Apply offset that decays exponentially away from the tear line
                for i in range(yres):
                    dist = abs(i - row)
                    dx[i] += offset * np.exp(-dist / max(tau, 1.0))

            # Smooth the displacement to avoid sharp edges
            for i in range(yres):
                dx[i] = gaussian_filter1d(dx[i], tau / 4.0)

        else:
            raise ValueError(f"Unknown method: {method!r}")

        coords_y = y_grid + dy
        coords_x = x_grid + dx
        result = map_coordinates(data, [coords_y, coords_x], mode='reflect', order=3)

        return (field.replace(data=result),)
