/**
 * AIEO Star Ratings — legacy jQuery handler (default fallback path).
 *
 * Targets buttons rendered by AIEO_Ratings::render_stars_html on archive,
 * single-product, and shortcode surfaces (selectors `.aieo-rating-stars`,
 * `.aieo-rating-star`, `.aieo-rating-aggregate`). Posts to
 * `aieo_rating_save`, paints optimistically, rolls back on error, syncs
 * every widget on the page bound to the same product, and dispatches the
 * public `aieo:rating:changed` / `aieo:rating:login_required` events.
 *
 * Loaded when `aieo_dmm_ratings_iapi_enabled !== 'yes'`. The IAPI module
 * (aieo-rating-stars-iapi.js) uses the exact same DOM contract.
 */
(function ($) {
    'use strict';
    if (typeof window.aieoRatings === 'undefined') { return; }
    var CFG = window.aieoRatings;

    function dispatch(name, detail) {
        if (window.aieo && window.aieo.iapi && window.aieo.iapi.dispatch) {
            window.aieo.iapi.dispatch(name, detail || {});
            return;
        }
        try {
            document.body.dispatchEvent(new CustomEvent(name, {
                bubbles: true, cancelable: false, detail: detail || {}
            }));
        } catch (e) { /* swallow */ }
    }

    function paintWidget($widget, value) {
        var v = parseInt(value, 10) || 0;
        $widget.attr('data-aieo-rating-current', String(v));
        $widget.find('.aieo-rating-star').each(function () {
            var sv = parseInt($(this).attr('data-aieo-rating-value'), 10) || 0;
            $(this).toggleClass('is-filled', sv > 0 && sv <= v);
        });
    }

    function paintAggregate($widget, avg, count, sum, current) {
        $widget.attr('data-aieo-rating-avg', String(avg));
        $widget.attr('data-aieo-rating-count', String(count));
        if (typeof sum === 'number') {
            $widget.attr('data-aieo-rating-sum', String(sum));
        }
        if (typeof current === 'number') {
            $widget.attr('data-aieo-rating-current', String(current));
        }
        // Caption rule: prompt ("Give your vote …") before the viewer has
        // rated; avg/count format after. Both strings come from CFG.
        var viewerCurrent = (typeof current === 'number')
            ? current
            : parseInt($widget.attr('data-aieo-rating-current'), 10) || 0;
        var $caption = $widget.find('.aieo-rating-caption');
        if ($caption.length) {
            if (viewerCurrent > 0 && count > 0 && CFG.captionFormat) {
                $caption.text(formatCaption(avg, count));
            } else {
                $caption.text(CFG.captionBeforeRated || '');
            }
        }
    }

    /**
     * Compute the new aggregate locally from the prior (sum,count) and the
     * vote delta. Mirrors the server's atomic stats math, so the optimistic
     * paint is identical to what the DB will hold after the async save.
     */
    function computeOptimistic(prevSum, prevCount, prev, next) {
        var countDelta = (next > 0 ? 1 : 0) - (prev > 0 ? 1 : 0);
        var sumDelta   = next - prev;
        var newCount   = Math.max(0, prevCount + countDelta);
        var newSum     = Math.max(0, prevSum + sumDelta);
        return {
            count: newCount,
            sum:   newSum,
            avg:   newCount > 0 ? Math.round((newSum / newCount) * 10) / 10 : 0
        };
    }

    function formatCaption(avg, count) {
        var fmt = CFG.captionFormat || '%avg (%count)';
        var avgStr = (Math.round((parseFloat(avg) || 0) * 10) / 10).toFixed(1);
        return fmt
            .replace('%avg5',  avgStr + '/5')
            .replace('%avg',   avgStr)
            .replace('%count', String(parseInt(count, 10) || 0));
    }

    function syncAllWidgetsForProduct(pid, payload) {
        $('.aieo-rating-stars[data-aieo-rating-product="' + pid + '"]').each(function () {
            // First save flips the widget out of "show 5 until rated" mode
            // so the painter applies the real value instead of the placeholder.
            $(this).removeClass('is-unrated-full');
            paintWidget($(this), payload.current_rating);
            paintAggregate($(this), payload.avg, payload.count, payload.sum, payload.current_rating);
        });
        $('.aieo-rating-aggregate[data-aieo-rating-product="' + pid + '"]').each(function () {
            $(this).removeClass('is-unrated-full');
            paintAggregate($(this), payload.avg, payload.count, payload.sum, payload.current_rating);
            // Adjust the proportional fill width in the half-star aggregate.
            var pct = Math.max(0, Math.min(100, ((parseFloat(payload.avg) || 0) / 5) * 100));
            $(this).find('.aieo-rating-aggregate__fg').css('width', pct + '%');
        });
    }

    $(document).on('click', '.aieo-rating-stars .aieo-rating-star', function (e) {
        e.preventDefault();
        e.stopPropagation();

        var $star    = $(this);
        var $widget  = $star.closest('.aieo-rating-stars');
        if (!$widget.length || $widget.hasClass('is-loading') || $widget.hasClass('is-readonly')) { return; }

        var pid     = parseInt($widget.attr('data-aieo-rating-product'), 10) || 0;
        var source  = $widget.attr('data-aieo-rating-source') || 'unknown';
        var value   = parseInt($star.attr('data-aieo-rating-value'), 10) || 0;
        var current = parseInt($widget.attr('data-aieo-rating-current'), 10) || 0;
        if (pid <= 0 || value < 1 || value > 5) { return; }

        // Login gate (client-side fast-path; server still enforces).
        if (CFG.requireLogin && !CFG.isLoggedIn) {
            if (CFG.loginRedirectMode === 'event') {
                dispatch('aieo:rating:login_required', { productId: pid });
                return;
            }
            if (CFG.loginUrl) {
                window.location = CFG.loginUrl;
                return;
            }
        }

        // Re-clicking the active star clears the rating (when allowed).
        var newValue = (CFG.allowClear && current === value) ? 0 : value;

        var prev = current;

        // Read the TRUE prior aggregate from the data attrs the PHP renderer
        // emitted. These are the exact integer sum + count, not the rounded
        // avg, so the optimistic math matches what the DB will hold.
        var trueSum   = parseInt($widget.attr('data-aieo-rating-sum'),   10) || 0;
        var trueCount = parseInt($widget.attr('data-aieo-rating-count'), 10) || 0;
        var trueAvg   = parseFloat($widget.attr('data-aieo-rating-avg')) || 0;

        // Compute the new aggregate locally — same math the server runs.
        var optimistic = computeOptimistic(trueSum, trueCount, prev, newValue);

        // Paint everything immediately: stars, caption, aggregate fills,
        // every other widget for the same product on the page. No waiting.
        syncAllWidgetsForProduct(pid, {
            current_rating: newValue,
            avg:            optimistic.avg,
            count:          optimistic.count,
            sum:            optimistic.sum
        });
        dispatch('aieo:rating:changed', {
            productId: pid,
            rating:    newValue,
            previous:  prev,
            avg:       optimistic.avg,
            count:     optimistic.count,
            source:    source
        });

        // Async fire-and-forget save. Server response is authoritative — if
        // it disagrees with our optimistic math (e.g. another vote landed in
        // the meantime, or the user was rate-limited), reconcile silently.
        $.post(CFG.ajaxUrl, {
            action:     'aieo_rating_save',
            nonce:      CFG.nonce,
            product_id: pid,
            rating:     newValue,
            source:     source
        }).done(function (resp) {
            if (!resp || !resp.success) {
                // Roll back to the prior state.
                syncAllWidgetsForProduct(pid, {
                    current_rating: prev,
                    avg:            trueAvg,
                    count:          trueCount,
                    sum:            trueSum
                });
                if (resp && resp.data && resp.data.login_required) {
                    if (CFG.loginRedirectMode === 'event') {
                        dispatch('aieo:rating:login_required', { productId: pid });
                    } else if (resp.data.login_url) {
                        window.location = resp.data.login_url;
                    }
                }
                return;
            }
            // Reconcile: only repaint if the server's authoritative aggregate
            // diverges from our local computation (rare — only on race).
            var d = resp.data || {};
            if (typeof d.avg   === 'number' && d.avg   !== optimistic.avg ||
                typeof d.count === 'number' && d.count !== optimistic.count) {
                syncAllWidgetsForProduct(pid, {
                    current_rating: typeof d.current_rating === 'number' ? d.current_rating : newValue,
                    avg:            typeof d.avg   === 'number' ? d.avg   : optimistic.avg,
                    count:          typeof d.count === 'number' ? d.count : optimistic.count,
                    sum:            typeof d.sum   === 'number' ? d.sum   : optimistic.sum
                });
            }
        }).fail(function () {
            // Network error — roll back.
            syncAllWidgetsForProduct(pid, {
                current_rating: prev,
                avg:            trueAvg,
                count:          trueCount,
                sum:            trueSum
            });
        });
    });
})(jQuery);
