import './style.scss';

import classnames from 'classnames/dedupe';

import {
	__experimentalUnitControl,
	BaseControl,
	CheckboxControl,
	Notice,
	PanelBody,
	RadioControl,
	RangeControl,
	TextareaControl,
	TextControl,
	ToggleControl,
	Tooltip,
	UnitControl as __stableUnitControl,
} from '@wordpress/components';
import {
	Component,
	RawHTML,
	useEffect,
	useRef,
	useState,
} from '@wordpress/element';
import { applyFilters } from '@wordpress/hooks';
import { __ } from '@wordpress/i18n';

import controlConditionCheck from '../../utils/control-condition-check';
import controlGetValue from '../../utils/control-get-value';
import {
	convertLegacyToModern,
	convertModernToLegacy,
} from '../../utils/convert-legacy-attributes';
import { maybeDecode, maybeEncode } from '../../utils/encode-decode';
import getControlNameClassName from '../../utils/get-control-name-class-name';
import AlignControl from '../align-control';
import AspectRatio from '../aspect-ratio';
import ClassesTree from '../classes-tree';
import CodeEditor from '../code-editor';
import CollapseControl from '../collapse-control';
import ColorPicker from '../color-picker';
import DatePicker from '../date-picker';
import ElementsSelector from '../elements-selector';
import GalleryControl from '../gallery-control';
import IconsSelector from '../icons-selector';
import NavigatorControl from '../navigator-control';
import ProNote from '../pro-note';
import SelectControl from '../select-control';
import SortableControl from '../sortable-control';
import TabsControl from '../tabs-control';
import TilesSelector from '../tiles-selector';
import ToggleGroupControl, {
	ToggleGroupButtonsControl,
} from '../toggle-group-control';
import ToggleModal from '../toggle-modal';

const UnitControl = __stableUnitControl || __experimentalUnitControl;

const {
	controls: registeredControls,
	controls_categories: registeredControlsCategories,
	plugin_version: pluginVersion,
} = window.VPGutenbergVariables;

const openedCategoriesCache = {};

/**
 * Component Class
 */
