/* Search & Social Appearance Audit - admin JS.
 * 1) Browser-driven scan chain: sequential AJAX batches with a progress bar
 *    while the page is open; WP-Cron continues server-side otherwise.
 * 2) Issue-details modal: problem, evidence, possible sources, actions.
 * 3) Per-row re-scan.
 */
( function ( $ ) {
	'use strict';

	if ( typeof window.ssaaScan === 'undefined' ) {
		return;
	}

	var cfg     = window.ssaaScan;
	var running = false;

	function post( action, data ) {
		return $.post( cfg.ajaxUrl, $.extend( { action: action, nonce: cfg.nonce }, data || {} ) );
	}

	// -------------------------------------------------------------------------
	// Scan chain
	// -------------------------------------------------------------------------

	var $start    = $( '#ssaa-start-scan' );
	var $cancel   = $( '#ssaa-cancel-scan' );
	var $progress = $( '#ssaa-progress' );
	var $fill     = $( '#ssaa-progress-fill' );
	var $text     = $( '#ssaa-progress-text' );

	// Whether the queue is paused (engine 0.4.0): the site is briefly
	// unavailable - our own maintenance window, or a 503/429 asking us back
	// later. Drives the label and a slower poll; ticking a paused queue at the
	// normal cadence is a busy-wait.
	function isDeferred( state ) {
		return !! ( state && state.deferred );
	}

	function render( state ) {
		var pct = state.total > 0 ? Math.round( ( state.done / state.total ) * 100 ) : 0;
		$fill.css( 'width', pct + '%' );
		$text.text(
			isDeferred( state )
				? cfg.i18n.deferred
				: cfg.i18n.progress.replace( '%1$s', String( state.done ) ).replace( '%2$s', String( state.total ) )
		);
		$start.prop( 'disabled', state.running );
		$cancel.prop( 'disabled', ! state.running );
		$progress.prop( 'hidden', ! state.running );
	}

	function finish() {
		running = false;
		window.location.reload();
	}

	var failures = 0;

	function tick() {
		if ( ! running ) {
			return;
		}
		post( 'ssaa_scan_batch' )
			.done( function ( r ) {
				if ( ! r || ! r.success ) {
					fail();
					return;
				}
				failures = 0;
				render( r.data );
				if ( ! r.data.running || 0 === r.data.remaining ) {
					finish();
					return;
				}
				if ( isDeferred( r.data ) ) {
					// Queue paused: poll gently (the pause is tens of
					// seconds) and let the server-side watchdog resume it.
					window.setTimeout( tick, 15000 );
				} else if ( 0 === r.data.processed ) {
					// Another worker (the cron continuation) holds the lock -
					// poll gently instead of hammering admin-ajax.
					window.setTimeout( tick, 4000 );
				} else {
					tick();
				}
			} )
			.fail( fail );
	}

	function fail() {
		// Transient failures (throttling, a dropped connection) must not be
		// reported as a failed scan - the server-side queue is unaffected.
		// Retry with backoff; only alert after repeated failures.
		failures++;
		if ( failures <= 3 ) {
			window.setTimeout( tick, 3000 * failures );
			return;
		}
		window.alert( cfg.i18n.failed );
		running = false;
	}

	$start.on( 'click', function () {
		$start.prop( 'disabled', true ).text( cfg.i18n.scanning );
		post( 'ssaa_start_scan' ).done( function ( r ) {
			if ( r && r.success ) {
				running = true;
				render( r.data );
				tick();
			} else {
				window.alert( cfg.i18n.failed );
			}
		} ).fail( function () {
			window.alert( cfg.i18n.failed );
		} );
	} );

	$cancel.on( 'click', function () {
		running = false;
		post( 'ssaa_cancel_scan' ).always( function () {
			window.location.reload();
		} );
	} );

	if ( cfg.state && cfg.state.running ) {
		running = true;
		render( cfg.state );
		tick();
	}

	// -------------------------------------------------------------------------
	// Issue details modal
	// -------------------------------------------------------------------------

	var $overlay = $( '#ssaa-modal-overlay' );
	var $title   = $( '#ssaa-modal-title' );
	var $body    = $( '#ssaa-modal-body' );
	var $view    = $( '#ssaa-modal-view' );
	var $edit    = $( '#ssaa-modal-edit' );
	var $rescan  = $( '#ssaa-modal-rescan' );
	var modalPostId = 0;
	var modalRef    = '';

	function severityChip( severity ) {
		var label = ( cfg.i18n.severity && cfg.i18n.severity[ severity ] ) || severity;
		return $( '<span/>', {
			'class': 'ssaa-chip ssaa-chip-' + severity,
			text: label
		} );
	}

	function openModal( payload ) {
		modalPostId = payload.post || 0;
		modalRef    = payload.ref || '';
		$title.text( payload.title || '' );
		$body.empty();

		if ( payload.facts && payload.facts.length ) {
			var $facts = $( '<dl/>', { 'class': 'ssaa-modal-facts' } );
			payload.facts.forEach( function ( fact ) {
				$( '<dt/>', { text: fact.label } ).appendTo( $facts );
				$( '<dd/>', { text: fact.value } ).appendTo( $facts );
			} );
			$body.append( $facts );
		}

		if ( ! payload.issues || ! payload.issues.length ) {
			$body.append( $( '<p/>', { 'class': 'ssaa-modal-ok', text: cfg.i18n.noIssues } ) );
		}

		( payload.issues || [] ).forEach( function ( issue ) {
			var ignored = issue.ignored || '';
			var $issue  = $( '<div/>', { 'class': 'ssaa-modal-issue' + ( ignored ? ' ssaa-modal-issue-ignored' : '' ) } );

			var $head = $( '<h3/>' )
				.append( severityChip( issue.severity ) )
				.append( document.createTextNode( ' ' + cfg.i18n.problem ) );
			if ( ignored ) {
				$head.append( document.createTextNode( ' ' ) ).append( $( '<span/>', {
					'class': 'ssaa-chip ssaa-chip-status',
					text: ( 'site' === ignored ) ? cfg.i18n.ignoredSite : cfg.i18n.ignoredPage
				} ) );
			}
			$head.appendTo( $issue );

			$( '<p/>', { text: issue.message } ).appendTo( $issue );
			$( '<p/>' ).append( $( '<code/>', { text: issue.code } ) ).appendTo( $issue );

			if ( issue.evidence && issue.evidence.length ) {
				$( '<h4/>', { text: cfg.i18n.evidence } ).appendTo( $issue );
				issue.evidence.forEach( function ( snippet ) {
					// textContent assignment - snippets render as escaped code.
					$( '<pre/>', { 'class': 'ssaa-evidence' } )
						.append( $( '<code/>', { text: snippet } ) )
						.appendTo( $issue );
				} );
			}

			// Ignore controls.
			var $controls = $( '<p/>', { 'class': 'ssaa-ignore-controls' } );
			function ignoreButton( label, scope, remove ) {
				return $( '<button/>', { type: 'button', 'class': 'button button-small', text: label } )
					.on( 'click', function () {
						var $btn = $( this );
						// Disable every control in this group so nothing double-fires,
						// and put a busy state on the clicked button. The success path
						// reloads the page, so the busy state is left in place until
						// that happens - it is only cleared on error.
						var $siblings = $controls.find( 'button' ).prop( 'disabled', true );
						$btn.addClass( 'ssaa-btn-busy' )
							.empty()
							.append( $( '<span/>', { 'class': 'ssaa-btn-spinner', 'aria-hidden': 'true' } ) )
							.append( document.createTextNode( cfg.i18n.ignoring ) );
						post( 'ssaa_ignore_issue', {
							scope: scope,
							code: issue.code,
							post_id: payload.post,
							remove: remove ? 1 : 0
						} ).done( function ( r ) {
							if ( r && r.success ) {
								window.location.reload();
							} else {
								window.alert( cfg.i18n.failed );
								$btn.removeClass( 'ssaa-btn-busy' ).text( label );
								$siblings.prop( 'disabled', false );
							}
						} ).fail( function () {
							window.alert( cfg.i18n.failed );
							$btn.removeClass( 'ssaa-btn-busy' ).text( label );
							$siblings.prop( 'disabled', false );
						} );
					} );
			}
			if ( ignored ) {
				$controls.append( ignoreButton( cfg.i18n.unignore, ignored, true ) );
			} else if ( payload.post ) {
				$controls
					.append( ignoreButton( cfg.i18n.ignorePage, 'page', false ) )
					.append( document.createTextNode( ' ' ) )
					.append( ignoreButton( cfg.i18n.ignoreSite, 'site', false ) );
			} else {
				// Archive rows have no post ID; page-scope ignores are keyed
				// by post ID and would collide across archives at 0, so only
				// the sitewide ignore is offered.
				$controls.append( ignoreButton( cfg.i18n.ignoreSite, 'site', false ) );
			}
			$controls.appendTo( $issue );

			$body.append( $issue );
		} );

		// Sources are only meaningful alongside issues.
		if ( payload.issues && payload.issues.length ) {
			var $sources = $( '<div/>', { 'class': 'ssaa-modal-sources' } );
			$( '<h4/>', { text: cfg.i18n.sources } ).appendTo( $sources );
			if ( payload.sources && payload.sources.length ) {
				var $list = $( '<ul/>' );
				payload.sources.forEach( function ( name ) {
					$( '<li/>', { text: name } ).appendTo( $list );
				} );
				$sources.append( $list );
				$( '<p/>', { 'class': 'description', text: cfg.i18n.sourcesNote } ).appendTo( $sources );
			} else {
				$( '<p/>', { 'class': 'description', text: cfg.i18n.noSources } ).appendTo( $sources );
			}
			$body.append( $sources );
		}

		$view.attr( 'href', payload.view || '#' );
		if ( payload.edit ) {
			$edit.attr( 'href', payload.edit ).show();
		} else {
			$edit.hide();
		}

		$overlay.removeAttr( 'hidden' ).addClass( 'ssaa-open' );
		$( document.body ).addClass( 'ssaa-modal-open' );
	}

	function closeModal() {
		$overlay.attr( 'hidden', 'hidden' ).removeClass( 'ssaa-open' );
		$( document.body ).removeClass( 'ssaa-modal-open' );
	}

	$( document ).on( 'click', '.ssaa-issues-btn', function () {
		var payload = $( this ).data( 'payload' );
		if ( payload ) {
			openModal( payload );
		}
	} );

	$( document ).on( 'click', '.ssaa-modal-close, .ssaa-modal-overlay', function ( e ) {
		if ( e.target === this ) {
			closeModal();
		}
	} );

	$( document ).on( 'keydown', function ( e ) {
		if ( 'Escape' === e.key && $overlay.hasClass( 'ssaa-open' ) ) {
			closeModal();
		}
	} );

	// -------------------------------------------------------------------------
	// "Changes since last scan" modal (premium). The markup is rendered only
	// in the premium build; these handlers are inert when it is absent.
	// -------------------------------------------------------------------------
	function closeChanges() {
		$( '#ssaa-changes-overlay' ).attr( 'hidden', 'hidden' ).removeClass( 'ssaa-open' );
		$( document.body ).removeClass( 'ssaa-modal-open' );
	}

	$( document ).on( 'click', '.ssaa-changes-open', function () {
		$( '#ssaa-changes-overlay' ).removeAttr( 'hidden' ).addClass( 'ssaa-open' );
		$( document.body ).addClass( 'ssaa-modal-open' );
	} );

	$( document ).on( 'click', '.ssaa-changes-close', function () {
		closeChanges();
	} );

	$( document ).on( 'click', '#ssaa-changes-overlay', function ( e ) {
		if ( e.target === this ) {
			closeChanges();
		}
	} );

	$( document ).on( 'keydown', function ( e ) {
		if ( 'Escape' === e.key && $( '#ssaa-changes-overlay' ).hasClass( 'ssaa-open' ) ) {
			closeChanges();
		}
	} );

	// -------------------------------------------------------------------------
	// Image lightbox: click a preview thumbnail to see it full size. Click
	// (not hover) so it works on touch devices too.
	// -------------------------------------------------------------------------

	var $lightbox      = $( '#ssaa-img-lightbox' );
	var $lightboxImg   = $( '#ssaa-img-lightbox-img' );
	var $lightboxFrame = $( '#ssaa-img-lightbox-frame' );
	var $lightboxCap   = $( '#ssaa-img-lightbox-caption' );
	var lightboxCrop   = 0; // target aspect ratio of the current platform crop; 0 = no frame

	// Draw the crop frame: the centred rectangle the platform keeps (object-fit:
	// cover). Everything outside it is dimmed by the frame's box-shadow, which is
	// clipped to the image by .ssaa-lightbox-imgwrap { overflow: hidden }.
	function positionLightboxFrame() {
		if ( ! lightboxCrop ) {
			$lightboxFrame.attr( 'hidden', 'hidden' );
			return;
		}
		var img = $lightboxImg[0];
		var rW = img.clientWidth;
		var rH = img.clientHeight;
		var nW = img.naturalWidth;
		var nH = img.naturalHeight;
		if ( ! rW || ! rH || ! nW || ! nH ) {
			$lightboxFrame.attr( 'hidden', 'hidden' );
			return;
		}
		var imageRatio = nW / nH;
		var keptW, keptH, left, top;
		if ( imageRatio > lightboxCrop ) {
			keptH = rH;
			keptW = rH * lightboxCrop;
			top   = 0;
			left  = ( rW - keptW ) / 2;
		} else {
			keptW = rW;
			keptH = rW / lightboxCrop;
			left  = 0;
			top   = ( rH - keptH ) / 2;
		}
		// Image already matches the target ratio - nothing is cropped, so no frame.
		if ( keptW >= rW - 0.5 && keptH >= rH - 0.5 ) {
			$lightboxFrame.attr( 'hidden', 'hidden' );
			return;
		}
		$lightboxFrame.css( {
			left: Math.round( left ) + 'px',
			top: Math.round( top ) + 'px',
			width: Math.round( keptW ) + 'px',
			height: Math.round( keptH ) + 'px'
		} ).removeAttr( 'hidden' );
	}

	function closeLightbox() {
		$lightbox.attr( 'hidden', 'hidden' ).removeClass( 'ssaa-open' );
		$lightboxImg.attr( 'src', '' );
		$lightboxFrame.attr( 'hidden', 'hidden' );
		$lightboxCap.attr( 'hidden', 'hidden' ).text( '' );
		lightboxCrop = 0;
		$( document.body ).removeClass( 'ssaa-modal-open' );
	}

	$( document ).on( 'click', '.ssaa-zoomable', function ( e ) {
		e.preventDefault();
		var $el = $( this );
		var src = $el.data( 'full' ) || $el.attr( 'src' );
		if ( ! src ) {
			return;
		}
		lightboxCrop = parseFloat( $el.attr( 'data-crop' ) ) || 0;
		var label = $el.attr( 'data-crop-label' ) || '';
		$lightboxFrame.attr( 'hidden', 'hidden' );
		if ( label && lightboxCrop ) {
			$lightboxCap.text( label ).removeAttr( 'hidden' );
		} else {
			$lightboxCap.attr( 'hidden', 'hidden' ).text( '' );
		}
		$lightboxImg.attr( 'src', src );
		$lightbox.removeAttr( 'hidden' ).addClass( 'ssaa-open' );
		$( document.body ).addClass( 'ssaa-modal-open' );
		// Cached images fire no load event, so position now if already complete.
		if ( $lightboxImg[0].complete ) {
			window.requestAnimationFrame( positionLightboxFrame );
		}
	} );

	$lightboxImg.on( 'load', function () {
		window.requestAnimationFrame( positionLightboxFrame );
	} );

	$( window ).on( 'resize', function () {
		if ( $lightbox.hasClass( 'ssaa-open' ) ) {
			positionLightboxFrame();
		}
	} );

	$( document ).on( 'click', '.ssaa-img-lightbox-close', function () {
		closeLightbox();
	} );

	$( document ).on( 'click', '.ssaa-img-lightbox', function ( e ) {
		if ( e.target === this ) {
			closeLightbox();
		}
	} );

	$( document ).on( 'keydown', function ( e ) {
		if ( 'Escape' === e.key && $lightbox.hasClass( 'ssaa-open' ) ) {
			closeLightbox();
		}
	} );

	// -------------------------------------------------------------------------
	// Re-scan (row action + modal button)
	// -------------------------------------------------------------------------

	function rescan( target, $trigger ) {
		var original = $trigger.text();
		$trigger.text( cfg.i18n.rescanning ).prop( 'disabled', true );
		// Ref-based when available (archives have no usable post ID); the
		// handler accepts either.
		post( 'ssaa_rescan_item', target )
			.done( function () {
				window.location.reload();
			} )
			.fail( function () {
				window.alert( cfg.i18n.failed );
				$trigger.text( original ).prop( 'disabled', false );
			} );
	}

	$( document ).on( 'click', '.ssaa-rescan', function ( e ) {
		e.preventDefault();
		var ref = $( this ).data( 'ref' );
		rescan( ref ? { ref: ref } : { post_id: $( this ).data( 'post' ) }, $( this ) );
	} );

	$rescan.on( 'click', function () {
		if ( modalRef ) {
			rescan( { ref: modalRef }, $( this ) );
		} else if ( modalPostId ) {
			rescan( { post_id: modalPostId }, $( this ) );
		}
	} );
} )( jQuery );


