"""Presentation operations -- manage presentation overlays on data fields."""

from __future__ import annotations

import numpy as np

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


@register_node(display_name="Presentation Ops")
class PresentationOps:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "field": ("DATA_FIELD",),
                "operation": (["logscale", "extract_presentation", "attach", "blend"],),
                "blend_factor": ("FLOAT", {
                    "default": 0.5,
                    "min": 0.0,
                    "max": 1.0,
                    "step": 0.01,
                    "show_when_widget_value": {"operation": ["blend"]},
                }),
            },
            "optional": {
                "overlay": ("DATA_FIELD", {
                    "show_when_widget_value": {"operation": ["attach", "blend"]},
                }),
            },
        }

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

    DESCRIPTION = (
        "Manage presentation overlays on data fields. "
        "logscale applies logarithmic scaling for visualising data with large dynamic range. "
        "extract_presentation normalises the field to [0, 1]. "
        "attach replaces the field data with an overlay (resampled if needed). "
        "blend linearly mixes the field and overlay by a configurable factor. "
        "Equivalent to Gwyddion's presentationops.c module."
    )

    def process(self, field: DataField, operation: str, blend_factor: float,
                overlay: DataField | None = None) -> tuple:
        data = np.asarray(field.data, dtype=np.float64)

        if operation == "logscale":
            data_pos = data - data.min() + 1e-30
            result = np.log10(data_pos)

        elif operation == "extract_presentation":
            dmin, dmax = data.min(), data.max()
            if dmax > dmin:
                result = (data - dmin) / (dmax - dmin)
            else:
                result = np.zeros_like(data)

        elif operation == "attach":
            if overlay is None:
                raise ValueError("'attach' operation requires an overlay field.")
            overlay_data = np.asarray(overlay.data, dtype=np.float64)
            result = self._match_shape(overlay_data, data.shape)

        elif operation == "blend":
            if overlay is None:
                raise ValueError("'blend' operation requires an overlay field.")
            overlay_data = np.asarray(overlay.data, dtype=np.float64)
            overlay_matched = self._match_shape(overlay_data, data.shape)
            result = (1.0 - blend_factor) * data + blend_factor * overlay_matched

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

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

    @staticmethod
    def _match_shape(source: np.ndarray, target_shape: tuple[int, ...]) -> np.ndarray:
        """Resample *source* to *target_shape* using scipy zoom if shapes differ."""
        if source.shape == target_shape:
            return source
        from scipy.ndimage import zoom
        factors = tuple(t / s for t, s in zip(target_shape, source.shape))
        return zoom(source, factors, order=3)
