import './bootstrap';

import Alpine from 'alpinejs';
import gsap from 'gsap';
import Swal from 'sweetalert2';
import Swiper from 'swiper';
import { Navigation, Pagination, EffectFade, Autoplay, Thumbs } from 'swiper/modules';

// Alpine powers small reactive UI (quantity selectors, dropdowns, cart
// drawer, tabs) without a full SPA framework — keeps Blade in charge.
window.Alpine = Alpine;
window.gsap = gsap;
window.Swal = Swal;
window.Swiper = Swiper;
window.SwiperModules = { Navigation, Pagination, EffectFade, Autoplay, Thumbs };

Alpine.start();

const prefersReducedMotion = () => window.matchMedia('(prefers-reduced-motion: reduce)').matches;

/**
 * Delegated so it works on every .btn everywhere (including ones added
 * later by Alpine-driven UI) without needing per-element re-binding.
 */
function initRippleEffect() {
    document.addEventListener('click', (event) => {
        if (prefersReducedMotion()) return;

        const btn = event.target.closest('.btn');
        if (! btn) return;

        const rect = btn.getBoundingClientRect();
        const size = Math.max(rect.width, rect.height);
        const ripple = document.createElement('span');
        ripple.className = 'btn-ripple';
        ripple.style.width = ripple.style.height = `${size}px`;
        ripple.style.left = `${event.clientX - rect.left - size / 2}px`;
        ripple.style.top = `${event.clientY - rect.top - size / 2}px`;
        btn.appendChild(ripple);
        ripple.addEventListener('animationend', () => ripple.remove());
    });
}

/**
 * Count-up on scroll into view. Usage: <span data-counter="1240"
 * data-counter-suffix="+">0</span>. Respects reduced motion by jumping
 * straight to the final value.
 */
function initAnimatedCounters() {
    const counters = document.querySelectorAll('[data-counter]');
    if (! counters.length) return;

    const finalValue = (el) => (el.dataset.counterPrefix || '') + Number(el.dataset.counter).toLocaleString() + (el.dataset.counterSuffix || '');

    if (prefersReducedMotion()) {
        counters.forEach((el) => { el.textContent = finalValue(el); });
        return;
    }

    const animate = (el) => {
        const target = Number(el.dataset.counter);
        const duration = 1200;
        const start = performance.now();

        const step = (now) => {
            const progress = Math.min((now - start) / duration, 1);
            const eased = 1 - Math.pow(1 - progress, 3);
            const value = Math.round(target * eased);
            el.textContent = (el.dataset.counterPrefix || '') + value.toLocaleString() + (el.dataset.counterSuffix || '');
            if (progress < 1) requestAnimationFrame(step);
        };
        requestAnimationFrame(step);
    };

    const observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
            if (entry.isIntersecting) {
                animate(entry.target);
                observer.unobserve(entry.target);
            }
        });
    }, { threshold: 0.4 });

    counters.forEach((el) => observer.observe(el));
}

/**
 * A restrained 3D tilt on product/category cards — desktop pointer only.
 * Rect is cached on enter (not re-read every mousemove, which would force
 * a synchronous layout on each event) and the style write is batched to
 * one per animation frame via rAF, so the compositor never falls behind
 * the mouse. `will-change` is toggled on enter/leave rather than left on
 * permanently — a permanent hint on every card in a grid just reserves
 * GPU layers nothing is using.
 */
function initTiltCards() {
    if (prefersReducedMotion() || window.matchMedia('(hover: none)').matches) return;

    document.querySelectorAll('.tilt-card').forEach((card) => {
        let rect = null;
        let frame = null;
        let pendingX = 0;
        let pendingY = 0;

        const apply = () => {
            frame = null;
            card.style.transform = `perspective(900px) rotateX(${(-pendingY * 5).toFixed(2)}deg) rotateY(${(pendingX * 5).toFixed(2)}deg) translateY(-4px)`;
        };

        card.addEventListener('mouseenter', () => {
            rect = card.getBoundingClientRect();
            card.style.willChange = 'transform';
        }, { passive: true });

        card.addEventListener('mousemove', (event) => {
            if (! rect) rect = card.getBoundingClientRect();
            pendingX = (event.clientX - rect.left) / rect.width - 0.5;
            pendingY = (event.clientY - rect.top) / rect.height - 0.5;
            if (frame === null) frame = requestAnimationFrame(apply);
        }, { passive: true });

        card.addEventListener('mouseleave', () => {
            if (frame !== null) { cancelAnimationFrame(frame); frame = null; }
            rect = null;
            card.style.transform = '';
            card.style.willChange = '';
        }, { passive: true });
    });
}

