import * as React from 'react'
import {__, _x} from '@wordpress/i18n'

import {
	useCallback,
} from '@wordpress/element'

import {
	ToggleControl,
	TextControl,
	BaseControl,
} from '@wordpress/components'

import {
	hasBlockSupport,
	getBlockSupport,
	createBlock,
	// @ts-expect-error
} from '@wordpress/blocks'

import {
	InspectorAdvancedControls,
	store as blockEditorStore,
	// @ts-expect-error
} from '@wordpress/block-editor'

import {
	createHigherOrderComponent,
	useDebounce,
} from '@wordpress/compose'

import {
	useSelect,
	useDispatch,
} from '@wordpress/data'

import {
	addFilter,
} from '@wordpress/hooks'

import {
	plus,
} from '@wordpress/icons'

import {
	ButtonGroup,
	Button,
} from '@ska/components'

import type {
	tBlock,
	tBlockEditProps,
	tBlockNameOrType,
	tBlockType,
} from '@ska/shared'

export interface AppenderValue {
	/** Type of appender to render. */
	type?: 'default' | 'hidden' | 'repeater'
	/** Repeater appender button text. */
	label?: string
	/** Whether repeater appender should strip content. */
	stripContent?: boolean
}

export interface AppenderAttributes {
	skaBlocksAppender?: AppenderValue
}

export const APPENDER_SUPPORT_KEY = 'skaBlocksAppender'
export const APPENDER_SUPPORT_DEFAULT_ENABLED = false
export const hasAppenderSupport = (block: tBlockNameOrType) => hasBlockSupport(block, APPENDER_SUPPORT_KEY, APPENDER_SUPPORT_DEFAULT_ENABLED)
const defaultLabel = _x('Add item', 'Repeater appender default label', 'ska-blocks')

/**
 * Default value can be provided in block `supports` attribute.
 *
 * @example
 * ```json
 * 	"supports": {
 * 		"skaBlocksAppender": {
 * 			"render": false
 * 		}
 * 	}
 * ```
 */
const getDefaultValue = (block: tBlockType) => {

	const support = getBlockSupport(block, APPENDER_SUPPORT_KEY, APPENDER_SUPPORT_DEFAULT_ENABLED)

	return {
		...(typeof support === 'object' && {
			...support,
		}),
	}
}

const withCustomAttributes = (settings: tBlockType) => {

	if(!hasAppenderSupport(settings)) {
		return settings
	}

	if(!settings.attributes.skaBlocksAppender) {

		const defaultValue = getDefaultValue(settings)

		Object.assign(settings.attributes, {
			skaBlocksAppender: {
				type: 'object',
				...(Object.keys(defaultValue).length > 0 && {
					default: defaultValue,
				}),
			},
		})
	}

	return settings
}

const useSupportsRepeater = (clientId: string) => {

	return useSelect(select => {

		const {
			getBlock,
		} = select(blockEditorStore)

		const block = getBlock(clientId) as tBlock

		return block.innerBlocks.length > 0
	}, [clientId])
}

const APPENDER_OPTIONS = [
	{
		label: _x('Default', 'Block appender type', 'ska-blocks'),
		value: 'default',
	},
	{
		label: _x('Repeater', 'Block appender type', 'ska-blocks'),
		value: 'repeater',
	},
	{
		label: _x('Disabled', 'Block appender type', 'ska-blocks'),
		value: 'hidden',
	},
]

const AppenderInspectorControls: React.FC<tBlockEditProps<AppenderAttributes>> = (props) => {

	const {
		clientId,
		attributes,
		setAttributes,
	} = props

	const {
		skaBlocksAppender = {},
	} = attributes

	const {
		type,
		stripContent = false,
		label = '',
	} = skaBlocksAppender

	const supportsRepeater = useSupportsRepeater(clientId)
	const isRepeater = type === 'repeater'

	return <>
		<InspectorAdvancedControls>
			<BaseControl __nextHasNoMarginBottom>
				<ButtonGroup.Radio
					label={__('Block appender', 'ska-blocks')}
					fluid
					options={supportsRepeater || isRepeater ? APPENDER_OPTIONS : APPENDER_OPTIONS.filter(({value}) => value !== 'repeater')}
					value={type || 'default'}
					onChange={(value = 'default') => setAttributes({skaBlocksAppender: {...skaBlocksAppender, type: (value === 'default' ? undefined : value) as AppenderValue['type']}})}
				/>
				{isRepeater && <>
					<ToggleControl
						label={__('Appender strip content', 'ska-blocks')}
						help={__(`Enable to strip content from blocks before repeating.`, 'ska-blocks')}
						checked={stripContent}
						onChange={() => setAttributes({skaBlocksAppender: {...skaBlocksAppender, stripContent: !stripContent}})}
						__nextHasNoMarginBottom
					/>
					<TextControl
						label={__('Appender label', 'ska-blocks')}
						help={__('Override repeater appender button text.', 'ska-blocks')}
						value={label}
						placeholder={defaultLabel}
						onChange={nextValue => setAttributes({skaBlocksAppender: {...skaBlocksAppender, label: nextValue}})}
						__nextHasNoMarginBottom
						__next40pxDefaultSize
					/>
				</>}
			</BaseControl>
		</InspectorAdvancedControls>
	</>
}

