/**
 * Custom Fields — native Gutenberg document sidebar.
 */
( function ( wp, config ) {
	'use strict';

	if ( ! wp || ! wp.plugins || ! wp.element || ! wp.data || ! wp.components || ! wp.i18n || ! config ) {
		return;
	}

	var createElement = wp.element.createElement;
	var Fragment = wp.element.Fragment;
	var useState = wp.element.useState;
	var useEffect = wp.element.useEffect;
	var useMemo = wp.element.useMemo;
	var PluginDocumentSettingPanel = wp.editPost && wp.editPost.PluginDocumentSettingPanel;
	var useSelect = wp.data.useSelect;
	var useDispatch = wp.data.useDispatch;
	var __ = wp.i18n.__;
	var TextControl = wp.components.TextControl;
	var TextareaControl = wp.components.TextareaControl;
	var ToggleControl = wp.components.ToggleControl;
	var SelectControl = wp.components.SelectControl;
	var CheckboxControl = wp.components.CheckboxControl;
	var RadioControl = wp.components.RadioControl;
	var Button = wp.components.Button;
	var Notice = wp.components.Notice;
	var BaseControl = wp.components.BaseControl;
	var ComboboxControl = wp.components.ComboboxControl;
	var RawHTML = wp.element.RawHTML;
	var useRef = wp.element.useRef;

	if ( ! PluginDocumentSettingPanel ) {
		return;
	}

	var strings = config.strings || {};
	var initialSchema = config.schema || { groups: [] };

	function usePostMeta() {
		var meta = useSelect( function ( select ) {
			var editor = select( 'core/editor' );
			return editor ? editor.getEditedPostAttribute( 'meta' ) || {} : {};
		}, [] );

		var editPost = useDispatch( 'core/editor' ).editPost;

		function setMetaKey( key, value ) {
			editPost( {
				meta: Object.assign( {}, meta, ( function () {
					var patch = {};
					patch[ key ] = value;
					return patch;
				}() ) )
			} );
		}

		return { meta: meta, setMetaKey: setMetaKey };
	}

	function getMetaValue( meta, field ) {
		if ( Object.prototype.hasOwnProperty.call( meta, field.metaKey ) ) {
			return meta[ field.metaKey ];
		}
		return field.value;
	}

	function stringifyValue( value ) {
		if ( Array.isArray( value ) ) {
			return value.join( ',' );
		}
		if ( value && typeof value === 'object' ) {
			return JSON.stringify( value );
		}
		if ( value === true || value === '1' ) {
			return '1';
		}
		if ( value === false || value === '0' ) {
			return '0';
		}
		return value == null ? '' : String( value );
	}

	function compare( actual, operator, expected ) {
		switch ( operator ) {
			case '!=':
				return actual !== expected;
			case 'contains':
				return expected !== '' && actual.indexOf( expected ) !== -1;
			case 'not_contains':
				return expected === '' || actual.indexOf( expected ) === -1;
			case 'empty':
				return actual === '' || actual === '0';
			case 'not_empty':
				return actual !== '' && actual !== '0';
			case '==':
			default:
				return actual === expected;
		}
	}

	function matchesConditional( conditional, values ) {
		if ( ! conditional || ! conditional.enabled || ! conditional.rules || ! conditional.rules.length ) {
			return true;
		}
		var results = conditional.rules.map( function ( rule ) {
			return compare( values[ rule.field ] || '', rule.operator || '==', rule.value || '' );
		} );
		return conditional.match === 'any'
			? results.indexOf( true ) !== -1
			: results.indexOf( false ) === -1;
	}

	function buildFieldValues( group, meta ) {
		var values = {};
		( group.fields || [] ).forEach( function ( field ) {
			if ( ! field.key ) {
				return;
			}
			values[ field.key ] = stringifyValue( getMetaValue( meta, field ) );
		} );
		return values;
	}

	function openMediaFrame( args, onSelect ) {
		if ( ! wp.media ) {
			return;
		}
		var frame = wp.media(
			Object.assign(
				{
					title: strings.selectImage || 'Select',
					button: { text: strings.selectImage || 'Select' },
					multiple: false
				},
				args
			)
		);
		frame.on( 'select', function () {
			var selection = frame.state().get( 'selection' );
			onSelect( selection );
		} );
		frame.open();
	}

	function mapRelOptions( options ) {
		return ( options || [] ).map( function ( option ) {
			return { value: String( option.value ), label: option.label };
		} );
	}

	function searchRelational( field, query, include ) {
		if ( ! config.searchUrl || ! config.restNonce ) {
			return Promise.resolve( [] );
		}

		var type = field.type;
		var endpoint = '';
		var params = new URLSearchParams();
		params.set( 's', query );
		if ( include ) {
			params.set( 'include', include );
		}

		if ( type === 'post_object' ) {
			endpoint = 'posts';
			params.set( 'post_types', ( field.postTypes || [] ).join( ',' ) );
		} else if ( type === 'term' ) {
			endpoint = 'terms';
			params.set( 'taxonomy', field.taxonomy || '' );
		} else if ( type === 'user' ) {
			endpoint = 'users';
		} else {
			return Promise.resolve( [] );
		}

		return window.fetch( config.searchUrl + endpoint + '?' + params.toString(), {
			headers: { 'X-WP-Nonce': config.restNonce }
		} )
			.then( function ( response ) {
				return response.json();
			} )
			.then( function ( data ) {
				return mapRelOptions( data.results || [] );
			} )
			.catch( function () {
				return [];
			} );
	}

	function RelationalField( props ) {
		var field = props.field;
		var value = props.value;
		var setMetaKey = props.setMetaKey;
		var label = props.label;
		var help = props.help;

		var initialOptions = useMemo( function () {
			var opts = mapRelOptions( field.options );
			return [ { label: '—', value: '' } ].concat(
				opts.filter( function ( option ) {
					return option.value !== '';
				} )
			);
		}, [ field.options ] );

		var optionsState = useState( initialOptions );
		var options = optionsState[0];
		var setOptions = optionsState[1];
		var filterState = useState( '' );
		var filter = filterState[0];
		var setFilter = filterState[1];

		useEffect( function () {
			setOptions( initialOptions );
		}, [ initialOptions, setOptions ] );

		useEffect( function () {
			var timer = window.setTimeout( function () {
				var include = value ? String( value ) : '';
				searchRelational( field, filter, include ).then( function ( results ) {
					setOptions( [ { label: '—', value: '' } ].concat( results ) );
				} );
			}, 300 );

			return function () {
				window.clearTimeout( timer );
			};
		}, [ filter, field, value, setOptions ] );

		return createElement( ComboboxControl, {
			label: label,
			help: help,
			value: value == null || value === '' ? null : String( value ),
			options: options,
			onChange: function ( next ) {
				setMetaKey( field.metaKey, next ? parseInt( next, 10 ) : '' );
			},
			onFilterValueChange: function ( next ) {
				setFilter( next );
			},
			allowReset: true
		} );
	}

	function OembedField( props ) {
		var field = props.field;
		var value = props.value;
		var setMetaKey = props.setMetaKey;
		var label = props.label;
		var help = props.help;
		var previewState = useState( '' );
		var preview = previewState[0];
		var setPreview = previewState[1];
		var loadingState = useState( false );
		var loading = loadingState[0];
		var setLoading = loadingState[1];

		useEffect( function () {
			var url = value == null ? '' : String( value ).trim();
			if ( ! url || ! config.oembedUrl ) {
				setPreview( '' );
				setLoading( false );
				return;
			}

			setLoading( true );
			var params = new URLSearchParams( { url: url, maxwidth: '640' } );
			window.fetch( config.oembedUrl + '?' + params.toString() )
				.then( function ( response ) {
					return response.json();
				} )
				.then( function ( data ) {
					setPreview( data && data.html ? data.html : '' );
				} )
				.catch( function () {
					setPreview( '' );
				} )
				.finally( function () {
					setLoading( false );
				} );
		}, [ value, setLoading, setPreview ] );

		return createElement(
			Fragment,
			null,
			createElement( TextControl, {
				label: label,
				help: help,
				type: 'url',
				value: value == null ? '' : String( value ),
				onChange: function ( next ) {
					setMetaKey( field.metaKey, next );
				}
			} ),
			loading
				? createElement(
					'p',
					{ className: 'wpps-cf-gutenberg-oembed-preview is-loading description' },
					strings.oembedLoading || 'Loading preview…'
				)
				: preview
					? createElement( RawHTML, { className: 'wpps-cf-gutenberg-oembed-preview' }, preview )
					: null
		);
	}

	function ColorField( props ) {
		var field = props.field;
		var value = props.value;
		var setMetaKey = props.setMetaKey;
		var label = props.label;
		var help = props.help;
		var inputRef = useRef( null );

		useEffect( function () {
			var $input = window.jQuery && inputRef.current ? window.jQuery( inputRef.current ) : null;
			if ( ! $input || ! $input.length || typeof $input.wpColorPicker !== 'function' ) {
				return;
			}

			if ( $input.hasClass( 'wp-color-picker' ) ) {
				return;
			}

			$input.wpColorPicker( {
				defaultColor: '',
				change: function ( event, ui ) {
					setMetaKey( field.metaKey, ui.color.toString() );
				},
				clear: function () {
					setMetaKey( field.metaKey, '' );
				}
			} );

			return function () {
				if ( $input.hasClass( 'wp-color-picker' ) ) {
					try {
						$input.wpColorPicker( 'destroy' );
					} catch ( error ) {
						// Ignore teardown errors when the panel unmounts quickly.
					}
				}
			};
		}, [ field.metaKey, setMetaKey ] );

		useEffect( function () {
			var $input = window.jQuery && inputRef.current ? window.jQuery( inputRef.current ) : null;
			if ( ! $input || ! $input.hasClass( 'wp-color-picker' ) ) {
				return;
			}

			var current = $input.wpColorPicker( 'color' );
			if ( String( current ) !== String( value || '' ) ) {
				$input.wpColorPicker( 'color', value || '' );
			}
		}, [ value ] );

		return createElement(
			BaseControl,
			{ label: label, help: help, className: 'wpps-cf-gutenberg-color' },
			createElement( 'input', {
				ref: inputRef,
				type: 'text',
				className: 'cf-color-field',
				defaultValue: value == null ? '' : String( value )
			} )
		);
	}

	function GalleryField( props ) {
		var field = props.field;
		var value = props.value;
		var setMetaKey = props.setMetaKey;
		var label = props.label;
		var help = props.help;
		var ids = Array.isArray( value )
			? value.map( function ( id ) {
				return parseInt( id, 10 );
			} ).filter( Boolean )
			: [];

		var attachments = useSelect( function ( select ) {
			var core = select( 'core' );
			if ( ! core || typeof core.getMedia !== 'function' ) {
				return [];
			}

			return ids.map( function ( id ) {
				return core.getMedia( id );
			} ).filter( Boolean );
		}, [ ids.join( ',' ) ] );

		return createElement(
			BaseControl,
			{ label: label, help: help, className: 'wpps-cf-gutenberg-gallery' },
			attachments.length
				? createElement(
					'div',
					{ className: 'wpps-cf-gutenberg-gallery__grid' },
					attachments.map( function ( attachment ) {
						var thumb = attachment.media_details
							&& attachment.media_details.sizes
							&& attachment.media_details.sizes.thumbnail
							? attachment.media_details.sizes.thumbnail.source_url
							: attachment.source_url;

						return createElement(
							'div',
							{ key: attachment.id, className: 'wpps-cf-gutenberg-gallery__item' },
							createElement( 'img', { src: thumb, alt: attachment.alt_text || '' } ),
							createElement(
								Button,
								{
									variant: 'link',
									isDestructive: true,
									onClick: function () {
										setMetaKey(
											field.metaKey,
											ids.filter( function ( item ) {
												return item !== attachment.id;
											} )
										);
									}
								},
								strings.remove || 'Remove'
							)
						);
					} )
				)
				: createElement( 'p', { className: 'description' }, '—' ),
			createElement(
				Button,
				{
					variant: 'secondary',
					onClick: function () {
						openMediaFrame(
							{ multiple: true, library: { type: 'image' } },
							function ( selection ) {
								var next = ids.slice();
								selection.each( function ( item ) {
									var id = item.get( 'id' );
									if ( next.indexOf( id ) === -1 ) {
										next.push( id );
									}
								} );
								setMetaKey( field.metaKey, next );
							}
						);
					}
				},
				strings.selectImage || 'Select images'
			),
			ids.length
				? createElement(
					Button,
					{
						variant: 'link',
						isDestructive: true,
						onClick: function () {
							setMetaKey( field.metaKey, [] );
						}
					},
					strings.clearGallery || 'Clear all'
				)
				: null
		);
	}

	function FieldControl( props ) {
		var field = props.field;
		var meta = props.meta;
		var setMetaKey = props.setMetaKey;
		var value = getMetaValue( meta, field );
		var type = field.type;
		var help = field.instructions || '';
		var label = field.label || field.name;

		if ( type === 'message' ) {
			return createElement(
				Notice,
				{ status: 'info', isDismissible: false, className: 'wpps-cf-gutenberg-message' },
				field.message || label
			);
		}

		if ( type === 'toggle' ) {
			return createElement( ToggleControl, {
				label: label,
				help: help,
				checked: value === '1' || value === 1 || value === true,
				onChange: function ( checked ) {
					setMetaKey( field.metaKey, checked ? '1' : '0' );
				}
			} );
		}

		if ( type === 'textarea' ) {
			return createElement( TextareaControl, {
				label: label,
				help: help,
				value: value == null ? '' : String( value ),
				rows: field.rows || 4,
				onChange: function ( next ) {
					setMetaKey( field.metaKey, next );
				}
			} );
		}

		if ( type === 'select' ) {
			var selectOptions = [ { label: '—', value: '' } ].concat(
				( field.choices || [] ).map( function ( choice ) {
					return { label: choice.label, value: choice.value };
				} )
			);
			if ( field.multiple ) {
				var selectedValues = Array.isArray( value ) ? value : ( value ? [ String( value ) ] : [] );
				return createElement(
					BaseControl,
					{ label: label, help: help },
					selectOptions.filter( function ( option ) {
						return option.value !== '';
					} ).map( function ( option ) {
						return createElement( CheckboxControl, {
							key: option.value,
							label: option.label,
							checked: selectedValues.indexOf( option.value ) !== -1,
							onChange: function ( checked ) {
								var next = selectedValues.slice();
								if ( checked ) {
									if ( next.indexOf( option.value ) === -1 ) {
										next.push( option.value );
									}
								} else {
									next = next.filter( function ( item ) {
										return item !== option.value;
									} );
								}
								setMetaKey( field.metaKey, next );
							}
						} );
					} )
				);
			}
			return createElement( SelectControl, {
				label: label,
				help: help,
				value: value == null ? '' : String( value ),
				options: selectOptions,
				onChange: function ( next ) {
					setMetaKey( field.metaKey, next );
				}
			} );
		}

		if ( type === 'radio' ) {
			return createElement( RadioControl, {
				label: label,
				help: help,
				selected: value == null ? '' : String( value ),
				options: ( field.choices || [] ).map( function ( choice ) {
					return { label: choice.label, value: choice.value };
				} ),
				onChange: function ( next ) {
					setMetaKey( field.metaKey, next );
				}
			} );
		}

		if ( type === 'checkbox' ) {
			var selected = Array.isArray( value ) ? value : [];
			return createElement(
				BaseControl,
				{ label: label, help: help },
				( field.choices || [] ).map( function ( choice ) {
					return createElement( CheckboxControl, {
						key: choice.value,
						label: choice.label,
						checked: selected.indexOf( choice.value ) !== -1,
						onChange: function ( checked ) {
							var next = selected.slice();
							if ( checked ) {
								if ( next.indexOf( choice.value ) === -1 ) {
									next.push( choice.value );
								}
							} else {
								next = next.filter( function ( item ) {
									return item !== choice.value;
								} );
							}
							setMetaKey( field.metaKey, next );
						}
					} );
				} )
			);
		}

		if ( type === 'image' || type === 'file' ) {
			var attachmentId = parseInt( value, 10 ) || 0;
			return createElement(
				BaseControl,
				{ label: label, help: help },
				createElement(
					'div',
					{ className: 'wpps-cf-gutenberg-media' },
					attachmentId
						? createElement( 'p', null, '#' + attachmentId )
						: createElement( 'p', { className: 'description' }, '—' ),
					createElement(
						Button,
						{
							variant: 'secondary',
							onClick: function () {
								openMediaFrame(
									{
										library: { type: type === 'image' ? 'image' : '' }
									},
									function ( selection ) {
										var item = selection.first();
										if ( item ) {
											setMetaKey( field.metaKey, item.get( 'id' ) );
										}
									}
								);
							}
						},
						type === 'image' ? ( strings.selectImage || 'Select image' ) : ( strings.selectFile || 'Select file' )
					),
					attachmentId
						? createElement(
							Button,
							{
								variant: 'link',
								isDestructive: true,
								onClick: function () {
									setMetaKey( field.metaKey, '' );
								}
							},
							strings.remove || 'Remove'
						)
						: null
				)
			);
		}

		if ( type === 'gallery' ) {
			return createElement( GalleryField, {
				field: field,
				value: value,
				setMetaKey: setMetaKey,
				label: label,
				help: help
			} );
		}

		if ( type === 'post_object' || type === 'term' || type === 'user' ) {
			return createElement( RelationalField, {
				field: field,
				value: value,
				setMetaKey: setMetaKey,
				label: label,
				help: help
			} );
		}

		if ( type === 'oembed' ) {
			return createElement( OembedField, {
				field: field,
				value: value,
				setMetaKey: setMetaKey,
				label: label,
				help: help
			} );
		}

		if ( type === 'color' ) {
			return createElement( ColorField, {
				field: field,
				value: value,
				setMetaKey: setMetaKey,
				label: label,
				help: help
			} );
		}

		if ( type === 'link' ) {
			var link = value && typeof value === 'object' ? value : { url: '', title: '', target: '' };
			return createElement(
				Fragment,
				null,
				createElement( TextControl, {
					label: label + ' — URL',
					value: link.url || '',
					onChange: function ( next ) {
						setMetaKey( field.metaKey, Object.assign( {}, link, { url: next } ) );
					}
				} ),
				createElement( TextControl, {
					label: label + ' — ' + __( 'Link text', 'wp-powersuite' ),
					value: link.title || '',
					onChange: function ( next ) {
						setMetaKey( field.metaKey, Object.assign( {}, link, { title: next } ) );
					}
				} ),
				createElement( ToggleControl, {
					label: label + ' — ' + __( 'Open in new tab', 'wp-powersuite' ),
					checked: link.target === '_blank',
					onChange: function ( checked ) {
						setMetaKey( field.metaKey, Object.assign( {}, link, { target: checked ? '_blank' : '' } ) );
					}
				} )
			);
		}

		var inputType = 'text';
		if ( type === 'number' || type === 'range' ) {
			inputType = 'number';
		} else if ( type === 'email' ) {
			inputType = 'email';
		} else if ( type === 'url' ) {
			inputType = 'url';
		} else if ( type === 'phone' ) {
			inputType = 'tel';
		} else if ( type === 'password' ) {
			inputType = 'password';
		} else if ( type === 'date' ) {
			inputType = 'date';
		} else if ( type === 'time' ) {
			inputType = 'time';
		} else if ( type === 'datetime' ) {
			inputType = 'datetime-local';
		}

		return createElement( TextControl, {
			label: label,
			help: help,
			type: inputType,
			value: value == null ? '' : String( value ),
			onChange: function ( next ) {
				setMetaKey( field.metaKey, next );
			}
		} );
	}

	function GroupPanel( props ) {
		var group = props.group;
		var metaApi = usePostMeta();
		var fieldValues = buildFieldValues( group, metaApi.meta );
		var visibleFields = ( group.fields || [] ).filter( function ( field ) {
			return matchesConditional( field.conditional, fieldValues );
		} );

		return createElement(
			PluginDocumentSettingPanel,
			{
				name: 'wpps-cf-group-' + group.key,
				title: group.title,
				className: 'wpps-cf-gutenberg-panel'
			},
			! group.sidebarOnly
				? createElement(
					Notice,
					{ status: 'info', isDismissible: false },
					strings.advancedFields || 'These fields also appear in the meta box below the editor.'
				)
				: null,
			visibleFields.map( function ( field ) {
				return createElement(
					'div',
					{ key: field.key || field.metaKey, className: 'wpps-cf-gutenberg-field wpps-cf-gutenberg-field--' + field.type },
					createElement( FieldControl, {
						field: field,
						meta: metaApi.meta,
						setMetaKey: metaApi.setMetaKey
					} )
				);
			} )
		);
	}

	function CustomFieldsSidebar() {
		var postId = useSelect( function ( select ) {
			return select( 'core/editor' ).getCurrentPostId();
		}, [] );
		var postType = useSelect( function ( select ) {
			return select( 'core/editor' ).getCurrentPostType();
		}, [] );

		var schemaState = useState( initialSchema );
		var schema = schemaState[0];
		var setSchema = schemaState[1];

		useEffect( function () {
			if ( ! postType || ! config.restUrl ) {
				return;
			}
			var url = config.restUrl + encodeURIComponent( postType ) + '?post_id=' + encodeURIComponent( postId || 0 );
			window.fetch( url, {
				headers: {
					'X-WP-Nonce': config.restNonce || ''
				}
			} )
				.then( function ( response ) {
					return response.json();
				} )
				.then( function ( data ) {
					if ( data && Array.isArray( data.groups ) ) {
						setSchema( data );
					}
				} )
				.catch( function () {
					// Keep localized schema.
				} );
		}, [ postType, postId, setSchema ] );

		var groups = schema.groups || [];
		if ( ! groups.length ) {
			return null;
		}

		return createElement(
			Fragment,
			null,
			groups.map( function ( group ) {
				return createElement( GroupPanel, { key: group.key, group: group } );
			} )
		);
	}

	wp.plugins.registerPlugin( 'wpps-custom-fields-sidebar', {
		render: CustomFieldsSidebar,
		icon: 'list-view'
	} );
}( window.wp, window.wppsCfEditor ) );
