"""Shaded presentation — render surface with directional lighting."""

from __future__ import annotations

import numpy as np
from scipy.ndimage import sobel

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


@register_node(display_name="Shade")
class Shade:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "field": ("DATA_FIELD",),
                "azimuth": ("FLOAT", {"default": 315.0, "min": 0.0, "max": 360.0, "step": 1.0}),
                "elevation": ("FLOAT", {"default": 45.0, "min": 5.0, "max": 85.0, "step": 1.0}),
                "blend": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.05}),
            }
        }

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

    DESCRIPTION = (
        "Render a surface with directional hillshade lighting. "
        "Azimuth controls the light direction (0=north, 90=east). "
        "Elevation controls the light angle above the horizon. "
        "Blend mixes original data (0) with shaded relief (1). "
    )

    KEYWORDS = ("hillshade", "relief", "lighting", "lambertian", "render", "illumination")

    def process(self, field: DataField, azimuth: float, elevation: float,
                blend: float) -> tuple:
        data = np.asarray(field.data, dtype=np.float64)

        gx = sobel(data, axis=1) / (8.0 * field.dx)
        gy = sobel(data, axis=0) / (8.0 * field.dy)

        az_rad = np.radians(azimuth)
        el_rad = np.radians(elevation)

        # Lambertian shading
        lx = np.cos(el_rad) * np.sin(az_rad)
        ly = np.cos(el_rad) * np.cos(az_rad)
        lz = np.sin(el_rad)

        normal_z = 1.0 / np.sqrt(gx**2 + gy**2 + 1.0)
        normal_x = -gx * normal_z
        normal_y = -gy * normal_z

        shade = np.clip(normal_x * lx + normal_y * ly + normal_z * lz, 0.0, 1.0)

        # Normalize original data to [0, 1]
        dmin, dmax = data.min(), data.max()
        if dmax > dmin:
            norm_data = (data - dmin) / (dmax - dmin)
        else:
            norm_data = np.ones_like(data) * 0.5

        result = (1.0 - blend) * norm_data + blend * shade
        return (field.replace(data=result, si_unit_z=""),)
