/**
 * Wuppi site branding visibility controls for the block editor.
 *
 * Vanilla JS, no build step required.
 *
 * @package wuppi
 */
( function ( wp ) {
	'use strict';

	if (
		! wp ||
		! wp.blocks ||
		! wp.blockEditor ||
		! wp.components ||
		! wp.compose ||
		! wp.data ||
		! wp.element ||
		! wp.hooks ||
		! wp.i18n
	) {
		return;
	}

	var blocks = wp.blocks;
	var blockEditor = wp.blockEditor;
	var components = wp.components;
	var compose = wp.compose;
	var data = wp.data;
	var element = wp.element;
	var hooks = wp.hooks;
	var i18n = wp.i18n;

	var InspectorControls = blockEditor.InspectorControls;
	var PanelBody = components.PanelBody;
	var ToggleControl = components.ToggleControl;
	var createHigherOrderComponent = compose.createHigherOrderComponent;
	var Fragment = element.Fragment;
	var el = element.createElement;
	var __ = i18n.__;
	var useDispatch = data.useDispatch;
	var useSelect = data.useSelect;

	var BRANDING_GROUP_CLASS = 'calm-branding-text';
	var SUPPORTED_BLOCKS = {
		'core/site-title': __( 'Show site title', 'wuppi' ),
		'core/site-tagline': __( 'Show site tagline', 'wuppi' ),
	};

	/**
	 * Whether the block supports the Wuppi visibility toggle.
	 *
	 * @param {string} blockName Block name.
	 * @return {boolean}
	 */
	function isSupportedBlock( blockName ) {
		return !! SUPPORTED_BLOCKS[ blockName ];
	}

	/**
	 * Whether block attributes contain a given CSS class.
	 *
	 * @param {Object} attributes Block attributes.
	 * @param {string} className  CSS class to find.
	 * @return {boolean}
	 */
	function blockHasClassName( attributes, className ) {
		var classes;
		var index;

		if ( ! attributes || ! attributes.className || ! className ) {
			return false;
		}

		classes = String( attributes.className ).trim().split( /\s+/ );

		for ( index = 0; index < classes.length; index++ ) {
			if ( classes[ index ] === className ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Lightweight object merge helper for ES5 editor scripts.
	 *
	 * @param {Object} target Merge target.
	 * @param {Object} source Merge source.
	 * @return {Object}
	 */
	function mergeObjects( target, source ) {
		var output = target || {};
		var key;

		for ( key in source ) {
			if ( Object.prototype.hasOwnProperty.call( source, key ) ) {
				output[ key ] = source[ key ];
			}
		}

		return output;
	}

	/**
	 * Join class names while skipping empty values.
	 *
	 * @return {string}
	 */
	function joinClassNames() {
		var index;
		var classes = [];

		for ( index = 0; index < arguments.length; index++ ) {
			if ( arguments[ index ] ) {
				classes.push( arguments[ index ] );
			}
		}

		return classes.join( ' ' );
	}

	/**
	 * Whether the current block is the Wuppi branding group.
	 *
	 * @param {Object} props Block edit props.
	 * @return {boolean}
	 */
	function isBrandingGroupBlock( props ) {
		return !! (
			props &&
			props.name === 'core/group' &&
			blockHasClassName( props.attributes, BRANDING_GROUP_CLASS )
		);
	}

	/**
	 * Return the direct branding child blocks for the given group.
	 *
	 * @param {Object} select           wp.data select function.
	 * @param {string} clientId         Parent block client ID.
	 * @param {boolean} isBrandingGroup Whether the parent block is the branding group.
	 * @return {Object}
	 */
	function getBrandingChildBlocks( select, clientId, isBrandingGroup ) {
		var block;
		var innerBlocks;
		var index;
		var brandingBlocks = {};

		if ( ! isBrandingGroup || ! clientId ) {
			return brandingBlocks;
		}

		block = select( 'core/block-editor' ).getBlock( clientId );

		if ( ! block || ! block.innerBlocks || ! block.innerBlocks.length ) {
			return brandingBlocks;
		}

		innerBlocks = block.innerBlocks;

		for ( index = 0; index < innerBlocks.length; index++ ) {
			if ( isSupportedBlock( innerBlocks[ index ].name ) ) {
				brandingBlocks[ innerBlocks[ index ].name ] = innerBlocks[ index ];
			}
		}

		return brandingBlocks;
	}

	/**
	 * Add the shared visibility attribute to the supported blocks.
	 *
	 * @param {Object} settings  Block settings.
	 * @param {string} blockName Block name.
	 * @return {Object}
	 */
	function addVisibilityAttribute( settings, blockName ) {
		if ( ! settings || ! isSupportedBlock( blockName ) ) {
			return settings;
		}

		settings.attributes = settings.attributes || {};

		if ( ! settings.attributes.wuppiHidden ) {
			settings.attributes.wuppiHidden = {
				type: 'boolean',
				default: false,
			};
		}

		return settings;
	}

	hooks.addFilter(
		'blocks.registerBlockType',
		'wuppi/site-branding-visibility/attribute',
		addVisibilityAttribute
	);

	// When the editor has already registered the core blocks, patch them in place as well.
	addVisibilityAttribute( blocks.getBlockType( 'core/site-title' ), 'core/site-title' );
	addVisibilityAttribute( blocks.getBlockType( 'core/site-tagline' ), 'core/site-tagline' );

	hooks.addFilter(
		'editor.BlockEdit',
		'wuppi/site-branding-visibility/inspector',
		createHigherOrderComponent(
			function ( BlockEdit ) {
				return function ( props ) {
					var isBrandingGroup = isBrandingGroupBlock( props );
					var brandingBlocks = useSelect(
						function ( select ) {
							return getBrandingChildBlocks( select, props.clientId, isBrandingGroup );
						},
						[ props.clientId, isBrandingGroup ]
					);
					var blockEditorDispatch = useDispatch( 'core/block-editor' );
					var siteTitleBlock = brandingBlocks[ 'core/site-title' ];
					var siteTaglineBlock = brandingBlocks[ 'core/site-tagline' ];

					function setBrandingVisibility( block, isVisible ) {
						if ( ! block || ! block.clientId ) {
							return;
						}

						blockEditorDispatch.updateBlockAttributes( block.clientId, {
							wuppiHidden: ! isVisible,
						} );
					}

					if ( ! isBrandingGroup ) {
						return el( BlockEdit, props );
					}

					return el(
						Fragment,
						null,
						el( BlockEdit, props ),
						el(
							InspectorControls,
							null,
							el(
								PanelBody,
								{
									title: __( 'Visibility', 'wuppi' ),
									initialOpen: true,
								},
								el( ToggleControl, {
									label: SUPPORTED_BLOCKS[ 'core/site-title' ],
									checked: siteTitleBlock ? ! siteTitleBlock.attributes.wuppiHidden : false,
									disabled: ! siteTitleBlock,
									help: siteTitleBlock ? '' : __( 'Site Title block not found in this branding group.', 'wuppi' ),
									onChange: function ( value ) {
										setBrandingVisibility( siteTitleBlock, value );
									},
								} ),
								el( ToggleControl, {
									label: SUPPORTED_BLOCKS[ 'core/site-tagline' ],
									checked: siteTaglineBlock ? ! siteTaglineBlock.attributes.wuppiHidden : false,
									disabled: ! siteTaglineBlock,
									help: siteTaglineBlock ? '' : __( 'Site Tagline block not found in this branding group.', 'wuppi' ),
									onChange: function ( value ) {
										setBrandingVisibility( siteTaglineBlock, value );
									},
								} ),
								el(
									'p',
									{
										className: 'components-base-control__help',
									},
									__(
										'These controls only change visibility in the template. They do not modify Settings > General.',
										'wuppi'
									)
								)
							)
						)
					);
				};
			},
			'withWuppiSiteBrandingVisibilityInspector'
		)
	);

	hooks.addFilter(
		'editor.BlockListBlock',
		'wuppi/site-branding-visibility/class-name',
		createHigherOrderComponent(
			function ( BlockListBlock ) {
				return function ( props ) {
					var hidden;
					var wrapperProps;

					if ( ! isSupportedBlock( props.name ) ) {
						return el( BlockListBlock, props );
					}

					hidden = !! ( props.attributes && props.attributes.wuppiHidden );
					wrapperProps = mergeObjects( {}, props.wrapperProps || {} );

					if ( hidden ) {
						wrapperProps[ 'data-wuppi-visibility' ] = 'hidden';
					}

					return el(
						BlockListBlock,
						mergeObjects(
							mergeObjects( {}, props ),
							{
								className: joinClassNames(
									props.className,
									hidden ? 'is-wuppi-hidden-site-branding-block' : ''
								),
								wrapperProps: wrapperProps,
							}
						)
					);
				};
			},
			'withWuppiSiteBrandingVisibilityClassName'
		)
	);
} )( window.wp );
