/**
 * AIEO Star Ratings — Interactivity API path.
 *
 * Hydrates the existing DOM emitted by AIEO_Ratings::render_stars_html
 * (selectors `.aieo-rating-stars`, `.aieo-rating-star`,
 * `.aieo-rating-aggregate` — see CONTRACT.md). Routes through the
 * `aieo/ratings` IAPI store. Same DOM contract as the legacy jQuery
 * handler so a flag flip is the only difference.
 *
 * Loaded only when `aieo_dmm_ratings_iapi_enabled === 'yes'`.
 */

import { store } from '@wordpress/interactivity';

const ratingsStore = store( 'aieo/ratings' );

/* --------------------------------------------------------------------- */

function paintWidget( widget, value ) {
    const v = parseInt( value, 10 ) || 0;
    widget.setAttribute( 'data-aieo-rating-current', String( v ) );
    widget.querySelectorAll( '.aieo-rating-star' ).forEach( function ( s ) {
        const sv = parseInt( s.getAttribute( 'data-aieo-rating-value' ), 10 ) || 0;
        s.classList.toggle( 'is-filled', sv > 0 && sv <= v );
    } );
}

function paintAggregate( widget, avg, count, sum, current ) {
    widget.setAttribute( 'data-aieo-rating-avg',   String( avg   ) );
    widget.setAttribute( 'data-aieo-rating-count', String( count ) );
    if ( typeof sum === 'number' ) {
        widget.setAttribute( 'data-aieo-rating-sum', String( sum ) );
    }
    if ( typeof current === 'number' ) {
        widget.setAttribute( 'data-aieo-rating-current', String( current ) );
    }
    const cap = widget.querySelector( '.aieo-rating-caption' );
    const cfg = window.aieoRatings || {};
    if ( cap ) {
        const viewerCurrent = ( typeof current === 'number' )
            ? current
            : parseInt( widget.getAttribute( 'data-aieo-rating-current' ), 10 ) || 0;
        if ( viewerCurrent > 0 && count > 0 && cfg.captionFormat ) {
            cap.textContent = formatCaption( avg, count, cfg.captionFormat );
        } else {
            cap.textContent = cfg.captionBeforeRated || '';
        }
    }
    // Aggregate-only view: adjust the proportional fill width.
    const fg = widget.querySelector( '.aieo-rating-aggregate__fg' );
    if ( fg ) {
        const pct = Math.max( 0, Math.min( 100, ( ( parseFloat( avg ) || 0 ) / 5 ) * 100 ) );
        fg.style.width = pct + '%';
    }
}

/**
 * Seed the store's byProduct[] entry for a product from the DOM. Called on
 * load + after late-arriving cards mount. Skips ids already in the store
 * with a non-zero count so a fresh render doesn't clobber post-vote state.
 */
function seedFromDom( root ) {
    const store = ratingsStore.state.byProduct;
    ( root || document ).querySelectorAll(
        '.aieo-rating-stars[data-aieo-rating-product], .aieo-rating-aggregate[data-aieo-rating-product]'
    ).forEach( function ( el ) {
        const pid = parseInt( el.getAttribute( 'data-aieo-rating-product' ), 10 ) || 0;
        if ( pid <= 0 ) { return; }
        if ( store[ pid ] && store[ pid ].count > 0 ) { return; }
        const sum     = parseInt( el.getAttribute( 'data-aieo-rating-sum' ),   10 );
        const count   = parseInt( el.getAttribute( 'data-aieo-rating-count' ), 10 ) || 0;
        const avg     = parseFloat( el.getAttribute( 'data-aieo-rating-avg' ) ) || 0;
        const current = parseInt( el.getAttribute( 'data-aieo-rating-current' ), 10 ) || 0;
        store[ pid ] = {
            avg:            avg,
            count:          count,
            sum:            isNaN( sum ) ? Math.round( avg * count ) : sum,
            distribution:   ( store[ pid ] && store[ pid ].distribution ) || [0,0,0,0,0],
            current_rating: current
        };
    } );
}

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

function syncAllForProduct( pid, payload ) {
    document.querySelectorAll(
        '.aieo-rating-stars[data-aieo-rating-product="' + pid + '"]'
    ).forEach( function ( w ) {
        // First save flips the widget out of "show 5 until rated" mode
        // so the painter applies the real value instead of the placeholder.
        w.classList.remove( 'is-unrated-full' );
        paintWidget( w, payload.current_rating );
        paintAggregate( w, payload.avg, payload.count, payload.sum, payload.current_rating );
    } );
    document.querySelectorAll(
        '.aieo-rating-aggregate[data-aieo-rating-product="' + pid + '"]'
    ).forEach( function ( w ) {
        w.classList.remove( 'is-unrated-full' );
        paintAggregate( w, payload.avg, payload.count, payload.sum, payload.current_rating );
    } );
}

/* --------------------------------------------------------------------- */
/* Listeners                                                             */
/* --------------------------------------------------------------------- */