const withAppenderControls = createHigherOrderComponent(
	(BlockEdit: any) => (props: tBlockEditProps) => {
		if(hasAppenderSupport(props.name)) {
			return <>
				<AppenderInspectorControls {...props as any as tBlockEditProps<AppenderAttributes>} />
				<BlockEdit {...props} />
			</>
		}
		return <BlockEdit {...props} />
	},
	'withSkaBlocksAppender'
)

const RepeaterAppender: React.FC<tBlockEditProps<AppenderAttributes>> = props => {

	const {
		clientId,
		isSelected = false,
		attributes,
	} = props

	const {
		skaBlocksAppender = {},
	} = attributes

	const {
		label = defaultLabel,
		stripContent = false,
	} = skaBlocksAppender

	const {
		template,
		index,
		hasChildSelected,
	} = useSelect(select => {

		const {
			getBlockIndex,
			getBlock,
			hasSelectedInnerBlock,
		} = select(blockEditorStore)

		const block = getBlock(clientId) as tBlock
		const template = block.innerBlocks.length ? block.innerBlocks[block.innerBlocks.length - 1] : undefined
		const isAncestorOfSelectedBlock = hasSelectedInnerBlock(clientId, true)

		return {
			block,
			template,
			index: template ? getBlockIndex(template.clientId) as number : 0,
			hasChildSelected: isAncestorOfSelectedBlock,
		}
	}, [clientId])

	const {
		insertBlock,
		toggleBlockHighlight,
	} = useDispatch(blockEditorStore)

	const {clientId: templateClientId} = template || {}

	const debouncedToggleBlockHighlight = useDebounce(toggleBlockHighlight, 50)

	const onMouseEnter = useCallback(() => {
		if(templateClientId) {
			debouncedToggleBlockHighlight(templateClientId, true)
		}
	}, [templateClientId, debouncedToggleBlockHighlight])

	const onMouseLeave = useCallback(() => {
		if(templateClientId) {
			debouncedToggleBlockHighlight(templateClientId, false)
		}
	}, [templateClientId, debouncedToggleBlockHighlight])

	const onClick = useCallback(() => {

		toggleBlockHighlight(templateClientId, false)

		const makeBlock: any = (block: tBlock) => {

			const {
				name,
				attributes: blockAttributes = {},
				innerBlocks = [],
			} = block


			const {
				content,
				...attributes
			} = blockAttributes

			return [
				name,
				{
					...attributes,
					...(!!content && !stripContent && {
						content,
					}),
				},
				innerBlocks.map(block => createBlock(...makeBlock(block))),
			]
		}

		insertBlock(createBlock(...makeBlock(template)), index + 1, clientId)
	}, [clientId, templateClientId, template, index, insertBlock, stripContent, toggleBlockHighlight])

	if(!template || (!isSelected && !hasChildSelected)) {
		return null
	}

	return (
		<Button
			className='ska-appender ska-appender--repeat'
			variant='primary'
			icon={plus}
			label={__('Repeat pattern', 'ska-blocks')}
			children={label}
			onClick={onClick}
			onMouseEnter={onMouseEnter}
			onMouseLeave={onMouseLeave}
		/>
	)
}

const NO_APPENDER = () => null

interface InnerBlocksOptions {
	__unstableDisableLayoutClassNames?: any
	__unstableDisableDropZone?: any
	dropZoneElement?: any
	renderAppender?: boolean | (() => React.ReactNode)
}

export const useAppenderInnerBlocksOptions = (props: tBlockEditProps<AppenderAttributes>, options: InnerBlocksOptions = {}): InnerBlocksOptions => {

	const {
		clientId,
		attributes,
	} = props

	const {
		skaBlocksAppender = {},
	} = attributes

	const {
		type,
	} = skaBlocksAppender

	const supportsRepeater = useSupportsRepeater(clientId)

	switch(type) {

		case 'hidden':
			return {
				...options,
				renderAppender: NO_APPENDER,
			}

		case 'repeater':
			if(supportsRepeater) {
				return {
					...options,
					renderAppender: () => <RepeaterAppender {...props} />,
				}
			}
	}

	return options
}

export default () => {
	addFilter('blocks.registerBlockType', 'ska-blocks/with-appender-attributes', withCustomAttributes)
	addFilter('editor.BlockEdit', 'ska-blocks/with-appender-controls', withAppenderControls, 400)
}