/**
 * Scroll-reveal for both repeated grid items (`[data-reveal]`, fades its
 * children with a CSS stagger — product/category cards, gallery tiles) and
 * single elements (`[data-reveal-self]` — section headings, hero captions,
 * anything that used to carry a `data-aos` attribute). One
 * IntersectionObserver for the whole page replaces AOS entirely: AOS kept
 * its own scroll+resize listener running for the page's whole lifetime,
 * re-measuring every tracked element's position with getBoundingClientRect
 * on every throttled tick — exactly the kind of per-scroll-frame JS work
 * that reads as stutter. IntersectionObserver does the same job without
 * running on the scroll thread at all.
 */
function initRevealGroups() {
    const targets = document.querySelectorAll('[data-reveal], [data-reveal-self]');
    if (! targets.length) return;

    if (prefersReducedMotion()) {
        targets.forEach((el) => el.classList.add('is-visible'));
        return;
    }

    const observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
            if (entry.isIntersecting) {
                entry.target.classList.add('is-visible');
                observer.unobserve(entry.target);
            }
        });
    }, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });

    targets.forEach((el) => observer.observe(el));
}

/**
 * Generic AJAX submit handler for forms marked `data-ajax="cart"` or
 * `data-ajax="wishlist"` — wishlist toggling, cart add/quantity/remove all
 * go through this instead of a full-page redirect, so the header badge
 * counts (and, on the cart page, the summary totals) update immediately.
 * Every one of these routes still returns a normal redirect for non-JS
 * requests (see the controllers) — this only changes the JS-enabled path.
 */
