/**
 * Sifency Light Beam Widget — Frontend JS v1.2
 */
(function ($) {
    'use strict';

    /* ── Helpers ──────────────────────────────────────────────────────────── */
    function lerp(a, b, t) { return a + (b - a) * t; }
    function clamp(v, mn, mx) { return Math.min(Math.max(v, mn), mx); }

    function debounce(fn, ms) {
        var timer;
        return function () {
            clearTimeout(timer);
            timer = setTimeout(fn, ms);
        };
    }

    /* ── Walk up the DOM to find the containing Elementor section ─────────── */
    function getSectionEl(startEl) {
        var el = startEl.parentElement;
        while (el && el !== document.body) {
            if (
                el.classList.contains('elementor-section') ||
                el.classList.contains('e-con') ||
                el.classList.contains('elementor-container') ||
                el.tagName === 'SECTION'
            ) {
                return el;
            }
            el = el.parentElement;
        }
        return startEl.parentElement;
    }

    /* ── Main Init ───────────────────────────────────────────────────────── */
    function initLightBeam($scope) {
        var widgetEl = $scope[0];
        if (!widgetEl) { return; }

        var wrapper = widgetEl.querySelector('.slb-wrapper');
        if (!wrapper) { return; }

        var beam = wrapper.querySelector('.slb-beam');
        if (!beam) { return; }

        if (typeof gsap === 'undefined') {
            console.warn('[Sifency Light Beam] GSAP not loaded.');
            return;
        }

        /* ── Parse config ─────────────────────────────────────────── */
        var raw = wrapper.dataset.config || '{}';
        var cfg;
        try { cfg = JSON.parse(raw); } catch (e) { cfg = {}; }

        var anchor = cfg.anchor || 'top-left';
        var baseAngle = typeof cfg.baseAngle === 'number' ? cfg.baseAngle : 135;

        /* ── Reparent wrapper into the section ────────────────────── */
        var section = getSectionEl(widgetEl);

        // Make section a positioned ancestor so absolute children work
        if (window.getComputedStyle(section).position === 'static') {
            section.style.position = 'relative';
        }

        section.appendChild(wrapper);
        wrapper.style.cssText = [
            'position:absolute',
            'top:0',
            'left:0',
            'width:100%',
            'height:100%',
            'pointer-events:none',
        ].join(';');

        /* ── GSAP: set transform-origin at beam tip (0,0) ─────────── */
        gsap.set(beam, { transformOrigin: '0px 0px' });

        /* ── State ────────────────────────────────────────────────── */
        var state = {
            scrollAngle: 0,
            mouseAngle: 0,
            targetMouse: 0,
            rafId: null,
        };

        function applyRotation() {
            var total = baseAngle + state.scrollAngle + state.mouseAngle;
            gsap.set(beam, { rotation: total });
        }

        applyRotation();

        var cleanups = [];

        /* ── Scroll Interaction ───────────────────────────────────── */
        if (cfg.scrollEnabled) {
            if (typeof ScrollTrigger === 'undefined') {
                console.warn('[Sifency Light Beam] ScrollTrigger not loaded — scroll disabled.');
            } else {
                var scrollFrom = typeof cfg.scrollStartAngle === 'number' ? cfg.scrollStartAngle : -20;
                var scrollTo = typeof cfg.scrollEndAngle === 'number' ? cfg.scrollEndAngle : 40;
                var scrub = typeof cfg.scrollScrub === 'number' ? cfg.scrollScrub : 1;
                var trigStart = cfg.scrollTriggerStart || 'top 90%';
                var trigEnd = cfg.scrollTriggerEnd || 'bottom 10%';

                var st = ScrollTrigger.create({
                    trigger: section,
                    start: trigStart,
                    end: trigEnd,
                    scrub: scrub,
                    onUpdate: function (self) {
                        state.scrollAngle = lerp(scrollFrom, scrollTo, self.progress);
                        applyRotation();
                    },
                });
                cleanups.push(function () { st.kill(); });

                if (cfg.scrollOpacityEnabled) {
                    var opFrom = typeof cfg.scrollOpacityFrom === 'number' ? cfg.scrollOpacityFrom : 0;
                    var opTo = typeof cfg.scrollOpacityTo === 'number' ? cfg.scrollOpacityTo : 1;

                    var stOp = ScrollTrigger.create({
                        trigger: section,
                        start: trigStart,
                        end: trigEnd,
                        scrub: scrub,
                        onUpdate: function (self) {
                            gsap.set(beam, { opacity: lerp(opFrom, opTo, self.progress) });
                        },
                    });
                    cleanups.push(function () { stOp.kill(); });
                }
            }
        }

        /* ── Mouse Interaction ────────────────────────────────────── */
        if (cfg.mouseEnabled) {
            var intensity = typeof cfg.mouseIntensity === 'number' ? cfg.mouseIntensity : 60;
            var easeSpeed = typeof cfg.mouseEase === 'number' ? cfg.mouseEase : 0.15;
            var invert = cfg.mouseInvert === true;
            var resetOnLeave = cfg.mouseResetOnLeave !== false;
            var scope = cfg.mouseScope || 'window';
            var dir = invert ? -1 : 1;

            /* Get the anchor point in viewport coordinates ────────── */
            function getAnchorViewport() {
                var r = wrapper.getBoundingClientRect();
                var ax, ay;

                // Horizontal
                if (anchor.indexOf('center') !== -1) {
                    ax = r.left + r.width * 0.5;
                } else if (anchor.indexOf('right') !== -1) {
                    ax = r.right;
                } else {
                    ax = r.left;
                }

                // Vertical
                if (anchor.indexOf('middle') !== -1) {
                    ay = r.top + r.height * 0.5;
                } else if (anchor.indexOf('bottom') !== -1) {
                    ay = r.bottom;
                } else {
                    ay = r.top;
                }

                return { x: ax, y: ay };
            }

            /*
             * Mouse → rotation delta
             *
             * We compute the true angle FROM the anchor point TO the mouse
             * using atan2, convert to degrees, then offset it against the
             * baseAngle so that at rest (mouse at anchor) the delta is 0.
             * The result is clamped to ±intensity.
             *
             * This replaces the old formula that double-multiplied by both
             * mag and mouseDeg, producing a nonlinear and erratic response.
             */
            function onMouseMove(e) {
                var ap = getAnchorViewport();
                var dx = e.clientX - ap.x;
                var dy = e.clientY - ap.y;

                // True angle from anchor to cursor, in degrees
                var angleDeg = Math.atan2(dy, dx) * (180 / Math.PI);

                // Offset relative to baseAngle so delta is 0 at rest
                var delta = angleDeg - baseAngle;

                // Normalise to [-180, 180]
                while (delta > 180) { delta -= 360; }
                while (delta < -180) { delta += 360; }

                // Clamp to ±intensity and apply invert
                state.targetMouse = dir * clamp(delta, -intensity, intensity);
            }

            function onMouseLeave() {
                if (resetOnLeave) { state.targetMouse = 0; }
            }

            /* RAF lerp loop — smooths mouse movement ─────────────── */
            function rafLoop() {
                // Clamp easeSpeed so lerp factor stays in (0, 1]
                var lerpT = clamp(easeSpeed, 0.01, 1);
                state.mouseAngle = lerp(state.mouseAngle, state.targetMouse, lerpT);
                applyRotation();
                state.rafId = requestAnimationFrame(rafLoop);
            }
            state.rafId = requestAnimationFrame(rafLoop);

            /* Bind to the correct element ────────────────────────── */
            var trackEl;
            if (scope === 'window') {
                trackEl = window;
            } else if (scope === 'parent') {
                trackEl = section;
            } else {
                trackEl = wrapper;
            }

            $(trackEl).on('mousemove.slb', onMouseMove);
            $(trackEl).on('mouseleave.slb', onMouseLeave);

            cleanups.push(function () {
                if (state.rafId) { cancelAnimationFrame(state.rafId); state.rafId = null; }
                $(trackEl).off('mousemove.slb mouseleave.slb');
            });
        }

        /* ── ResizeObserver — keep ScrollTrigger accurate ─────────── */
        if (typeof ResizeObserver !== 'undefined') {
            var onResize = debounce(function () {
                if (typeof ScrollTrigger !== 'undefined') {
                    ScrollTrigger.refresh();
                }
            }, 150);

            var ro = new ResizeObserver(onResize);
            ro.observe(section);
            cleanups.push(function () { ro.disconnect(); });
        }

        /* ── Cleanup hook for Elementor editor live reload ─────────── */
        if (wrapper.__slbCleanup) { wrapper.__slbCleanup(); }
        wrapper.__slbCleanup = function () {
            cleanups.forEach(function (fn) { fn(); });
            cleanups = [];
            if (wrapper.parentElement === section) {
                widgetEl.appendChild(wrapper);
            }
        };
    }

    /* ── Elementor Hook ──────────────────────────────────────────────────── */
    $(window).on('elementor/frontend/init', function () {
        elementorFrontend.hooks.addAction(
            'frontend/element_ready/sifency-light-beam.default',
            initLightBeam
        );
    });

})(jQuery);