/* global jQuery, lodash, hCaptcha, hCaptchaSettingsBase, HCaptchaGeneralObject, kaggDialog */

/**
 * @param HCaptchaGeneralObject.ajaxUrl
 * @param HCaptchaGeneralObject.badJSONError
 * @param HCaptchaGeneralObject.checkConfigAction
 * @param HCaptchaGeneralObject.checkConfigNonce
 * @param HCaptchaGeneralObject.checkConfigNotice
 * @param HCaptchaGeneralObject.checkingConfigMsg
 * @param HCaptchaGeneralObject.completeHCaptchaContent
 * @param HCaptchaGeneralObject.completeHCaptchaTitle
 * @param HCaptchaGeneralObject.modeLive
 * @param HCaptchaGeneralObject.modeTestEnterpriseBotDetected
 * @param HCaptchaGeneralObject.modeTestEnterpriseBotDetectedSiteKey
 * @param HCaptchaGeneralObject.modeTestEnterpriseSafeEndUser
 * @param HCaptchaGeneralObject.modeTestEnterpriseSafeEndUserSiteKey
 * @param HCaptchaGeneralObject.modeTestPublisher
 * @param HCaptchaGeneralObject.modeTestPublisherSiteKey
 * @param HCaptchaGeneralObject.siteKey
 */

/* eslint-disable no-console */

/**
 * General settings page logic.
 *
 * @param {Object} $ jQuery instance.
 */