class ControlsRender extends Component {
	render() {
		const {
			category,
			categoryToggle = true,
			categoryInitialOpen = false,
			controls,
			clientId,
			isSetupWizard,
			isModernBlock = false,
			showPanel = true,
		} = this.props;

		if ( ! this.props.attributes ) {
			return null;
		}

		// Convert attributes if legacy controls used inside modern blocks.
		const attributes = isModernBlock
			? convertModernToLegacy( this.props.attributes )
			: this.props.attributes;
		const setAttributes = ( newAttrs ) => {
			if ( isModernBlock ) {
				newAttrs = convertLegacyToModern( newAttrs );

				Object.keys( newAttrs ).forEach( ( key ) => {
					if ( typeof newAttrs[ key ] === 'object' ) {
						newAttrs[ key ] = {
							...this.props.attributes[ key ],
							...newAttrs[ key ],
						};
					}
				} );
			}

			this.props.setAttributes( newAttrs );
		};

		// content source conditions.
		if (
			/^content-source-/g.test( category ) &&
			category !== 'content-source-general' &&
			`content-source-${ attributes.content_source }` !== category
		) {
			return null;
		}

		const usedControls = controls || registeredControls;
		const result = [];

		Object.keys( usedControls ).forEach( ( name ) => {
			const control = usedControls[ name ];

			if (
				category &&
				( ! control.category || category !== control.category )
			) {
				return;
			}

			const controlData = applyFilters(
				'vpf.editor.controls-render-data',
				{
					attributes,
					setAttributes,
					onChange: ( val ) => {
						const newAttrs = applyFilters(
							'vpf.editor.controls-on-change',
							{ [ control.name ]: val },
							control,
							val,
							attributes
						);
						setAttributes( newAttrs );
					},
					...control,
				}
			);

			// Conditions check.
			if ( ! ControlsRender.AllowRender( controlData, isSetupWizard ) ) {
				return;
			}

			result.push(
				applyFilters(
					'vpf.editor.controls-render',
					<ControlsRender.Control
						key={ `control-${ control.name }-${ control.label }` }
						{ ...controlData }
						clientId={ clientId }
						isSetupWizard={ isSetupWizard }
						renderProps={ this.props }
					/>,
					controlData,
					this.props
				)
			);
		} );

		let categoryTitle = categoryToggle ? category : false;
		let categoryIcon = false;
		let categoryPro = false;
		let categoryOpened = categoryInitialOpen || ! categoryToggle;

		if (
			categoryToggle &&
			typeof registeredControlsCategories[ category ] !== 'undefined'
		) {
			categoryTitle = registeredControlsCategories[ category ].title;
			categoryIcon =
				registeredControlsCategories[ category ].icon || false;
			categoryPro = !! registeredControlsCategories[ category ].is_pro;

			if ( typeof openedCategoriesCache[ category ] === 'undefined' ) {
				openedCategoriesCache[ category ] =
					registeredControlsCategories[ category ].is_opened || false;
			}
			categoryOpened =
				categoryOpened || openedCategoriesCache[ category ];
		}

		if ( isSetupWizard ) {
			return result.length ? (
				<div className="vpf-setup-wizard-panel">{ result }</div>
			) : (
				''
			);
		}

		if ( ! showPanel ) {
			return result.length ? result : '';
		}

		return result.length ? (
			<PanelBody
				title={
					categoryTitle ? (
						<>
							{ categoryIcon ? (
								<span className="vpf-control-category-title-icon">
									<RawHTML>{ categoryIcon }</RawHTML>
								</span>
							) : null }
							<span>{ categoryTitle }</span>
							{ categoryPro ? (
								<span className="vpf-control-category-title-pro">
									{ __( 'PRO', 'visual-portfolio' ) }
								</span>
							) : (
								''
							) }
						</>
					) : (
						false
					)
				}
				onToggle={ () => {
					openedCategoriesCache[ category ] = ! categoryOpened;
				} }
				initialOpen={ categoryOpened }
				scrollAfterOpen
			>
				{ result }
			</PanelBody>
		) : (
			''
		);
	}
}

/**
 * Render Single Control.
 *
 * @param {Object} props - control props.
 *
 * @return {JSX} control.
 */
