"""Fractal interpolation — fill masked regions using fractal (self-similar) synthesis."""

from __future__ import annotations

import numpy as np

from backend.node_registry import register_node
from backend.data_types import DataField
from backend.nodes.helpers import mask_to_bool


@register_node(display_name="Fractal Interpolation")
class FractalInterpolation:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "field": ("DATA_FIELD",),
                "mask": ("IMAGE",),
                "iterations": ("INT", {"default": 200, "min": 10, "max": 5000, "step": 10}),
            }
        }

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

    DESCRIPTION = (
        "Fill masked regions using fractal interpolation. Matches the spectral "
        "characteristics of the surrounding surface to produce natural-looking "
        "infill that preserves texture. Better than Laplace for rough surfaces. "
    )

    def process(self, field: DataField, mask: np.ndarray, iterations: int) -> tuple:
        data = np.asarray(field.data, dtype=np.float64).copy()
        hole = mask_to_bool(mask)

        if not hole.any():
            return (field.replace(data=data),)

        # Step 1: Estimate power spectrum from valid (unmasked) data
        valid_data = data.copy()
        valid_mean = data[~hole].mean() if (~hole).any() else 0.0
        valid_data[hole] = valid_mean

        fft_valid = np.fft.fft2(valid_data)
        power = np.abs(fft_valid) ** 2

        # Step 2: Generate fractal noise matching the power spectrum
        rng = np.random.default_rng(42)
        phases = rng.uniform(0, 2 * np.pi, data.shape)
        noise_fft = np.sqrt(power) * np.exp(1j * phases)
        noise = np.real(np.fft.ifft2(noise_fft))

        # Normalize noise to match local statistics around masked region
        if (~hole).any():
            noise = (noise - noise[~hole].mean()) / max(noise[~hole].std(), 1e-30) * \
                    data[~hole].std() + data[~hole].mean()

        # Step 3: Initialize masked pixels with fractal noise, then blend
        # with Laplace relaxation for smooth boundaries
        data[hole] = noise[hole]

        # Relax boundaries to ensure continuity
        padded = np.pad(data, 1, mode='edge')
        hole_padded = np.pad(hole, 1, mode='constant', constant_values=False)

        for _ in range(iterations):
            avg = (padded[:-2, 1:-1] + padded[2:, 1:-1] +
                   padded[1:-1, :-2] + padded[1:-1, 2:]) / 4.0
            # Blend: 90% fractal noise + 10% relaxation to smooth boundaries
            blend = 0.1
            new_vals = (1.0 - blend) * padded[1:-1, 1:-1][hole] + blend * avg[hole]
            padded[1:-1, 1:-1][hole] = new_vals

        data = padded[1:-1, 1:-1].copy()
        return (field.replace(data=data),)