function initAjaxForms() {
    const toast = (icon, title) => {
        window.Swal?.fire({
            icon, title, toast: true, position: 'top-end', showConfirmButton: false,
            timer: icon === 'error' ? 3800 : 2500, timerProgressBar: true,
        });
    };

    // Re-adding a class that's already present doesn't restart its CSS
    // animation — a forced reflow (reading offsetWidth) between remove and
    // add is what makes the badge-pop/heartbeat replay on every toggle,
    // not just the first one.
    const replayAnimation = (el, className) => {
        el.classList.remove(className);
        void el.offsetWidth;
        el.classList.add(className);
    };

    const updateHeaderCounts = ({ cartCount, wishlistCount } = {}) => {
        if (cartCount !== undefined) {
            document.querySelectorAll('[data-cart-count]').forEach((el) => {
                el.textContent = Number(cartCount).toLocaleString();
                el.classList.toggle('hidden', ! cartCount);
                replayAnimation(el, 'badge-pop');
            });
        }
        if (wishlistCount !== undefined) {
            document.querySelectorAll('[data-wishlist-count]').forEach((el) => {
                el.textContent = Number(wishlistCount).toLocaleString();
                el.classList.toggle('hidden', ! wishlistCount);
                replayAnimation(el, 'badge-pop');
            });
        }
    };
    window.updateHeaderCounts = updateHeaderCounts;

    const patchCartTotals = (totals) => {
        if (! totals) return;

        const subtotalEl = document.getElementById('cart-subtotal');
        if (subtotalEl) subtotalEl.textContent = Number(totals.subtotal).toLocaleString();

        const discountRow = document.getElementById('cart-discount-row');
        discountRow?.classList.toggle('hidden', ! (totals.discount > 0));
        const discountEl = document.getElementById('cart-discount');
        if (discountEl) discountEl.textContent = Number(totals.discount).toLocaleString();

        const shippingEl = document.getElementById('cart-shipping');
        if (shippingEl) {
            const symbol = window.NIYARASA?.currencySymbol ?? '';
            shippingEl.innerHTML = totals.shipping > 0
                ? `${symbol} ${Number(totals.shipping).toLocaleString()}`
                : '<span class="text-success">Free</span>';
        }

        const grandTotalEl = document.getElementById('cart-grand-total');
        if (grandTotalEl) grandTotalEl.textContent = Number(totals.grand_total).toLocaleString();
    };

    document.addEventListener('submit', (event) => {
        const form = event.target.closest('form[data-ajax]');
        if (! form) return;

        event.preventDefault();
        const submitter = event.submitter;
        if (submitter) submitter.disabled = true;

        fetch(form.action, {
            method: 'POST', // real HTTP verb is spoofed via the form's own _method field, same as a native submit
            headers: { Accept: 'application/json' },
            body: new FormData(form),
        })
            .then(async (response) => {
                const data = await response.json().catch(() => ({}));

                if (! response.ok) {
                    toast('error', data.message || 'Something went wrong.');
                    return;
                }

                updateHeaderCounts(data);
                patchCartTotals(data.totals);

                if (data.item) {
                    form.closest('[data-cart-item]')?.querySelectorAll('[data-line-total]').forEach((el) => {
                        el.textContent = Number(data.item.lineTotal).toLocaleString();
                    });
                }

                if (data.action) {
                    const active = data.action === 'added';
                    form.querySelectorAll('.icon-heart').forEach((btn) => {
                        btn.classList.toggle('is-active', active);
                        btn.setAttribute('aria-label', active ? 'Remove from wishlist' : 'Add to wishlist');
                        btn.title = active ? 'Remove from wishlist' : 'Add to wishlist';
                        if (active) replayAnimation(btn, 'heartbeat');
                    });
                }

                if (data.message) toast('success', data.message);

                if (form.hasAttribute('data-remove-on-success')) {
                    const target = form.closest('[data-cart-item], [data-wishlist-item]');
                    if (target) {
                        target.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
                        target.style.opacity = '0';
                        target.style.transform = 'scale(0.96)';
                        setTimeout(() => {
                            target.remove();
                            // Whole list just emptied out — reload so the
                            // server-rendered empty state and pagination
                            // take over correctly instead of faking it in JS.
                            if (! document.querySelector('[data-cart-item], [data-wishlist-item]')) {
                                window.location.reload();
                            }
                        }, 300);
                    }
                }
            })
            .catch(() => toast('error', 'Network error — please try again.'))
            .finally(() => { if (submitter) submitter.disabled = false; });
    });
}

document.addEventListener('DOMContentLoaded', () => {
    initRippleEffect();
    initAnimatedCounters();
    initTiltCards();
    initRevealGroups();
    initAjaxForms();

    const { Navigation, Pagination, EffectFade, Autoplay } = SwiperModules;

    if (document.querySelector('.hero-slider')) {
        new Swiper('.hero-slider', {
            modules: [Navigation, Pagination, EffectFade, Autoplay],
            effect: 'fade',
            fadeEffect: { crossFade: true },
            loop: true,
            autoplay: { delay: 3800, disableOnInteraction: false, pauseOnMouseEnter: true },
            pagination: { el: '.hero-pagination', clickable: true },
            navigation: { nextEl: '.hero-next', prevEl: '.hero-prev' },
            grabCursor: true,
        });
    }

    if (document.querySelector('.reviews-slider')) {
        new Swiper('.reviews-slider', {
            modules: [Navigation, Pagination],
            slidesPerView: 1,
            spaceBetween: 24,
            pagination: { el: '.reviews-pagination', clickable: true },
            breakpoints: {
                768: { slidesPerView: 2 },
                1024: { slidesPerView: 3 },
            },
        });
    }

    if (document.querySelector('.promo-slider')) {
        new Swiper('.promo-slider', {
            modules: [Pagination, Autoplay],
            slidesPerView: 1.15,
            spaceBetween: 16,
            autoplay: { delay: 4000, disableOnInteraction: false },
            pagination: { el: '.promo-pagination', clickable: true },
            breakpoints: {
                640: { slidesPerView: 2, spaceBetween: 20 },
                1024: { slidesPerView: 2.2, spaceBetween: 24 },
            },
        });
    }
});
