"""Lateral Force Simulation — topography artifacts in friction channels."""

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="Lateral Force Simulation")
class LateralForceSim:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "field": ("DATA_FIELD",),
                "direction": (["forward", "reverse", "both"],),
                "friction_coefficient": ("FLOAT", {
                    "default": 0.3, "min": 0.0, "max": 10.0, "step": 0.01,
                }),
                "adhesion": ("FLOAT", {
                    "default": 1e-9, "min": 0.0, "max": 1e-6, "step": 1e-12,
                }),
                "load": ("FLOAT", {
                    "default": 10e-9, "min": 1e-12, "max": 1e-6, "step": 1e-12,
                }),
            }
        }

    OUTPUTS = (
        ('DATA_FIELD', 'forward'),
        ('DATA_FIELD', 'reverse'),
    )
    FUNCTION = "process"

    DESCRIPTION = (
        "Simulates topography-induced artifacts in lateral force (friction) "
        "microscopy channels. Computes the lateral force signal from surface "
        "slope, friction coefficient, adhesion, and normal load for forward "
        "and/or reverse scan directions. Based on a contact-mechanics model "
        "where the measured lateral force depends on the local tilt angle of "
        "the sample surface."
    )

    KEYWORDS = ("lfm", "friction", "ffm", "tribology")

    def process(
        self,
        field: DataField,
        direction: str,
        friction_coefficient: float,
        adhesion: float,
        load: float,
    ) -> tuple:
        data = np.asarray(field.data, dtype=np.float64)

        # Surface slope angle from x-gradient of topography
        dz_dx = np.gradient(data, axis=1)
        theta = np.arctan2(dz_dx, field.dx)

        sin_theta = np.sin(theta)
        cos_theta = np.cos(theta)

        va = load * sin_theta
        vc = cos_theta
        vb = friction_coefficient * (load * vc + adhesion)
        vd = friction_coefficient * sin_theta

        # Forward scan (tip moves in +x)
        denom_fwd = vc - vd
        safe_fwd = np.abs(denom_fwd) >= 1e-30
        lateral_forward = np.where(safe_fwd, (va + vb) / np.where(safe_fwd, denom_fwd, 1.0), 0.0)

        # Reverse scan (tip moves in -x)
        denom_rev = vc + vd
        safe_rev = np.abs(denom_rev) >= 1e-30
        lateral_reverse = np.where(safe_rev, -(va - vb) / np.where(safe_rev, denom_rev, 1.0), 0.0)

        if direction == "forward":
            lateral_reverse = lateral_forward.copy()
        elif direction == "reverse":
            lateral_forward = lateral_reverse.copy()

        out_fwd = field.replace(data=lateral_forward, si_unit_z="N")
        out_rev = field.replace(data=lateral_reverse, si_unit_z="N")

        return (out_fwd, out_rev)
