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

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

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

import {
	InspectorControls,
	InspectorAdvancedControls,
	BlockControls,
	// @ts-expect-error
} from '@wordpress/block-editor'

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

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

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

import {
	SkaPanelBody,
} from '@ska/plugin'

import {
	URLPicker,
} from '../components'

import {
	useDynamicLinks,
	type LinkValue,
} from './link'

import {
	useHasPermissions,
} from '../plugins/permissions'

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

export interface AsValue extends LinkValue {
	/** Tag name to render as. */
	element?: string
	/** Custom tag name to render as, used when `element` is set to `custom`. */
	customElement?: string
	/** Render as a void element (without children). */
	isVoid?: boolean
}

export interface AsAttributes {
	skaBlocksAs?: AsValue
}

export const AS_SUPPORT_KEY = 'skaBlocksAs'
export const AS_SUPPORT_DEFAULT_ENABLED = false
export const hasAsSupport = (block: tBlockNameOrType) => hasBlockSupport(block, AS_SUPPORT_KEY, AS_SUPPORT_DEFAULT_ENABLED)

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

	const support = getBlockSupport(block, AS_SUPPORT_KEY, AS_SUPPORT_DEFAULT_ENABLED)

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

const withCustomAttributes = (settings: tBlockType) => {

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

	if(!settings.attributes.skaBlocksAs) {

		const defaultValue = getDefaultValue(settings)

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

	return settings
}

export const AS_ELEMENTS = applyFilters('ska.blocks.asElements', [
	{
		label: '<div>',
		value: 'div',
	},
	{
		label: '<span>',
		value: 'span',
	},
	{
		label: '<a>',
		value: 'a',
	},
	{
		label: '<button>',
		value: 'button',
	},
	{
		label: '<section>',
		value: 'section',
	},
	{
		label: '<ul>',
		value: 'ul',
	},
	{
		label: '<li>',
		value: 'li',
	},
	{
		label: '<ol>',
		value: 'ol',
	},
	{
		label: '<template>',
		value: 'template',
	},
]) as {label: string, value: string}[]

export const isValidAsElement = ({element = 'div', customElement = ''}: AsValue): boolean => {

	if(element !== 'custom') {
		return true // Assuming all predefined options are valid
	}

	if(!customElement) {
		return true // Falls back to div
	}

	let isInvalid = false
	try {
		const el = document.createElement(customElement)
		isInvalid = el.toString() === '[object HTMLUnknownElement]'
	} catch(e) {
		isInvalid = true
	}

	return !isInvalid
}

const AsInspectorControls: React.FC<tBlockEditProps<AsAttributes>> = (props) => {

	const {
		attributes,
		setAttributes,
	} = props

	const {
		skaBlocksAs = {},
	} = attributes

	const {
		element = 'div',
		customElement = '',
		href = '',
		opensInNewTab = false,
		isVoid = false,
	} = skaBlocksAs

	const dynamicLinks = useDynamicLinks()

	if(!useHasPermissions()) {
		return null
	}

	return <>
		<InspectorControls>
			<SkaPanelBody
				title={__('Element type', 'ska-blocks')}
				initialOpen={element !== 'div'}
				skaIcon
			>
				<ButtonGroup.Radio
					pills
					isSmall
					options={AS_ELEMENTS.concat({
						label: _x('Custom', 'Custom HTML element option', 'ska-blocks'),
						value: 'custom',
					})}
					value={element}
					onChange={(nextValue: string) => {
						const {element, ...rest} = skaBlocksAs
						setAttributes({
							skaBlocksAs: {
								...rest,
								...(nextValue && {element: nextValue}),
							},
						})
					}}
				/>
				{element === 'custom' && (
					<TextControl
						className='ska-blocks--use-code-inputs'
						label={__('Custom element tag name', 'ska-blocks')}
						{...(!isValidAsElement(skaBlocksAs) && {
							help: sprintf(__('The tag <%s> is unrecognized in this browser.', 'ska-blocks'), customElement),
							className: 'ska-invalid',
						})}
						placeholder='div'
						value={customElement}
						onChange={(nextValue: string) => setAttributes({skaBlocksAs: {...skaBlocksAs, customElement: nextValue}})}
						__nextHasNoMarginBottom
						__next40pxDefaultSize
					/>
				)}
			</SkaPanelBody>
		</InspectorControls>
		<InspectorAdvancedControls>
			<WithTooltip
				label={__('View help', 'ska-blocks')}
				tooltip={__(`Render this block without any children.`, 'ska-blocks')}
			>
				<ToggleControl
					label={__('Is void element', 'ska-blocks')}
					checked={isVoid}
					onChange={() => setAttributes({skaBlocksAs: {...skaBlocksAs, isVoid: !isVoid}})}
					__nextHasNoMarginBottom
				/>
			</WithTooltip>
		</InspectorAdvancedControls>
		{element === 'a' && (
			<BlockControls>
				<ToolbarGroup>
					<URLPicker
						value={href}
						onChange={(nextValue, {id, kind, type}) => {
							setAttributes({skaBlocksAs: {...skaBlocksAs, href: nextValue, id, kind, type}})
						}}
						opensInNewTab={opensInNewTab}
						onToggleOpensInNewTab={() => setAttributes({skaBlocksAs: {...skaBlocksAs, opensInNewTab: !opensInNewTab}})}
						renderControlBottom={dynamicLinks.length > 0 ? (
							<ButtonGroup.Radio
								label={__('Dynamic link', 'ska-blocks')}
								isSmall
								pills
								options={dynamicLinks}
								value={href}
								onChange={nextValue => {
									setAttributes({
										skaBlocksAs: {
											...skaBlocksAs,
											href: nextValue,
											opensInNewTab,
											id: undefined,
											kind: undefined,
											type: undefined,
										},
									})
								}}
							/>
						) : null}
					/>
				</ToolbarGroup>
			</BlockControls>
		)}
	</>
}

const withAsControls = createHigherOrderComponent(
	(BlockEdit: any) => (props: tBlockEditProps) => {
		if(hasAsSupport(props.name)) {
			return <>
				<AsInspectorControls {...props as any as tBlockEditProps<AsAttributes>} />
				<BlockEdit {...props} />
			</>
		}
		return <BlockEdit {...props} />
	},
	'withSkaBlocksAs'
)

export type AsElement = string

export type AsProps = {
	href?: string
	'data-ska-href'?: string
	target?: string
	rel?: string

	/** Inclusion of these silences TypeScript about `AsElement`'s props lacking overlap with IntrinsicAttributes. */
	key?: React.Key
	ref?: React.Ref<any>
}

export type AsInfo = {
	/** Can the element use RichText/InnerBlocks. */
	isVoidElement: boolean
}

/** A void element is an element in HTML that cannot have any child nodes (i.e., nested elements or text nodes). */
const VOID_ELEMENTS = [
	'area',
	'base',
	'basefont',
	'bgsound',
	'br',
	'col',
	'embed',
	'frame',
	'hr',
	'img',
	'input',
	'keygen',
	'link',
	'meta',
	'param',
	'source',
	'track',
	'wbr',
]

/** Elements that should remain a div in the editor for editing purposes. */
const RENDER_AS_DIV_IN_EDITOR = [
	'template',
	'dialog',
]

export const getAs = (attributes: AsAttributes, save = false): [AsElement, AsProps, AsInfo] => {

	const {
		skaBlocksAs = {},
	} = attributes

	const {
		element = 'div',
		customElement = '',
		href = '',
		opensInNewTab = false,
		isVoid = false,
	} = skaBlocksAs

	let Element = isValidAsElement(skaBlocksAs)
		? (element === 'custom' ? customElement || 'div' : element)
		: 'div'

	if(!save && RENDER_AS_DIV_IN_EDITOR.includes(Element)) {
		Element = 'div'
	}

	return [
		Element,
		{
			...(Element === 'a' && href && {
				[save ? 'href' : 'data-ska-href']: href,
				...(opensInNewTab && {
					target: '_blank',
					rel: 'noopener noreferrer',
				}),
			}),
			...(!save && {
				...(Element === 'button' && {
					onKeyDown: (e: React.KeyboardEvent) => {
						if(e.code !== 'Space') {
							return
						}
						e.preventDefault()
						// @ts-ignore
						e.target?.ownerDocument?.execCommand('insertText', false, ' ')
					},
				}),
				...(Element === 'form' && {
					onSubmit: (e: React.SyntheticEvent) => {
						e.preventDefault()
					},
				}),
				...(Element === 'input' && {
					onChange: (e: React.SyntheticEvent) => {
						e.preventDefault()
					},
				}),
			}),
		},
		{
			isVoidElement: isVoid || VOID_ELEMENTS.includes(Element),
		},
	]
}

export const useAs = (props: tBlockEditProps<AsAttributes>): [AsElement, Omit<AsProps, 'href'>, AsInfo] => {
	return getAs(props.attributes, false)
}

useAs.save = (props: tBlockSaveProps<AsAttributes>): [AsElement, Omit<AsProps, 'data-ska-href'>, AsInfo] => {
	return getAs(props.attributes, true)
}

export default () => {
	addFilter('blocks.registerBlockType', 'ska-blocks/with-as-attributes', withCustomAttributes)
	addFilter('editor.BlockEdit', 'ska-blocks/with-as-controls', withAsControls, 500)
}