ControlsRender.Control = function ( props ) {
	const { attributes, onChange, isSetupWizard } = props;
	const $ref = useRef();
	const [ positionInGroup, setPositionInGroup ] = useState( '' );

	const controlVal = controlGetValue( props.name, attributes );

	useEffect( () => {
		if ( props.group && $ref.current ) {
			const $element = $ref.current.parentElement.parentElement;
			let $prevSibling = $element.previousElementSibling;
			let $nextSibling = $element.nextElementSibling;

			// Skip separator.
			while (
				$prevSibling &&
				$prevSibling.classList.contains( 'vpf-control-group-separator' )
			) {
				$prevSibling = $prevSibling.previousElementSibling;
			}
			while (
				$nextSibling &&
				$nextSibling.classList.contains( 'vpf-control-group-separator' )
			) {
				$nextSibling = $nextSibling.nextElementSibling;
			}

			const isGroupEnabled =
				( $prevSibling &&
					$prevSibling.classList.contains(
						`vpf-control-group-${ props.group }`
					) ) ||
				( $nextSibling &&
					$nextSibling.classList.contains(
						`vpf-control-group-${ props.group }`
					) );
			const isStart =
				$prevSibling &&
				! $prevSibling.classList.contains(
					`vpf-control-group-${ props.group }`
				) &&
				$prevSibling.classList.contains( `vpf-control-wrap` );
			const isEnd =
				$nextSibling &&
				! $nextSibling.classList.contains(
					`vpf-control-group-${ props.group }`
				) &&
				$nextSibling.classList.contains( `vpf-control-wrap` );

			let newPosition = '';

			if ( ! isGroupEnabled ) {
				// skip
			} else if ( isStart ) {
				newPosition = 'start';
			} else if ( isEnd ) {
				newPosition = 'end';
			}

			if ( positionInGroup !== newPosition ) {
				setPositionInGroup( newPosition );
			}
		}
	}, [ $ref, props.group, controlVal, positionInGroup ] );

	// Conditions check.
	if ( ! ControlsRender.AllowRender( props, isSetupWizard ) ) {
		return null;
	}

	let renderControl = '';
	let renderControlLabel = props.label;
	let renderControlAfter = '';
	let renderControlHelp = props.description ? (
		<RawHTML className="components-base-control__help">
			{ props.description }
		</RawHTML>
	) : null;
	let renderControlClassName = classnames(
		'vpf-control-wrap',
		`vpf-control-wrap-${ props.type }`,
		getControlNameClassName( props.name ),
		props.className
	);

	if ( props.group ) {
		renderControlClassName = classnames(
			renderControlClassName,
			'vpf-control-with-group',
			`vpf-control-group-${ props.group }`,
			positionInGroup
				? `vpf-control-group-position-${ positionInGroup }`
				: false
		);
	}

	const categoryControlOptions = [];

	// Check if category is empty.
	if (
		( props.type === 'category_tabs' ||
			props.type === 'category_toggle_group' ||
			props.type === 'category_collapse' ||
			props.type === 'category_navigator' ) &&
		props.options &&
		props.options.length
	) {
		props.options.forEach( ( opt ) => {
			const isEmpty = ControlsRender.isCategoryEmpty( {
				...props.renderProps,
				category: opt.category,
				categoryToggle: false,
			} );

			if ( ! isEmpty ) {
				categoryControlOptions.push( opt );
			}
		} );
	}

	// Specific controls.
	switch ( props.type ) {
		case 'category_tabs':
			if ( categoryControlOptions.length ) {
				renderControl = (
					<TabsControl
						controlName={ props.name }
						options={ categoryControlOptions }
						key={ categoryControlOptions }
					>
						{ ( tab ) => {
							return (
								<ControlsRender
									{ ...props.renderProps }
									category={ tab.name }
									categoryToggle={ false }
								/>
							);
						} }
					</TabsControl>
				);
			} else {
				renderControl = null;
			}

			break;
		case 'category_toggle_group':
			if ( categoryControlOptions.length ) {
				renderControl = (
					<ToggleGroupControl
						controlName={ props.name }
						options={ categoryControlOptions }
						key={ categoryControlOptions }
						__next40pxDefaultSize
						__nextHasNoMarginBottom
					>
						{ ( group ) => {
							return (
								<ControlsRender
									{ ...props.renderProps }
									category={ group.category }
									categoryToggle={ false }
								/>
							);
						} }
					</ToggleGroupControl>
				);
			} else {
				renderControl = null;
			}

			break;
		case 'category_collapse':
			if ( categoryControlOptions.length ) {
				renderControl = (
					<CollapseControl
						controlName={ props.name }
						initialOpen={ props.initialOpen }
						options={ categoryControlOptions }
						key={ categoryControlOptions }
					>
						{ ( tab ) => {
							return (
								<ControlsRender
									{ ...props.renderProps }
									category={ tab.category }
									categoryToggle={ false }
								/>
							);
						} }
					</CollapseControl>
				);
			} else {
				renderControl = null;
			}

			break;
		case 'category_navigator':
			if ( categoryControlOptions.length ) {
				renderControl = (
					<NavigatorControl
						controlName={ props.name }
						options={ categoryControlOptions }
						key={ categoryControlOptions }
					>
						{ ( tab ) => {
							return (
								<ControlsRender
									{ ...props.renderProps }
									category={ tab.category }
									categoryToggle={ false }
								/>
							);
						} }
					</NavigatorControl>
				);
			} else {
				renderControl = null;
			}

			break;
		case 'html':
			renderControl = <RawHTML>{ props.default }</RawHTML>;
			break;
		case 'select':
		case 'select2':
			renderControl = (
				<SelectControl
					controlName={ props.name }
					callback={ props.value_callback }
					attributes={ attributes }
					value={ controlVal }
					options={ props.options || {} }
					onChange={ ( val ) => onChange( val ) }
					isSearchable={ props.searchable }
					isMultiple={ props.multiple }
					isCreatable={ props.creatable || props.tags }
				/>
			);
			break;
		case 'buttons':
			renderControl = (
				<ToggleGroupButtonsControl
					label={ props.label || props.name }
					hideLabelFromVision
					value={ controlVal }
					options={ props.options || {} }
					onChange={ ( val ) => {
						const keys = Object.keys( props.options || {} );
						const match = keys.find(
							( k ) => String( k ) === String( val )
						);
						onChange(
							match !== undefined ? match : val
						);
					} }
				/>
			);
			break;
		case 'icons_selector':
			renderControl = (
				<IconsSelector
					controlName={ props.name }
					callback={ props.value_callback }
					attributes={ attributes }
					value={ controlVal }
					options={ props.options }
					onChange={ ( val ) => onChange( val ) }
					columns={ props.columns }
					collapseRows={ props.collapse_rows || false }
					isSetupWizard={ isSetupWizard }
				/>
			);
			break;
		case 'tiles_selector':
			renderControl = (
				<TilesSelector
					value={ controlVal }
					options={ props.options }
					onChange={ ( val ) => onChange( val ) }
				/>
			);
			break;
		case 'elements_selector':
			renderControl = (
				<ElementsSelector
					value={ controlVal }
					locations={ props.locations }
					options={ props.options }
					onChange={ ( val ) => onChange( val ) }
					props={ props }
				/>
			);
			break;
		case 'align': {
			renderControl = (
				<AlignControl
					value={ controlVal }
					options={ props.options || 'horizontal' }
					onChange={ ( val ) => onChange( val ) }
				/>
			);
			break;
		}
		case 'aspect_ratio': {
			renderControl = (
				<AspectRatio
					value={ controlVal }
					onChange={ ( val ) => onChange( val ) }
				/>
			);
			break;
		}
		case 'gallery':
			renderControl = (
				<GalleryControl
					imageControls={ props.image_controls }
					focalPoint={ props.focal_point }
					attributes={ attributes }
					name={ props.name }
					value={ controlVal }
					onChange={ ( val ) => onChange( val ) }
					isSetupWizard={ isSetupWizard }
				/>
			);
			break;
		case 'code_editor':
			renderControl = (
				<CodeEditor
					value={
						props.encode ? maybeDecode( controlVal ) : controlVal
					}
					mode={ props.mode }
					maxLines={ props.max_lines }
					minLines={ props.min_lines }
					codePlaceholder={ props.code_placeholder }
					onChange={ ( val ) =>
						onChange( props.encode ? maybeEncode( val ) : val )
					}
				/>
			);

			if ( props.allow_modal ) {
				renderControlAfter = (
					<ToggleModal
						modalTitle={ __( 'Custom CSS', 'visual-portfolio' ) }
						buttonLabel={ __(
							'Open in Modal',
							'visual-portfolio'
						) }
						size="md"
					>
						<BaseControl
							id={ `vpf-custom-css-${
								props.label || props.name
							}` }
							label={ props.label }
							help={
								props.description ? (
									<RawHTML>{ props.description }</RawHTML>
								) : (
									false
								)
							}
							className={ classnames(
								'vpf-control-wrap',
								`vpf-control-wrap-${ props.type }`
							) }
							__nextHasNoMarginBottom
						>
							<div>{ renderControl }</div>
						</BaseControl>
						{ props.classes_tree ? (
							<>
								<p>
									{ __(
										'Classes Tree:',
										'visual-portfolio'
									) }
								</p>
								<ClassesTree { ...props } />
							</>
						) : (
							''
						) }
					</ToggleModal>
				);
			}
			break;
		case 'range':
			renderControl = (
				<RangeControl
					min={ props.min }
					max={ props.max }
					step={ props.step }
					value={ parseFloat( controlVal ) }
					onChange={ ( val ) => onChange( parseFloat( val ) ) }
					__next40pxDefaultSize
					__nextHasNoMarginBottom
				/>
			);
			break;
		case 'toggle':
			renderControl = (
				<ToggleControl
					checked={ controlVal }
					label={ props.alongside }
					onChange={ ( val ) => onChange( val ) }
					__nextHasNoMarginBottom
				/>
			);
			break;
		case 'checkbox':
			renderControl = (
				<CheckboxControl
					checked={ controlVal }
					label={ props.alongside }
					onChange={ ( val ) => onChange( val ) }
					__nextHasNoMarginBottom
				/>
			);
			break;
		case 'radio':
			renderControl = (
				<RadioControl
					label={ renderControlLabel }
					selected={ controlVal }
					options={ Object.keys( props.options || {} ).map(
						( val ) => ( {
							label: props.options[ val ],
							value: val,
						} )
					) }
					onChange={ ( option ) => onChange( option ) }
				/>
			);
			renderControlLabel = false;
			break;
		case 'color':
			renderControl = (
				<ColorPicker
					label={ renderControlLabel }
					value={ controlVal }
					alpha={ props.alpha }
					gradient={ props.gradient }
					onChange={ ( val ) => onChange( val ) }
				/>
			);
			renderControlLabel = false;
			break;
		case 'date':
			renderControl = (
				<DatePicker
					value={ controlVal }
					onChange={ ( val ) => onChange( val ) }
				/>
			);
			break;
		case 'textarea':
			renderControl = (
				<TextareaControl
					label={ renderControlLabel }
					value={ controlVal }
					onChange={ ( val ) => onChange( val ) }
					__nextHasNoMarginBottom
				/>
			);
			renderControlLabel = false;
			break;
		case 'url':
			renderControl = (
				<TextControl
					label={ renderControlLabel }
					type="url"
					value={ controlVal }
					onChange={ ( val ) => onChange( val ) }
					__next40pxDefaultSize
					__nextHasNoMarginBottom
				/>
			);
			renderControlLabel = false;
			break;
		case 'number':
			renderControl = (
				<TextControl
					label={ renderControlLabel }
					type="number"
					min={ props.min }
					max={ props.max }
					step={ props.step }
					value={ parseFloat( controlVal ) }
					onChange={ ( val ) => onChange( parseFloat( val ) ) }
					__next40pxDefaultSize
					__nextHasNoMarginBottom
				/>
			);
			renderControlLabel = false;
			break;
		case 'unit':
			renderControl = (
				<UnitControl
					label={ renderControlLabel }
					value={ controlVal }
					onChange={ ( val ) => onChange( val ) }
					labelPosition="edge"
					__unstableInputWidth="70px"
					__next40pxDefaultSize
					__nextHasNoMarginBottom
				/>
			);
			renderControlLabel = false;
			break;
		case 'hidden':
			renderControl = (
				<TextControl
					type="hidden"
					value={ controlVal }
					onChange={ ( val ) => onChange( val ) }
					__next40pxDefaultSize
					__nextHasNoMarginBottom
				/>
			);
			break;
		case 'notice':
			renderControl = renderControlHelp ? (
				<Notice status={ props.status } isDismissible={ false }>
					{ renderControlHelp }
				</Notice>
			) : (
				''
			);
			renderControlHelp = false;
			break;
		case 'pro_note':
			renderControl = (
				<ProNote title={ renderControlLabel }>
					{ renderControlHelp || '' }
					<ProNote.Button
						target="_blank"
						rel="noopener noreferrer"
						href={ `https://www.visualportfolio.com/pricing/?utm_source=plugin&utm_medium=block_settings&utm_campaign=${ props.name }&utm_content=${ pluginVersion }` }
					>
						{ __( 'Go Pro', 'visual-portfolio' ) }
					</ProNote.Button>
				</ProNote>
			);
			renderControlLabel = false;
			renderControlHelp = false;
			break;
		case 'sortable':
			renderControl = (
				<SortableControl
					label={ renderControlLabel }
					controlName={ props.name }
					attributes={ attributes }
					value={ controlVal }
					options={ props.options || {} }
					defaultVal={ props.default || {} }
					allowDisablingOptions={
						props.allow_disabling_options || false
					}
					onChange={ ( val ) => onChange( val ) }
				/>
			);
			break;
		default:
			renderControl = (
				<TextControl
					label={ renderControlLabel }
					value={ controlVal }
					onChange={ ( val ) => onChange( val ) }
					__next40pxDefaultSize
					__nextHasNoMarginBottom
				/>
			);
			renderControlLabel = false;
	}

	// Hint.
	if ( props.hint ) {
		renderControl = (
			<Tooltip text={ props.hint } position={ props.hint_place }>
				<div>{ renderControl }</div>
			</Tooltip>
		);
	}

	// TODO: use this filter for custom controls.
	const data = applyFilters(
		'vpf.editor.controls-render-inner-data',
		{
			renderControl,
			renderControlLabel,
			renderControlHelp,
			renderControlAfter,
			renderControlClassName,
		},
		{ props, controlVal }
	);

	// Prevent rendering.
	if ( data.renderControl === null ) {
		return null;
	}

	return (
		<>
			{ positionInGroup === 'start' ? (
				<div className="vpf-control-group-separator" />
			) : null }
			<BaseControl
				id={ `vpf-control-group-${ props.name }` }
				label={ data.renderControlLabel }
				className={ data.renderControlClassName }
				__nextHasNoMarginBottom
			>
				<div ref={ $ref }>{ data.renderControl }</div>
				{ data.renderControlHelp }
			</BaseControl>
			{ data.renderControlAfter }
			{ positionInGroup === 'end' ? (
				<div className="vpf-control-group-separator" />
			) : null }
		</>
	);
};

