import * as React from 'react'
import cx from 'classnames'

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

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

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

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

import {
	useStyleOverride,
} from '@ska/utils'

import {
	transformEditorStyles,
} from '../../tailwind/css'

import {
	BlockPreset,
	useStaticPresetsClassNames,
} from '../presets'

import type {
	tBlockAttributes,
	tBlockListBlockProps,
	tBlockNameOrType,
	tBlockType,
} from '@ska/shared'

import {
	useBlockInfo,
	useRecompile,
	useUpdateBlockInfo,
} from './hooks'

export interface TailwindValue {
	/** Tailwind class names. */
	cx?: string
	/** Generated CSS. */
	css?: string
	/** Last modified time. */
	t?: number
	/** Presets */
	p?: BlockPreset[]
	/** Sandbox/namespace block CSS (default: true). */
	sb?: boolean
}

/** `skaBlocks` in block attributes. */
export interface TailwindAttributes {
	skaBlocks?: TailwindValue
}

export const hasTailwindSupport = (block: tBlockNameOrType) => hasBlockSupport(block, 'customClassName', true)

const withCustomAttributes = (settings: tBlockType) => {

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

	if(!settings.attributes.skaBlocks) {
		Object.assign(settings.attributes, {
			skaBlocks: {
				type: 'object',
			},
		})
	}

	return settings
}

interface TailwindBlockListBlockProps extends tBlockListBlockProps<TailwindAttributes> {
	BlockListBlock: any
	isPreviewMode: boolean
	isInTemplate: boolean
}

/** Skips rendering TailwindBlockListBlock when the BlockListBlock is a clone, e.g. a repeated block in a Query Loop -> Post Template block. */
const MaybeTailwindBlockListBlock: React.FC<Omit<TailwindBlockListBlockProps, 'isPreviewMode' | 'isInTemplate'>> = props => {

	const {
		clientId,
		isSelectionEnabled,
		BlockListBlock,
	} = props

	const block = useBlockInfo(clientId, isSelectionEnabled)

	if(block.isClone) {
		return (
			<BlockListBlock
				{...props}
				className={cx('ska-blocks__clone', block.sourceClassName)}
			/>
		)
	}

	return (
		<TailwindBlockListBlock
			{...props}
			isPreviewMode={block.isPreviewMode}
			isInTemplate={block.isInTemplate}
		/>
	)
}

/** Manages Tailwind classes on a block and loads their Tailwind CSS. */
const TailwindBlockListBlock: React.FC<TailwindBlockListBlockProps> = ({
	BlockListBlock,
	isPreviewMode,
	isInTemplate, // When it's in a template `useUpdateBlockInfo()` should be used to keep track of its' classes.
	...props
}) => {

	const {
		clientId,
		attributes,
		setAttributes,
		className: blockListBlockClassNames = '',
		isSelectionEnabled,
	} = props

	const {
		skaBlocks = {},
	} = attributes

	const {
		cx: tailwindClassNames = '',
		css = '',
		sb = true,
	} = skaBlocks

	const staticPresetsClassNames = useStaticPresetsClassNames(attributes)
	const className = cx(staticPresetsClassNames, tailwindClassNames, blockListBlockClassNames)
	const isRecompileCandidate = (
		!isPreviewMode
		&& isSelectionEnabled
		&& 'skaBlocks' in attributes
		&& attributes.skaBlocks !== undefined
	)
	useRecompile(clientId, isRecompileCandidate, attributes, setAttributes)

	/** Adds `#${clientId}` prefix to selectors. */
	const transformedStyles = useMemo(() => transformEditorStyles(css, clientId, className, sb), [clientId, css, className, sb])
	useStyleOverride(`ska-blocks-tw--${clientId}`, transformedStyles)

	useUpdateBlockInfo(clientId, isInTemplate, className)

	return <BlockListBlock {...props} className={className} />
}

const withTailwind = createHigherOrderComponent(
	(BlockListBlock: any) => (props: tBlockListBlockProps) => {
		if(hasTailwindSupport(props.name)) {
			return (
				<MaybeTailwindBlockListBlock
					{...props as any as tBlockListBlockProps<TailwindAttributes>}
					BlockListBlock={BlockListBlock}
				/>
			)
		}
		return <BlockListBlock {...props} />
	},
	'withSkaBlocksTailwind'
)

/** Add Tailwind class names to saved block. */
const withTailwindClassNames = (props: {className?: string}, blockType: tBlockType, attributes: tBlockAttributes): object => {

	const {
		skaBlocks = {},
	} = attributes

	const {
		cx: tailwindClassNames = '',
	} = skaBlocks

	if(!tailwindClassNames) {
		return props
	}

	return {
		...props,
		className: cx(tailwindClassNames, props.className),
	}
}

export default () => {
	addFilter('blocks.registerBlockType', 'ska-blocks/with-tailwind-attributes', withCustomAttributes)
	addFilter('editor.BlockListBlock', 'ska-blocks/with-tailwind', withTailwind, 500)
	addFilter('blocks.getSaveContent.extraProps', `ska-blocks/with-tailwind-classes`, withTailwindClassNames)
}