const general = function( $ ) {
	const headerBarSelector = '.hcaptcha-header-bar';
	const msgSelector = '#hcaptcha-message';
	let $message = $( msgSelector );
	const $form = $( 'form.hcaptcha-general' );
	const $siteKey = $( '[name="hcaptcha_settings[site_key]"]' );
	const $secretKey = $( '[name="hcaptcha_settings[secret_key]"]' );
	const $sampleHCaptcha = $( '#hcaptcha-options .h-captcha' );
	const $checkConfig = $( '#check_config' );
	const $resetNotifications = $( '#reset_notifications' );
	const $theme = $( '[name="hcaptcha_settings[theme]"]' );
	const $size = $( '[name="hcaptcha_settings[size]"]' );
	const $language = $( '[name="hcaptcha_settings[language]"]' );
	const $mode = $( '[name="hcaptcha_settings[mode]"]' );
	const $customThemes = $( '[name="hcaptcha_settings[custom_themes][]"]' );
	const $customProp = $( '.hcaptcha-general-custom-prop select' );
	const $customValue = $( '.hcaptcha-general-custom-value input' );
	const $configParams = $( '[name="hcaptcha_settings[config_params]"]' );
	const $enterpriseInputs = $( '.hcaptcha-section-enterprise + table input' );
	const $recaptchaCompatOff = $( '[name="hcaptcha_settings[recaptcha_compat_off][]"]' );
	const $submit = $form.find( '#submit' );
	const modes = {};
	const dataErrorBgColor = '#ffabaf';
	let siteKeyInitVal = $siteKey.val();
	let secretKeyInitVal = $secretKey.val();
	let enterpriseInitValues = getEnterpriseValues();

	modes[ HCaptchaGeneralObject.modeLive ] = HCaptchaGeneralObject.siteKey;
	modes[ HCaptchaGeneralObject.modeTestPublisher ] = HCaptchaGeneralObject.modeTestPublisherSiteKey;
	modes[ HCaptchaGeneralObject.modeTestEnterpriseSafeEndUser ] = HCaptchaGeneralObject.modeTestEnterpriseSafeEndUserSiteKey;
	modes[ HCaptchaGeneralObject.modeTestEnterpriseBotDetected ] = HCaptchaGeneralObject.modeTestEnterpriseBotDetectedSiteKey;

	let credentialsChanged = false;
	let enterpriseSettingsChanged = false;

	let consoleLogs = [];

	interceptConsoleLogs();
	initDisabledKeyInputs();

	function interceptConsoleLogs() {
		consoleLogs = [];

		const systemLog = console.log;
		const systemWarn = console.warn;
		const systemInfo = console.info;
		const systemError = console.error;
		const systemClear = console.clear;

		/* istanbul ignore next */
		// eslint-disable-next-line no-unused-vars
		console.log = function( message ) {
			consoleLogs.push( [ 'Console log:', arguments ] );
			systemLog.apply( console, arguments );
		};

		/* istanbul ignore next */
		// eslint-disable-next-line no-unused-vars
		console.warn = function( message ) {
			consoleLogs.push( [ 'Console warn:', arguments ] );
			systemWarn.apply( console, arguments );
		};

		/* istanbul ignore next */
		// eslint-disable-next-line no-unused-vars
		console.info = function( message ) {
			consoleLogs.push( [ 'Console info:', arguments ] );
			systemInfo.apply( console, arguments );
		};

		/* istanbul ignore next */
		// eslint-disable-next-line no-unused-vars
		console.error = function( message ) {
			consoleLogs.push( [ 'Console error:', arguments ] );
			systemError.apply( console, arguments );
		};

		/* istanbul ignore next */
		console.clear = function() {
			consoleLogs = [];
			systemClear();
		};
	}

	function getCleanConsoleLogs() {
		const ignore = [
			'recaptchacompat disabled',
			'Missing sitekey - https://docs.hcaptcha.com/configuration#javascript-api',
		];
		const logs = [];

		for ( let i = 0; i < consoleLogs.length; i++ ) {
			// Extract strings only (some JS functions push objects to the console).
			const consoleLog = consoleLogs[ i ];
			const type = consoleLog[ 0 ];
			const args = consoleLog[ 1 ];
			const keys = Object.keys( args );
			const lines = [];

			for ( let a = 0; a < keys.length; a++ ) {
				const arg = args[ a ];

				if ( typeof arg === 'string' && ignore.indexOf( arg ) === -1 ) {
					lines.push( [ type, arg ].join( ' ' ) );
				}
			}

			logs.push( lines.join( '\n' ) );
		}

		consoleLogs = [];

		return logs.join( '\n' );
	}

	function getValues( $inputs ) {
		const values = {};

		$inputs.each( function() {
			const $input = $( this );
			const name = $input.attr( 'name' ).replace( /hcaptcha_settings\[(.+)]/, '$1' );
			values[ name ] = $input.val();
		} );

		return values;
	}

	function getEnterpriseValues() {
		return getValues( $enterpriseInputs );
	}

	function clearMessage() {
		$message.remove();
		// Concat below to avoid an inspection message.
		$( '<div id="hcaptcha-message">' + '</div>' ).insertAfter( headerBarSelector );
		$message = $( msgSelector );
	}

	function showMessage( message = '', msgClass = '' ) {
		message = message === undefined ? '' : String( message );

		const logs = getCleanConsoleLogs();

		message += '\n' + logs;
		message = message.trim();

		if ( ! message ) {
			return;
		}

		$message.removeClass();
		$message.addClass( msgClass + ' notice is-dismissible' );

		const messageLines = message.split( '\n' ).map( function( line ) {
			return `<p>${ line }</p>`;
		} );
		$message.html( messageLines.join( '' ) );

		$( document ).trigger( 'wp-updates-notice-added' );

		$( 'html, body' ).stop().animate(
			{
				scrollTop: $message.offset().top - hCaptchaSettingsBase.getStickyHeight(),
			},
			1000
		);
	}

	function showSuccessMessage( message = '' ) {
		showMessage( message, 'notice-success' );
	}

	function showErrorMessage( message = '' ) {
		showMessage( message, 'notice-error' );
	}

	function hCaptchaUpdate( params = {} ) {
		const globalParams = Object.assign( {}, hCaptcha.getParams(), params );
		const isCustomThemeActive = $customThemes.prop( 'checked' );
		const isModeLive = 'live' === $mode.val();

		if ( isCustomThemeActive && isModeLive ) {
			$sampleHCaptcha.attr( 'data-theme', 'custom' );
		} else {
			$sampleHCaptcha.attr( 'data-theme', $theme.val() );
		}

		if (
			( isCustomThemeActive && typeof params.theme === 'object' ) ||
			( ! isCustomThemeActive && typeof params.theme !== 'object' )
		) {
			globalParams.theme = params.theme;
		} else {
			globalParams.theme = hCaptcha.getParams().theme;
		}

		hCaptcha.setParams( globalParams );

		$sampleHCaptcha.html( '' );

		for ( const key in params ) {
			if ( typeof params[ key ] === 'object' ) {
				continue;
			}

			$sampleHCaptcha.attr( `data-${ key }`, `${ params[ key ] }` );
		}

		hCaptcha.bindEvents();
	}

	function deepMerge( target, source ) {
		const isObject = ( obj ) => obj && typeof obj === 'object';

		if ( ! isObject( target ) || ! isObject( source ) ) {
			return source;
		}

		Object.keys( source ).forEach( ( key ) => {
			const targetValue = target[ key ];
			const sourceValue = source[ key ];

			if ( Array.isArray( targetValue ) && Array.isArray( sourceValue ) ) {
				target[ key ] = targetValue.concat( sourceValue );
			} else if ( isObject( targetValue ) && isObject( sourceValue ) ) {
				target[ key ] = deepMerge( Object.assign( {}, targetValue ), sourceValue );
			} else {
				target[ key ] = sourceValue;
			}
		} );

		return target;
	}

	function syncConfigParams( configParams, parentKey = '' ) {
		for ( const key in configParams ) {
			// Construct the full key path.
			const fullKey = parentKey ? `${ parentKey }--${ key }` : key;

			// If the value is an object, recursively print its keys.
			if ( typeof configParams[ key ] === 'object' && configParams[ key ] !== null ) {
				syncConfigParams( configParams[ key ], fullKey );
			} else {
				// Update the custom property selector.
				const value = configParams[ key ];
				const propKey = fullKey.replace( /theme--/g, '' );
				const newValue = `${ propKey }=${ value }`;
				const $prop = $customProp.find( `option[value*="${ propKey }="]` );

				if ( $prop.length === 1 ) {
					$prop.attr( 'value', newValue );
					if ( $prop.is( ':selected' ) ) {
						$customValue.val( value );
					}
				}
			}
		}
	}

	function applyCustomThemes( params = {} ) {
		let configParamsJson = $configParams.val().trim();
		let configParams;

		configParamsJson = configParamsJson ? configParamsJson : null;

		try {
			configParams = JSON.parse( configParamsJson );
		} catch ( ex ) {
			$configParams.css( 'background-color', dataErrorBgColor );
			$submit.attr( 'disabled', true );
			showErrorMessage( HCaptchaGeneralObject.badJSONError );

			return;
		}

		$submit.attr( 'disabled', false );

		configParams = deepMerge( configParams, params );

		$configParams.val( JSON.stringify( configParams, null, 2 ) );

		syncConfigParams( configParams );

		if ( ! $customThemes.prop( 'checked' ) ) {
			configParams = {
				sitekey: $siteKey.val(),
				theme: $theme.val(),
				size: $size.val(),
				hl: $language.val(),
			};
		}

		hCaptchaUpdate( configParams );
	}

	function checkConfig() {
		clearMessage();
		$submit.attr( 'disabled', true );

		const data = {
			action: HCaptchaGeneralObject.checkConfigAction,
			nonce: HCaptchaGeneralObject.checkConfigNonce,
			mode: $mode.val(),
			siteKey: $siteKey.val(),
			secretKey: $secretKey.val(),
			'h-captcha-response': $( 'textarea[name="h-captcha-response"]' ).val(),
			'hcaptcha-widget-id': $( 'input[name="hcaptcha-widget-id"]' ).val(),
		};

		// noinspection JSVoidFunctionReturnValueUsed,JSCheckFunctionSignatures
		return $.post( {
			url: HCaptchaGeneralObject.ajaxUrl,
			data,
			beforeSend: () => showSuccessMessage( HCaptchaGeneralObject.checkingConfigMsg ),
		} )
			.done( function( response ) {
				if ( ! response.success ) {
					showErrorMessage( response.data );
					return;
				}

				siteKeyInitVal = $siteKey.val();
				secretKeyInitVal = $secretKey.val();
				credentialsChanged = false;

				enterpriseInitValues = getValues( $enterpriseInputs );
				enterpriseSettingsChanged = false;

				showSuccessMessage( response.data );
				$submit.attr( 'disabled', false );
			} )
			.fail(
				/**
				 * @param {Object} response
				 */
				function( response ) {
					showErrorMessage( response.statusText );
				}
			)
			.always( function() {
				hCaptchaUpdate();
			} );
	}

	function checkChangeCredentials() {
		if ( $siteKey.val() === siteKeyInitVal && $secretKey.val() === secretKeyInitVal ) {
			credentialsChanged = false;
			clearMessage();
		} else if ( ! credentialsChanged ) {
			credentialsChanged = true;
			showErrorMessage( HCaptchaGeneralObject.checkConfigNotice );
		}
	}

	function checkChangeEnterpriseSettings() {
		if ( JSON.stringify( getEnterpriseValues() ) === JSON.stringify( enterpriseInitValues ) ) {
			enterpriseSettingsChanged = false;
			clearMessage();
		} else if ( ! enterpriseSettingsChanged ) {
			enterpriseSettingsChanged = true;
			showErrorMessage( HCaptchaGeneralObject.checkConfigNotice );
		}
	}

	/**
	 * Set readonly and blocked state for key inputs.
	 *
	 * @param {jQuery}  $el Element to set readonly and blocked state for.
	 * @param {boolean} on  Whether to set readonly and blocked state.
	 */
	function setReadonlyBlocked( $el, on ) {
		if ( on ) {
			$el.prop( 'disabled', false )
				.attr( 'readonly', true )
				.attr( 'aria-disabled', 'true' )
				.on( 'keydown.hcaptchaHelper paste.hcaptchaHelper drop.hcaptchaHelper', ( e ) => e.preventDefault() );
		} else {
			$el.removeAttr( 'readonly' )
				.removeAttr( 'aria-disabled' )
				.off( 'keydown.hcaptchaHelper paste.hcaptchaHelper drop.hcaptchaHelper' );
		}
	}

	/**
	 * Show helper for disabled key inputs on click and hide it on blur.
	 */
	function initDisabledKeyInputs() {
		syncKeysWithMode();

		const $keys = $( '#site_key, #secret_key' );

		$keys
			.on( 'click.hcaptchaHelper', function() {
				const $input = $( this );

				// Show helper only when the input is readonly.
				if ( ! $input.is( '[readonly]' ) && $input.attr( 'aria-disabled' ) !== 'true' ) {
					return;
				}

				// Find a related helper within the same container.
				const $container = $input.parent();
				const $helper = $container.find( 'span.helper' ).first();
				const $helperContent = $container.find( 'span.helper-content' ).first();

				$helper.css( 'display', 'block' );
				$helperContent.css( 'display', 'block' );

				hCaptchaSettingsBase.highlightElement( $mode[ 0 ] );

				const onDoc = () => {
					$helper.css( 'display', 'none' );
					$helperContent.css( 'display', 'none' );
					$( document ).off( 'mousedown.hcaptchaHelper', onDoc );
				};

				$( document ).on( 'mousedown.hcaptchaHelper', onDoc );
			} )
			.on( 'keydown.hcaptchaHelper paste.hcaptchaHelper drop.hcaptchaHelper', ( e ) => {
				// Block paste, drop, and keydown events.
				const $input = $( e.currentTarget );

				if ( $input.is( '[readonly]' ) || $input.attr( 'aria-disabled' ) === 'true' ) {
					e.preventDefault();
				}
			} );
	}

	/**
	 * Sync keys with mode.
	 *
	 * @param {jQuery.Event|undefined} e Event object.
	 */
	function syncKeysWithMode( e = undefined ) {
		const mode = $mode.val();

		if ( ! modes.hasOwnProperty( mode ) ) {
			return;
		}

		if ( mode === HCaptchaGeneralObject.modeLive ) {
			setReadonlyBlocked( $siteKey, false );
			setReadonlyBlocked( $secretKey, false );
		} else {
			setReadonlyBlocked( $siteKey, true );
			setReadonlyBlocked( $secretKey, true );
		}

		// If the event is not triggered by the mode selector, skip the rest of the function.
		if ( e === undefined ) {
			return;
		}

		const sitekey = modes[ mode ];

		hCaptchaUpdate( { sitekey } );
	}

	// Test hook: expose internals for isolated unit tests
	// noinspection JSUnresolvedReference
	if ( typeof jest !== 'undefined' ) {
		// Expose only read-only references; no state is mutated here beyond normal function effects
		window.__generalTest = {
			getCleanConsoleLogs,
			interceptConsoleLogs,
			showMessage,
			showErrorMessage,
			hCaptchaUpdate,
			deepMerge,
		};
	}

	document.addEventListener( 'hCaptchaLoaded', function() {
		showErrorMessage();
	} );

	$checkConfig.on( 'click', function( event ) {
		event.preventDefault();

		// Check if hCaptcha is solved.
		if ( $( '.hcaptcha-general-sample-hcaptcha textarea[name="h-captcha-response"]' ).val() === '' ) {
			kaggDialog.confirm( {
				title: HCaptchaGeneralObject.completeHCaptchaTitle,
				content: HCaptchaGeneralObject.completeHCaptchaContent,
				type: 'info',
				buttons: {
					ok: {
						text: HCaptchaGeneralObject.OKBtnText,
					},
				},
				onAction: () => window.hCaptchaBindEvents(),
			} );

			return;
		}

		checkConfig();
	} );

	$submit.on( 'click', function( event ) {
		if ( ! ( credentialsChanged || enterpriseSettingsChanged ) ) {
			return;
		}

		event.preventDefault();

		const viewportHeight = $( window ).height();
		const buttonHeight = $checkConfig.outerHeight();
		const buttonTop = $checkConfig.offset().top;

		$( 'html, body' ).stop().animate(
			{
				scrollTop: buttonTop - viewportHeight + buttonHeight,
			},
			1000,
			function() {
				$checkConfig[ 0 ].click();
			}
		);
	} );

	$siteKey.on( 'change', function( e ) {
		const sitekey = $( e.target ).val();

		hCaptchaUpdate( { sitekey } );
		checkChangeCredentials();
	} );

	$secretKey.on( 'change', function() {
		checkChangeCredentials();
	} );

	$theme.on( 'change', function( e ) {
		const theme = $( e.target ).val();
		hCaptchaUpdate( { theme } );
	} );

	$size.on( 'change', function( e ) {
		const $invisibleNotice = $( '#hcaptcha-invisible-notice' );
		const size = $( e.target ).val();

		if ( 'invisible' === size ) {
			$invisibleNotice.show();
		} else {
			$invisibleNotice.hide();
		}

		hCaptchaUpdate( { size } );
	} );

	$language.on( 'change', function( e ) {
		const hl = $( e.target ).val();
		hCaptchaUpdate( { hl } );
	} );

	$mode.on( 'change', syncKeysWithMode );

	function toggleCustomThemeFields() {
		const isOn = $customThemes.prop( 'checked' );

		$customProp.prop( 'disabled', ! isOn );
		$customValue.prop( 'disabled', ! isOn );
		$configParams.prop( 'disabled', ! isOn );
	}

	toggleCustomThemeFields();

	$customThemes.on( 'change', function() {
		toggleCustomThemeFields();
		applyCustomThemes();
	} );

	$configParams.on( 'input', lodash.debounce( function() {
		applyCustomThemes();
	}, 300 ) );

	$configParams.on( 'focus', function() {
		$configParams.css( 'background-color', 'unset' );
	} );

	function forceHttps( host ) {
		host = host.replace( /(http|https):\/\//, '' );

		const url = new URL( 'https://' + host );

		return 'https://' + url.host;
	}

	function scriptUpdate() {
		const params = {
			onload: 'hCaptchaOnLoad',
			render: 'explicit',
		};

		if ( $recaptchaCompatOff.prop( 'checked' ) ) {
			params.recaptchacompat = 'off';
		}

		if ( $customThemes.prop( 'checked' ) ) {
			params.custom = 'true';
		}

		const enterpriseParams = {
			asset_host: 'assethost',
			endpoint: 'endpoint',
			host: 'host',
			image_host: 'imghost',
			report_api: 'reportapi',
			sentry: 'sentry',
		};

		const enterpriseValues = getEnterpriseValues();

		for ( const enterpriseParam in enterpriseParams ) {
			const value = enterpriseValues[ enterpriseParam ].trim();

			if ( value ) {
				params[ enterpriseParams[ enterpriseParam ] ] = encodeURIComponent( forceHttps( value ) );
			}
		}

		/**
		 * @param enterpriseValues.api_host
		 */
		let apiHost = enterpriseValues.api_host.trim();
		apiHost = apiHost ? apiHost : 'js.hcaptcha.com';
		apiHost = forceHttps( apiHost ) + '/1/api.js';

		const url = new URL( apiHost );

		for ( const name in params ) {
			url.searchParams.append( name, params[ name ] );
		}

		// Remove the existing API script.
		document.getElementById( 'hcaptcha-api' ).remove();
		// noinspection JSUnresolvedReference
		delete global.hcaptcha;

		// Remove sample hCaptcha.
		$sampleHCaptcha.html( '' );

		// Re-create the API script.
		const t = document.getElementsByTagName( 'head' )[ 0 ];
		const s = document.createElement( 'script' );

		s.type = 'text/javascript';
		s.id = 'hcaptcha-api';
		s.src = url.href;

		t.appendChild( s );
	}

	$enterpriseInputs.on( 'change', function() {
		scriptUpdate();
		checkChangeEnterpriseSettings();
	} );

	// Prevent saving values of some form elements.
	$checkConfig.removeAttr( 'name' );
	$resetNotifications.removeAttr( 'name' );
	$customProp.removeAttr( 'name' );
	$customValue.removeAttr( 'name' );

	// Disable group keys.
	$customProp.find( 'option' ).each( function() {
		const $option = $( this );
		const value = $option.val().split( '=' )[ 1 ];

		if ( ! value ) {
			$option.attr( 'disabled', true );
		}
	} );

	// Clear custom value.
	$customValue.val( '' );

	// On Custom Prop change.
	$customProp.on( 'change', function() {
		const $selected = $( this ).find( 'option:selected' );
		const option = $selected.val().split( '=' );
		const key = option[ 0 ];
		const value = option[ 1 ];

		if ( key === 'palette--mode' ) {
			$customValue.attr( 'type', 'text' );
			$customValue.val( value );
		} else {
			$customValue.val( value );
			$customValue.attr( 'type', 'color' );
		}
	} );

	// On Custom Value change (debounced for live color-picker updates).
	function customValueChange() {
		const value = $customValue.val();
		const $selected = $customProp.find( 'option:selected' );
		const option = $selected.val().split( '=' );
		let key = option[ 0 ];
		let params = value;

		$selected.val( key + '=' + value );

		key = 'theme--' + option[ 0 ];
		params = key.split( '--' ).reverse().reduce( function( acc, curr ) {
			const newObj = {};
			newObj[ curr ] = acc;

			return newObj;
		}, params );

		applyCustomThemes( params );
	}

	$customValue.on( 'input', lodash.debounce( customValueChange, 300 ) );
};

window.hCaptchaGeneral = general;

jQuery( document ).ready( general );