/**
 * Check if control is allowed to rendering.
 *
 * @param props
 * @param isSetupWizard
 */
ControlsRender.AllowRender = function ( props, isSetupWizard = false ) {
	if ( props.skip ) {
		return false;
	}

	if (
		props.condition &&
		props.condition.length &&
		! controlConditionCheck( props.condition, props.attributes )
	) {
		return false;
	}

	if ( isSetupWizard && ! props.setup_wizard ) {
		return false;
	}

	return true;
};

/**
 * Check if category does not contains controls.
 *
 * @param props
 */
ControlsRender.isCategoryEmpty = function ( props ) {
	const { category, attributes, setAttributes, controls, isSetupWizard } =
		props;

	const usedControls = controls || registeredControls;
	let isEmpty = true;

	Object.keys( usedControls ).forEach( ( name ) => {
		if ( ! isEmpty ) {
			return;
		}

		const control = usedControls[ name ];

		if (
			category &&
			( ! control.category || category !== control.category )
		) {
			return;
		}

		const controlData = applyFilters( 'vpf.editor.controls-render-data', {
			attributes,
			setAttributes,
			onChange: ( val ) => {
				const newAttrs = applyFilters(
					'vpf.editor.controls-on-change',
					{ [ control.name ]: val },
					control,
					val,
					attributes
				);
				setAttributes( newAttrs );
			},
			...control,
		} );

		// Conditions check.
		if ( ! ControlsRender.AllowRender( controlData, isSetupWizard ) ) {
			return;
		}

		isEmpty = false;
	} );

	return isEmpty;
};

export default ControlsRender;
