/**
 * AffiliateWP — List-table row actions
 *
 * Shared behavior for the unified Referrals/Affiliates action column:
 *
 *   1. Whole-row click navigates to the row's edit screen
 *      (`tr[data-edit-url]`). Clicks on interactive controls, the actions
 *      column, the checkbox, or an active text selection are ignored.
 *      Modifier-click opens the edit screen in a new tab. This handler is
 *      intentionally thin so it can be repointed at a drawer later.
 *
 *   2. The hover-rail icons (`[data-affwp-ajax]`) fire an in-place status
 *      change. The endpoint returns the re-rendered status + actions cells
 *      and we swap both, so the server stays the source of truth for which
 *      actions are valid at the new status. The icons are real nonced links,
 *      so without this script they still work via full page load.
 *
 * Native HTML Popover handles the kebab open/close; affwp-popover-anchor.js
 * (delegated, covers swapped-in markup) handles positioning.
 *
 * @package affiliate-wp
 * @since   2.33.0
 */
( function () {
	'use strict';

	if ( window.__affwpListRowActionsInit ) {
		return;
	}
	window.__affwpListRowActionsInit = true;

	var data = window.affwpListRowActions || {};
	var IGNORE = 'a, button, input, select, textarea, label, [popover], .affwp-ui, .column-actions, .check-column';

	/*
	 * Name-column reveal tooltips. The Name cell caps long display names (often an
	 * email used as the name) with a CSS ellipsis. Attach the dark reveal tooltip
	 * ONLY to names that actually overflow — a server-side length guess can't tell
	 * narrow text that fits from wide text that clips. The styled content rides in
	 * data-truncate-tooltip so the global tooltip auto-init (which scans
	 * data-tooltip-html) leaves these alone until we measure scrollWidth here.
	 */
	function bindTruncatedNameTooltips() {
		if ( ! window.affwpTooltips || typeof window.affwpTooltips.createTooltipInstance !== 'function' ) {
			return;
		}
		document.querySelectorAll( 'td.column-name span[data-truncate-tooltip]:not([data-tippy-processed])' ).forEach( function ( el ) {
			if ( el.scrollWidth > el.clientWidth ) {
				window.affwpTooltips.createTooltipInstance( el, el.getAttribute( 'data-truncate-tooltip' ) );
			}
		} );
	}

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

	/*
	 * Bulk-pay result toast.
	 *
	 * buildBulkPayToast() turns the server's paid/processing/failed tallies into a single
	 * state-aware { message, type }. It's composed at submit time (labels + numbers in hand),
	 * stashed in sessionStorage, then shown once after the list reloads.
	 */
	function buildBulkPayToast( d ) {
		const labels = data.labels || {};
		const paid = parseInt( d.paid, 10 ) || 0;
		const processing = parseInt( d.processing, 10 ) || 0;
		const failed = parseInt( d.failed, 10 ) || 0;

		const parts = [];
		if ( paid > 0 ) {
			parts.push( ( paid === 1 ? labels.toastPaidSingular : labels.toastPaidPlural || '' ).replace( '%d', paid ) );
		}
		if ( processing > 0 && labels.toastProcessingClause ) {
			parts.push( labels.toastProcessingClause.replace( '%d', processing ) );
		}
		if ( failed > 0 && labels.toastFailedClause ) {
			parts.push( labels.toastFailedClause.replace( '%d', failed ) );
		}
		if ( ! parts.length ) {
			return null;
		}

		return { message: parts.join( ' ' ), type: failed > 0 ? 'error' : 'success' };
	}

	// Shared with the bulk-pay drawer store so both compose the result toast identically.
	window.affwpBuildBulkPayToast = buildBulkPayToast;

	function showToast( toast ) {
		if ( ! toast || ! toast.message ) {
			return;
		}
		if ( typeof window.affwpToast === 'function' ) {
			window.affwpToast( toast );
		} else {
			window.dispatchEvent( new CustomEvent( 'affwp-toast', { detail: toast } ) );
		}
	}

	// Show (once) any bulk-pay toast flashed across the reload.
	( function () {
		let raw;
		try {
			raw = window.sessionStorage.getItem( 'affwp_bulk_pay_result' );
			if ( raw ) {
				window.sessionStorage.removeItem( 'affwp_bulk_pay_result' );
			}
		} catch ( e ) {
			return;
		}
		if ( ! raw ) {
			return;
		}
		let toast;
		try {
			toast = JSON.parse( raw );
		} catch ( e ) {
			return;
		}
		if ( document.readyState === 'loading' ) {
			document.addEventListener( 'DOMContentLoaded', () => showToast( toast ) );
		} else {
			showToast( toast );
		}
	} )();

	/*
	 * AJAX status toggle. Capture phase + stopPropagation so it runs before
	 * (and suppresses) the row-click navigation below.
	 */
	document.addEventListener( 'click', function ( e ) {
		var trigger = e.target.closest && e.target.closest( '[data-affwp-ajax]' );
		if ( ! trigger ) {
			return;
		}

		e.preventDefault();
		e.stopPropagation();

		if ( 'true' === trigger.getAttribute( 'aria-busy' ) ) {
			return;
		}

		var entity = trigger.getAttribute( 'data-affwp-ajax' );
		var nonce  = ( data.nonces || {} )[ entity ];

		// No localized nonce/URL: fall back to the link's full-reload behavior.
		if ( ! nonce || ! data.ajaxUrl ) {
			if ( trigger.href ) {
				window.location.href = trigger.href;
			}
			return;
		}

		var row  = trigger.closest( 'tr' );
		var body = new URLSearchParams();
		body.set( 'action', 'affwp_set_' + entity + '_status' );
		body.set( 'nonce', nonce );
		body.set( entity + '_id', trigger.getAttribute( 'data-affwp-id' ) );
		body.set( 'status', trigger.getAttribute( 'data-affwp-status' ) );
		body.set( 'list_url', window.location.href );

		trigger.setAttribute( 'aria-busy', 'true' );
		trigger.style.opacity = '0.5';

		fetch( data.ajaxUrl, {
			method: 'POST',
			credentials: 'same-origin',
			headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
			body: body.toString()
		} )
			.then( function ( r ) {
				return r.json();
			} )
			.then( function ( res ) {
				if ( ! res || ! res.success || ! res.data ) {
					throw new Error( ( res && res.data && res.data.message ) || ( data.labels && data.labels.error ) );
				}
				if ( row ) {
					var statusCell  = row.querySelector( '.column-status' );
					var actionsCell = row.querySelector( '.column-actions' );
					if ( statusCell && res.data.status_html ) {
						statusCell.innerHTML = res.data.status_html;
					}
					// Replacing innerHTML discards the clicked icon, so no aria-busy reset is needed here.
					if ( actionsCell && res.data.actions_html ) {
						actionsCell.innerHTML = res.data.actions_html;

						// Re-bind tooltips on the swapped-in icons. tippy skips already-processed nodes, so only new ones are touched.
						if ( window.affwpTooltips && typeof window.affwpTooltips.createTooltipInstance === 'function' ) {
							actionsCell.querySelectorAll( '[data-tooltip-html]:not([data-tippy-processed])' ).forEach( function ( el ) {
								window.affwpTooltips.createTooltipInstance( el, el.getAttribute( 'data-tooltip-html' ) );
							} );
						}
					}
				}
			} )
			.catch( function ( err ) {
				trigger.setAttribute( 'aria-busy', 'false' );
				trigger.style.opacity = '';
				window.alert( ( err && err.message ) || ( data.labels && data.labels.networkError ) || 'Error' );
			} );
	}, true );

	/*
	 * "Pay" rail action → open the confirm modal (Alpine modal store), fetch the
	 * method-resolved details, then pay via the dispatcher on confirm and swap the
	 * status + actions cells. Falls back to the link (the referral) without JS/Alpine.
	 */
	var MODAL_ID  = 'affwp-pay-referral';
	var payContext = null;

	function modalStore() {
		return ( window.Alpine && typeof window.Alpine.store === 'function' ) ? window.Alpine.store( 'modals' ) : null;
	}

	function setModalData( patch ) {
		var store = modalStore();
		var reg   = store && store.registry && store.registry[ MODAL_ID ];
		if ( reg ) {
			reg.data = Object.assign( {}, reg.data || {}, patch );
		}
	}

	document.addEventListener( 'click', function ( e ) {
		var trigger = e.target.closest && e.target.closest( '[data-affwp-pay]' );
		if ( ! trigger ) {
			return;
		}

		e.preventDefault();
		e.stopPropagation();

		var nonce = ( data.nonces || {} ).referral;
		var store = modalStore();

		// No nonce/URL/Alpine: fall back to the link (opens the referral).
		if ( ! nonce || ! data.ajaxUrl || ! store ) {
			if ( trigger.href ) {
				window.location.href = trigger.href;
			}
			return;
		}

		var referralId = trigger.getAttribute( 'data-affwp-id' );
		payContext = { row: trigger.closest( 'tr' ), referralId: referralId };

		// Open immediately with a spinner, then fill in (or show an error in the modal).
		// Never a JS alert — feedback stays inside the modal for consistency.
		store.open( MODAL_ID, { isLoading: true, isProcessing: false, error: '', referral_id: referralId } );

		var body = new URLSearchParams();
		body.set( 'action', 'affwp_pay_referral_preview' );
		body.set( 'nonce', nonce );
		body.set( 'referral_id', referralId );

		fetch( data.ajaxUrl, {
			method: 'POST',
			credentials: 'same-origin',
			headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
			body: body.toString()
		} )
			.then( function ( r ) {
				return r.json();
			} )
			.then( function ( res ) {
				if ( ! res || ! res.success || ! res.data ) {
					setModalData( { isLoading: false, error: ( res && res.data && res.data.message ) || ( data.labels && data.labels.error ) || 'Error' } );
					return;
				}
				setModalData( Object.assign( {}, res.data, { isLoading: false, isProcessing: false, error: '' } ) );
			} )
			.catch( function () {
				setModalData( { isLoading: false, error: ( data.labels && data.labels.networkError ) || 'Error' } );
			} );
	}, true );

	window.addEventListener( 'affwp:pay-referral-confirm', function () {
		if ( ! payContext ) {
			return;
		}

		var nonce = ( data.nonces || {} ).referral;
		if ( ! nonce || ! data.ajaxUrl ) {
			return;
		}

		var ctx   = payContext;
		var store = modalStore();

		setModalData( { isProcessing: true, error: '' } );

		var body = new URLSearchParams();
		body.set( 'action', 'affwp_pay_referral' );
		body.set( 'nonce', nonce );
		body.set( 'referral_id', ctx.referralId );
		body.set( 'list_url', window.location.href );

		fetch( data.ajaxUrl, {
			method: 'POST',
			credentials: 'same-origin',
			headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
			body: body.toString()
		} )
			.then( function ( r ) {
				return r.json();
			} )
			.then( function ( res ) {
				if ( ! res || ! res.success || ! res.data ) {
					throw new Error( ( res && res.data && res.data.message ) || ( data.labels && data.labels.error ) );
				}
				var statusCell  = ctx.row && ctx.row.querySelector( '.column-status' );
				var actionsCell = ctx.row && ctx.row.querySelector( '.column-actions' );
				var isListRow   = !! ( statusCell || actionsCell );

				if ( isListRow ) {
					if ( statusCell && res.data.status_html ) {
						statusCell.innerHTML = res.data.status_html;
					}
					if ( actionsCell && res.data.actions_html ) {
						actionsCell.innerHTML = res.data.actions_html;
						if ( window.affwpTooltips && typeof window.affwpTooltips.createTooltipInstance === 'function' ) {
							actionsCell.querySelectorAll( '[data-tooltip-html]:not([data-tippy-processed])' ).forEach( function ( el ) {
								window.affwpTooltips.createTooltipInstance( el, el.getAttribute( 'data-tooltip-html' ) );
							} );
						}
					}
				}

				if ( store ) {
					store.close( MODAL_ID );
				}
				payContext = null;

				// Confirm what happened — the in-place status swap is easy to miss.
				if ( res.data.toast ) {
					showToast( res.data.toast );
				}

				// On screens without list cells to swap (the referral edit screen), reload so
				// the form reflects the new paid/processing state. (Interim until the drawer.)
				if ( ! isListRow ) {
					window.location.reload();
				}
			} )
			.catch( function ( err ) {
				setModalData( { isProcessing: false, error: ( err && err.message ) || ( data.labels && data.labels.networkError ) || 'Error' } );
			} );
	} );

	/*
	 * Bulk "Pay": intercept the list-table bulk submit and open the bulk-pay drawer,
	 * which renders the shared Pay Affiliates preview (scoped to the selection) and
	 * pays from there. The drawer store owns the preview fetch, the confirm, and the
	 * result toast (see assets/js/affwp-bulk-pay-drawer.js).
	 */
	document.addEventListener( 'submit', function ( e ) {
		const form = e.target;
		if ( ! form || ! form.querySelector ) {
			return;
		}

		// Which bulk-action select (top or bottom) is set to "pay"?
		const sel1 = form.querySelector( 'select[name="action"]' );
		const sel2 = form.querySelector( 'select[name="action2"]' );
		const val  = ( sel1 && '-1' !== sel1.value ? sel1.value : '' ) || ( sel2 && '-1' !== sel2.value ? sel2.value : '' );
		if ( 'pay' !== val ) {
			return;
		}

		const checks = form.querySelectorAll( 'input[name="referral_id[]"]:checked, input[name="referral_id"]:checked' );
		if ( ! checks.length ) {
			return; // No selection — let WP show its "no items" behavior.
		}

		const drawer = window.Alpine && Alpine.store && Alpine.store( 'bulkPayDrawer' );
		if ( ! drawer || ! data.ajaxUrl ) {
			return; // No JS/Alpine: let the form submit (server no-ops 'pay', safe).
		}

		e.preventDefault();

		// Open the bulk-pay drawer: it renders the same grouped-payout preview as the
		// Pay Affiliates screen, scoped to this hand-picked selection, and pays from there.
		const selectedIds = [].slice.call( checks ).map( ( c ) => c.value );
		drawer.open( selectedIds );
	}, true );

	/*
	 * Whole-row click → edit screen.
	 */
	document.addEventListener( 'click', function ( e ) {
		if ( ! e.target.closest ) {
			return;
		}
		if ( e.target.closest( '[data-affwp-ajax]' ) ) {
			return;
		}

		var row = e.target.closest( 'tr[data-edit-url]' );
		if ( ! row || e.target.closest( IGNORE ) ) {
			return;
		}

		var url = row.getAttribute( 'data-edit-url' );
		if ( ! url ) {
			return;
		}

		// Don't hijack a text selection drag.
		var selection = window.getSelection && window.getSelection();
		if ( selection && selection.toString().length ) {
			return;
		}

		if ( e.metaKey || e.ctrlKey ) {
			window.open( url, '_blank' );
			return;
		}

		window.location.href = url;
	} );
} )();
