import './style.scss';

import classnames from 'classnames/dedupe';

import {
	__experimentalUnitControl,
	BaseControl,
	Button,
	ButtonGroup,
	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 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 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}`
	);

	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 = (
				<ButtonGroup>
					{Object.keys(props.options || {}).map((val) => (
						<Button
							variant={controlVal === val ? 'primary' : ''}
							isPressed={controlVal === val}
							key={val}
							onClick={() => onChange(val)}
						>
							{props.options[val]}
						</Button>
					))}
				</ButtonGroup>
			);
			break;
		case 'icons_selector':
			renderControl = (
				<IconsSelector
					controlName={props.name}
					callback={props.value_callback}
					attributes={attributes}
					value={controlVal}
					options={props.options}
					onChange={(val) => onChange(val)}
					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;