document.addEventListener( 'click', function ( ev ) {
    const star = ev.target.closest( '.aieo-rating-stars .aieo-rating-star' );
    if ( ! star ) { return; }
    const widget = star.closest( '.aieo-rating-stars' );
    if ( ! widget ) { return; }
    if ( widget.classList.contains( 'is-readonly' ) ) { return; }

    ev.preventDefault();
    ev.stopPropagation();

    const cfg     = window.aieoRatings || {};
    const pid     = parseInt( widget.getAttribute( 'data-aieo-rating-product' ), 10 ) || 0;
    const source  = widget.getAttribute( 'data-aieo-rating-source' ) || 'unknown';
    const value   = parseInt( star.getAttribute( 'data-aieo-rating-value' ), 10 ) || 0;
    const current = parseInt( widget.getAttribute( 'data-aieo-rating-current' ), 10 ) || 0;
    if ( pid <= 0 || value < 1 || value > 5 ) { return; }

    if ( cfg.requireLogin && ! cfg.isLoggedIn ) {
        if ( cfg.loginRedirectMode === 'event' ) {
            document.body.dispatchEvent( new CustomEvent( 'aieo:rating:login_required', {
                bubbles: true, detail: { productId: pid }
            } ) );
            return;
        }
        if ( cfg.loginUrl ) {
            window.location = cfg.loginUrl;
            return;
        }
    }

    // Make sure the store has the true prior aggregate seeded from the DOM
    // so the first click on a fresh page computes the new avg correctly
    // without waiting for a REST hydration.
    seedFromDom( widget );

    const newValue = ( cfg.allowClear && current === value ) ? 0 : value;

    // Kick off the save. The store mutates `byProduct[pid]` synchronously
    // BEFORE the await, so we can read the optimistic state right away and
    // paint without blocking on the network.
    const savePromise = ratingsStore.actions.save( pid, newValue, source );

    const optimistic = ratingsStore.state.byProduct[ pid ];
    if ( optimistic ) {
        syncAllForProduct( pid, optimistic );
    }

    // Reconcile after the server response (the store handles rollback on
    // error and re-aligns to authoritative values on success).
    savePromise.then( function ( result ) {
        const reconciled = ratingsStore.state.byProduct[ pid ];
        if ( reconciled ) { syncAllForProduct( pid, reconciled ); }
        if ( window.aieoIapiConfig && window.aieoIapiConfig.debug && result && result.error ) {
            /* eslint-disable no-console */
            console.warn( '[AIEO IAPI] rating save failed', result );
            /* eslint-enable no-console */
        }
    } );
}, true );

/* Multi-widget sync via the public event. */
document.body.addEventListener( 'aieo:rating:changed', function ( ev ) {
    const detail = ev.detail || {};
    const pid    = parseInt( detail.productId, 10 ) || 0;
    if ( pid <= 0 ) { return; }
    const row = ratingsStore.state.byProduct[ pid ];
    if ( ! row ) { return; }
    syncAllForProduct( pid, row );
} );

/* Initial fetch — one batch round-trip for every aggregate/widget on the page. */
function collectVisibleProductIds( root ) {
    const ids = new Set();
    ( root || document ).querySelectorAll(
        '.aieo-rating-stars[data-aieo-rating-product], .aieo-rating-aggregate[data-aieo-rating-product]'
    ).forEach( function ( el ) {
        const pid = parseInt( el.getAttribute( 'data-aieo-rating-product' ), 10 ) || 0;
        if ( pid > 0 ) { ids.add( pid ); }
    } );
    return Array.from( ids );
}

async function initialPaint( root ) {
    const ids = collectVisibleProductIds( root );
    if ( ids.length === 0 ) { return; }

    // DOM-first: every server-rendered widget already carries the true
    // sum/count/avg/current-rating in its data attrs. Seed the store from
    // there so first-click handlers have an exact prior — no roundtrip.
    seedFromDom( root );

    // Only fetch ids the DOM didn't seed (e.g. late-arriving widgets that
    // landed via JS-only render paths).
    const missing = ids.filter( function ( pid ) {
        const row = ratingsStore.state.byProduct[ pid ];
        return ! row || ( row.count === 0 && typeof row.sum !== 'number' );
    } );
    if ( missing.length > 0 ) {
        await ratingsStore.actions.fetch( missing );
    }
}

if ( document.readyState === 'loading' ) {
    document.addEventListener( 'DOMContentLoaded', function () { initialPaint(); } );
} else {
    initialPaint();
}

/* Late-arriving recommendation cards. */
document.body.addEventListener( 'aieo:reco:rendered', function ( ev ) {
    initialPaint( ( ev.detail && ev.detail.root ) || document );
} );

if ( window.aieoIapiConfig && window.aieoIapiConfig.debug ) {
    /* eslint-disable no-console */
    console.info( '[AIEO IAPI] ratings module loaded' );
    /* eslint-enable no-console */
}
