import {
	createElement,
	Fragment,
	Component,
	useState,
	useRef,
	useEffect,
	RawHTML,
} from '@wordpress/element'
import { BaseControl } from '@wordpress/components'
import classnames from 'classnames'
import ResponsiveControls, {
	maybePromoteScalarValueIntoResponsive,
	isOptionEnabledFor,
	getValueForDevice,
	isOptionResponsiveFor,
} from '../customizer/components/responsive-controls'
import deepEqual from 'deep-equal'
import { normalizeCondition, matchValuesWithCondition } from 'match-conditions'
import { __ } from 'ct-i18n'
import { getOptionLabelFor } from './helpers/get-label'
import ctEvents from 'ct-events'

import { getCurrentDevice } from '../customizer/components/useDeviceManager'

import { mutateResponsiveValueWithScalar } from './helpers/mutate-responsive-value'

const CORE_OPTIONS_CONTEXT = require.context('./options/', false, /\.js$/)
CORE_OPTIONS_CONTEXT.keys().forEach(CORE_OPTIONS_CONTEXT)

const hasCoreOptionModifier = (type) => {
	let index = CORE_OPTIONS_CONTEXT.keys()
		.map((module) => module.replace(/^\.\//, '').replace(/\.js$/, ''))
		.indexOf(type)

	return index > -1 && CORE_OPTIONS_CONTEXT.keys()[index]
}

export const capitalizeFirstLetter = (str) => {
	str = str == null ? '' : String(str)
	return str.charAt(0).toUpperCase() + str.slice(1)
}

const DefaultOptionComponent = ({ option }) => {
	return <div>Unimplemented option: {option.type}</div>
}

export const getOptionFor = (option) => {
	const dynamicOptionTypes = {}
	ctEvents.trigger('blocksy:options:register', dynamicOptionTypes)

	if (hasCoreOptionModifier(option.type)) {
		return CORE_OPTIONS_CONTEXT(hasCoreOptionModifier(option.type)).default
	}

	if (dynamicOptionTypes[option.type]) {
		return dynamicOptionTypes[option.type]
	}

	return DefaultOptionComponent
}

export const optionWithDefault = ({ option, value }) =>
	value === undefined ? option.value : value

const GenericOptionType = ({
	option,
	value,
	values,
	onChange,
	onChangeFor,
	hasRevertButton,
	id,
	purpose,
}) => {
	const [liftedOptionState, setLiftedOptionState] = useState(null)

	let maybeGutenbergDevice = null

	const childComponentRef = useRef(null)

	if (wp.data && wp.data.useSelect) {
		maybeGutenbergDevice = wp.data.useSelect((select) => {
			if (!select('core/edit-post')) {
				return null
			}

			return getCurrentDevice(select)
		})
	}

	const getInitialDevice = () => {
		return getCurrentDevice()
	}

	const [device, setInnerDevice] = useState(getInitialDevice())

	const listener = () => {
		setInnerDevice(getInitialDevice())
	}

	const ctEventsListener = ({ device }) => {
		setInnerDevice(device)
	}

	const setDevice = (device) => {
		ctEvents.trigger('ct:options:device:update', { device })
		setInnerDevice(device)

		if (wp.customize && wp.customize.previewedDevice) {
			wp.customize.previewedDevice.set(device)
		}

		if (
			wp.data &&
			wp.data.dispatch &&
			wp.data.dispatch('core/edit-post') &&
			wp.data.dispatch('core/edit-post')
				.__experimentalSetPreviewDeviceType
		) {
			wp.data
				.dispatch('core/edit-post')
				.__experimentalSetPreviewDeviceType(
					device.replace(/\w/, (c) => c.toUpperCase())
				)
		}
	}

	useEffect(() => {
		if (maybeGutenbergDevice) {
			setInnerDevice(maybeGutenbergDevice.toLowerCase())
		}
	}, [maybeGutenbergDevice])

	useEffect(() => {
		if (option.type !== 'ct-typography') {
			if (!isOptionResponsiveFor(option) && !option.markAsAutoFor) {
				return
			}
		}

		if (wp.customize && wp.customize.previewedDevice) {
			setTimeout(() => wp.customize.previewedDevice.bind(listener), 1000)
		}

		ctEvents.on('ct:options:device:update', ctEventsListener)

		setInnerDevice(getInitialDevice())

		return () => {
			if (option.type !== 'ct-typography') {
				if (!isOptionResponsiveFor(option)) {
					return
				}
			}

			if (wp.customize && wp.customize.previewedDevice) {
				wp.customize.previewedDevice.unbind(listener)
			}

			ctEvents.off('ct:options:device:update', ctEventsListener)
		}
	}, [])

	let OptionComponent = getOptionFor(option)
	let BeforeOptionContent = { content: null, option }

	ctEvents.trigger('blocksy:options:before-option', BeforeOptionContent)

	const globalResponsiveValue = maybePromoteScalarValueIntoResponsive(
		optionWithDefault({ value, option }),
		isOptionResponsiveFor(option)
	)

	const valueWithResponsive = isOptionResponsiveFor(option, {
		ignoreHidden: true,
	})
		? getValueForDevice({ option, value: globalResponsiveValue, device })
		: globalResponsiveValue

	const onChangeWithMobileBridge = (value) => {
		if (option.triggerRefreshOnChange) {
			wp.customize &&
				wp.customize.previewer &&
				wp.customize.previewer.refresh()
		}

		if (
			option.switchDeviceOnChange &&
			wp.customize &&
			wp.customize.previewedDevice() !== option.switchDeviceOnChange
		) {
			wp.customize.previewedDevice.set(option.switchDeviceOnChange)
		}

		if (
			option.sync &&
			(Object.keys(option.sync).length > 0 ||
				Array.isArray(option.sync)) &&
			wp.customize &&
			wp.customize.previewer
		) {
			let ids = (
				Array.isArray(option.sync) ? option.sync : [option.sync]
			).map((sync) => sync.id || option.id)

			if (ids.length > 1) {
				const maybeMainSync = (
					Array.isArray(option.sync) ? option.sync : [option.sync]
				).find(({ id }) => (id || option.id) === option.id)

				if (maybeMainSync) {
					ids = [option.id]
				}
			}

			wp.customize.previewer.send('ct:sync:refresh_partial', {
				id: ids,
				option,
				shouldSkip: !!option.sync.shouldSkip,
			})
		}

		onChange(value)
	}

	const onChangeWithResponsiveBridge = (scalarValue) => {
		const responsiveValue = maybePromoteScalarValueIntoResponsive(
			optionWithDefault({ value, option }),
			isOptionResponsiveFor(option)
		)

		let newValue = scalarValue

		if (isOptionResponsiveFor(option, { ignoreHidden: true })) {
			const isTabletSkipping =
				device === 'tablet' &&
				isOptionEnabledFor('tablet', option.responsive) === 'skip'

			newValue = mutateResponsiveValueWithScalar({
				scalarValue,
				responsiveValue,
				device,

				...(isTabletSkipping
					? {
							devices: ['desktop', 'mobile'],
					  }
					: {}),
			})
		}

		onChangeWithMobileBridge(newValue)
	}

	/**
	 * Handle transparent components
	 */
	if (!OptionComponent) {
		return <div>Unimplemented option: {option.type}</div>
	}

	let renderingConfig = { design: true, label: true, wrapperAttr: {} }
	let LabelToolbar = () => null
	let OptionMetaWrapper = null
	let ControlEnd = () => null
	let sectionClassName = () => ({})

	renderingConfig = {
		...renderingConfig,
		...(OptionComponent.renderingConfig || {}),
	}

	if (option.design) {
		renderingConfig.design = option.design
	}

	if (typeof renderingConfig.design === 'function') {
		renderingConfig.design = renderingConfig.design({
			option,
			value: valueWithResponsive,
		})
	}

	if (OptionComponent.LabelToolbar) {
		LabelToolbar = OptionComponent.LabelToolbar
	}

	if (OptionComponent.ControlEnd) {
		ControlEnd = OptionComponent.ControlEnd
	}

	if (OptionComponent.MetaWrapper) {
		OptionMetaWrapper = OptionComponent.MetaWrapper
	}

	if (OptionComponent.sectionClassName) {
		sectionClassName = OptionComponent.sectionClassName
	}

	let supportedPurposes = ['default']

	if (OptionComponent.supportedPurposes) {
		supportedPurposes = OptionComponent.supportedPurposes
	}

	let actualPurpose = purpose

	if (option.purpose && supportedPurposes.indexOf(option.purpose) !== -1) {
		actualPurpose = option.purpose
	}

	let maybeLabel = getOptionLabelFor({
		id,
		option,
		values,
		renderingConfig,
	})

	let OptionComponentWithoutDesign = (
		<Fragment>
			{BeforeOptionContent && BeforeOptionContent.content}
			<OptionComponent
				key={id}
				{...{
					...(option.type === 'ct-slider'
						? {
								ref: (c) => {
									if (c) {
										childComponentRef.current = c
									}
								},
						  }
						: {}),
					liftedOptionStateDescriptor: {
						liftedOptionState,
						setLiftedOptionState,
					},
					option: {
						...option,
						value: isOptionResponsiveFor(option, {
							ignoreHidden: true,
						})
							? getValueForDevice({
									device,
									option,
									value: maybePromoteScalarValueIntoResponsive(
										option.value || ''
									),
							  })
							: maybePromoteScalarValueIntoResponsive(
									option.value || '',
									isOptionResponsiveFor(option)
							  ),
					},
					value: valueWithResponsive,
					id,
					values,
					onChangeFor,
					device,
					onChange: onChangeWithResponsiveBridge,
					purpose: actualPurpose,
					maybeLabel,
				}}
			/>
		</Fragment>
	)

	if (!renderingConfig.design || renderingConfig.design === 'none') {
		return OptionComponentWithoutDesign
	}

	const RevertButton = () => {
		let computeOptionValue = renderingConfig.computeOptionValue

		if (!computeOptionValue) {
			computeOptionValue = (o, { option, values }) => o
		}

		if (option.type === 'ct-panel' && !option.switch) {
			return null
		}

		return (
			((option.type !== 'ct-image-picker' &&
				option.type !== 'ct-layers' &&
				hasRevertButton &&
				!option.disableRevertButton) ||
				option.forcedRevertButton) && (
				<button
					type="button"
					disabled={deepEqual(
						Object.keys(option).indexOf('revertDefaultValue') > -1
							? option.revertDefaultValue
							: computeOptionValue(option.value, {
									option,
									values,
							  }),

						// Important: all implementations should add handling
						// for undefined and skip revert in that case.
						renderingConfig.getValueForRevert
							? renderingConfig.getValueForRevert({
									value,
									option,
									values,
									device,
							  })
							: optionWithDefault({
									value,
									option,
							  })
					)}
					className="ct-revert"
					onClick={(e) => {
						e.stopPropagation()

						let revertValue = option.value

						if (
							Object.keys(option).indexOf('revertDefaultValue') >
							-1
						) {
							revertValue = option.revertDefaultValue
						}

						if (childComponentRef && childComponentRef.current) {
							childComponentRef.current.handleOptionRevert()
						}

						setLiftedOptionState(null)

						if (renderingConfig.performRevert) {
							renderingConfig.performRevert({
								onChangeFor,
								option,
							})
						}

						onChangeWithMobileBridge(revertValue)
					}}>
					<svg fill="currentColor" viewBox="0 0 35 35">
						<path d="M17.5,26L17.5,26C12.8,26,9,22.2,9,17.5v0C9,12.8,12.8,9,17.5,9h0c4.7,0,8.5,3.8,8.5,8.5v0C26,22.2,22.2,26,17.5,26z" />
						<polygon points="34.5,30.2 21.7,17.5 34.5,4.8 30.2,0.5 17.5,13.3 4.8,0.5 0.5,4.8 13.3,17.5 0.5,30.2 4.8,34.5 17.5,21.7 30.2,34.5 " />
					</svg>
				</button>
			)
		)
	}

	let maybeDesc =
		Object.keys(option).indexOf('desc') === -1 ? false : option.desc

	let maybeLink =
		Object.keys(option).indexOf('link') === -1 ? false : option.link || ' '

	const actualDesignType =
		typeof renderingConfig.design === 'boolean'
			? 'block'
			: renderingConfig.design

	if (purpose === 'taxonomy') {
		return (
			<tr className="form-field ct-term-screen-edit">
				<th scope="row">
					{maybeLabel && (
						<label>
							{maybeLabel} <RevertButton />
						</label>
					)}
				</th>
				<td>
					{OptionComponentWithoutDesign}
					{maybeDesc && <p className="description">{maybeDesc}</p>}
				</td>
			</tr>
		)
	}

	if (purpose === 'gutenberg') {
		if (
			!supportedPurposes.includes('gutenberg') ||
			actualPurpose !== 'gutenberg'
		) {
			return (
				<BaseControl
					label={maybeLabel}
					className={classnames('ct-control-gutenberg', {
						[`ct-divider-${option.divider}`]: !!option.divider,
					})}
					help={maybeDesc ? <RawHTML>{maybeDesc}</RawHTML> : ''}>
					{OptionComponentWithoutDesign}
				</BaseControl>
			)
		}

		return (
			<BaseControl
				__nextHasNoMarginBottom
				className={classnames('ct-control-gutenberg', {
					[`ct-divider-${option.divider}`]: !!option.divider,
				})}>
				{OptionComponentWithoutDesign}
			</BaseControl>
		)
	}

	if (renderingConfig.design === 'compact') {
		return (
			<section {...(option.sectionAttr || {})}>
				{maybeLabel && <label>{maybeLabel}</label>}

				{((isOptionResponsiveFor(option) &&
					isOptionEnabledFor(device, option.responsive)) ||
					!isOptionResponsiveFor(option)) &&
					OptionComponentWithoutDesign}
				{maybeLink && (
					<a
						dangerouslySetInnerHTML={{
							__html: maybeLink,
						}}
						{...(option.linkAttr || {})}
					/>
				)}
			</section>
		)
	}

	const getActualOption = ({
		wrapperAttr: { className, ...additionalWrapperAttr } = {},
		...props
	} = {}) => {
		const { className: optionClassName, ...optionAdditionalWrapperAttr } =
			option.wrapperAttr || {}

		const { class: sectionClassNameStr, ...sectionAttr } =
			option.sectionAttr || {}

		return (
			<Fragment>
				<div
					className={classnames(
						'ct-control',
						className,
						optionClassName,
						{}
					)}
					data-design={actualDesignType}
					{...(option.divider
						? { 'data-divider': option.divider }
						: {})}
					{...{
						...((isOptionResponsiveFor(option) &&
							!isOptionEnabledFor(device, option.responsive)) ||
						option.state === 'disabled'
							? { 'data-state': 'disabled' }
							: {}),
					}}
					{...{
						...optionAdditionalWrapperAttr,
						...additionalWrapperAttr,
					}}>
					<header {...(!maybeLabel ? { 'data-label': 'no' } : {})}>
						{maybeLabel && <label>{maybeLabel}</label>}
						<RevertButton />

						<LabelToolbar
							{...{
								option,
								value: valueWithResponsive,
								id,
								onChange: onChangeWithResponsiveBridge,
							}}
						/>

						{isOptionResponsiveFor(option, {
							ignoreHidden: true,
						}) &&
							actualDesignType.indexOf('block') > -1 &&
							!option.skipResponsiveControls && (
								<ResponsiveControls
									device={device}
									responsiveDescriptor={option.responsive}
									setDevice={setDevice}
								/>
							)}
					</header>

					{isOptionResponsiveFor(option) &&
						!isOptionEnabledFor(device, option.responsive) && (
							<div className="ct-notification" data-type="background:yellow">
								{option.disabledDeviceMessage ||
									__(
										"This option can't be edited for current device",
										'blocksy'
									)}
							</div>
						)}

					{((isOptionResponsiveFor(option) &&
						isOptionEnabledFor(device, option.responsive)) ||
						!isOptionResponsiveFor(option)) && (
						<Fragment>
							<section
								{...sectionAttr}
								className={classnames(
									{
										'ct-responsive-container':
											isOptionResponsiveFor(option, {
												ignoreHidden: true,
											}) && actualDesignType === 'inline',
									},
									sectionClassName({
										design: actualDesignType,
										option,
									}),
									sectionClassNameStr || ''
								)}>
								{isOptionResponsiveFor(option, {
									ignoreHidden: true,
								}) &&
									actualDesignType === 'inline' && (
										<ResponsiveControls
											device={device}
											responsiveDescriptor={
												option.responsive
											}
											setDevice={setDevice}
										/>
									)}
								{OptionComponentWithoutDesign}

								{maybeLink && (
									<a
										dangerouslySetInnerHTML={{
											__html: maybeLink,
										}}
										{...(option.linkAttr || {})}
									/>
								)}
							</section>

							<ControlEnd />

							{maybeDesc && (
								<div
									dangerouslySetInnerHTML={{
										__html: maybeDesc,
									}}
									className="ct-option-description"
								/>
							)}
						</Fragment>
					)}
				</div>
			</Fragment>
		)
	}

	return OptionMetaWrapper ? (
		<OptionMetaWrapper
			id={id}
			option={option}
			value={valueWithResponsive}
			onChangeFor={onChangeFor}
			values={values}
			getActualOption={getActualOption}
		/>
	) : (
		getActualOption()
	)
}

export default GenericOptionType
