import * as React from 'react'
import {__} from '@wordpress/i18n'
import cx from 'classnames'

import {
	useEffect,
	useReducer,
	RawHTML,
} from '@wordpress/element'

import {
	PanelBody,
	ToggleControl,
	ToolbarGroup,
	ToolbarButton,
	BaseControl,
	Button,
	__experimentalHStack as HStack,
	__experimentalView as View,
} from '@wordpress/components'

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

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

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

import {
	code as codeIcon,
} from '@wordpress/icons'

import {
	useQuery,
} from '@tanstack/react-query'

import {
	useDebouncedValue,
} from '@tanstack/react-pacer'

import {
	CodeEditor,
	ModalButton,
	TailwindIcon,
	UserJsonView,
	WithTooltip,
} from '@ska/components'

import {
	AIButton,
	useHTMLSystemPrompt,
} from '@ska/ai'

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

import Preview from './Preview'

import {
	htmlToBlocks,
} from './convert'

// @ts-ignore
import metadata from './block.json'

import SkaBlocksInstance from '../../SkaBlocks'

import {
	usePluginOptions,
} from '../../plugins/options'

import {
	QueryClientProvider,
	renderBlock,
} from '../../api'

import {
	useTailwindConfigVersion,
} from '../../tailwind'

import {
	html_beautify,
	css_beautify,
	// @ts-expect-error
} from 'js-beautify'

const HTML_BEAUTIFY_SETTINGS = {
	'indent_size': '1',
	'indent_char': '\t',
	'max_preserve_newlines': '-1',
	'preserve_newlines': false,
	'end_with_newline': false,
}

const CSS_BEAUTIFY_SETTINGS = {
	'indent_size': '1',
	'indent_char': '\t',
	'max_preserve_newlines': '-1',
	'preserve_newlines': false,
	'end_with_newline': false,
}

import type {
	SkaBlocks,
} from '../../types'

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

import './style.scss'

export interface TailwindBlockAttributes {
	/** HTML user input. */
	html: string
	/** CSS user input. */
	customCss: string
	/** Current tab. */
	tab: 'html' | 'css' | 'preview'
	/** Compiled CSS. */
	css: string
	/** Display HTML input in the editor, disable to display a button and edit contents in a modal. */
	inline: boolean
	/** Server side render the HTML and compile CSS based on that. */
	ssr: boolean
}

const Edit: React.FC<tBlockEditProps<TailwindBlockAttributes>> = props => {

	const {
		name: blockName,
		clientId,
		isSelected,
		attributes,
		setAttributes,
		insertBlocksAfter,
	} = props

	const {
		html: customHtml = '',
		css = '',
		customCss = '',
		tab = 'html',
		inline = true,
		ssr = false,
	} = attributes

	/** Unescape ampersands. */
	const html = customHtml.replaceAll('&amp;', '&')

	const {
		htmlToBlocksRemove,
	} = usePluginOptions()

	const ver = useTailwindConfigVersion()
	const canRender = ssr && isSelected
	const canCompile = ver > -1 && isSelected
	const [compileKey, incrementCompileKey] = useReducer(i => i + 1, 0)

	const setTab = (nextTab: TailwindBlockAttributes['tab']) => setAttributes({tab: nextTab})

	const {
		removeBlock,
	} = useDispatch(blockEditorStore)

	const blockProps = useBlockProps()

	const [htmlToRender] = useDebouncedValue(html, {
		wait: 3000,
		leading: true,
		trailing: true,
	})

	const renderQuery = useQuery({
		queryKey: ['ssr', compileKey, htmlToRender],
		queryFn: ({signal}) => {

			if(!htmlToRender.trim()) {
				return ''
			}

			return renderBlock({
				blockName,
				attributes: {html: htmlToRender},
				signal,
			})
		},
		placeholderData: htmlToRender,
		enabled: canRender,
		retry: 1,
		gcTime: 1000 * 60 * 10,
	})

	const htmlToCompile = ssr ? (renderQuery.data || '') : html
	const cssToCompile = customCss

	const currentContentToCompile = `${htmlToCompile}${cssToCompile}`
	const [contentToCompile] = useDebouncedValue(currentContentToCompile, {
		wait: 3000,
		leading: !ssr,
		trailing: true,
	})
	const isStale = currentContentToCompile !== contentToCompile

	const compileQuery = useQuery({
		queryKey: ['compile', ver, compileKey, ssr, contentToCompile],
		queryFn: () => {
			if(!contentToCompile.trim()) {
				return ''
			}
			return SkaBlocksInstance.compiler.compileHTMLwithCSS(htmlToCompile, cssToCompile, {throwOnError: true})
		},
		enabled: canCompile && (!ssr || renderQuery.isSuccess),
		retry: 0,
		gcTime: 1000 * 60,
	})

	useEffect(() => {
		if(
			ver > -1
			&& typeof compileQuery.data === 'string'
			&& compileQuery.data !== css
		) {
			setAttributes({css: compileQuery.data})
		}
	}, [ver, compileQuery.data, css, setAttributes])

	if(isSelected) {
		debugLog({
			blockName,
			renderQuery: {...renderQuery},
			compileQuery: {...compileQuery},
		})
	}

	const errors = <>
		{renderQuery.status === 'error' && renderQuery.failureReason?.message && <p className='ska-tailwind-block__error'>{renderQuery.failureReason.message}</p>}
		{compileQuery.status === 'error' && compileQuery.failureReason?.message && <p className='ska-tailwind-block__error'>{compileQuery.failureReason.message}</p>}
	</>

	const htmlEditor = <>
		<CodeEditor
			placeholder={__('HTML that has Tailwind classes…', 'ska-blocks')}
			value={html}
			onChange={nextValue => setAttributes({html: nextValue})}
		/>
		{errors}
	</>

	const cssEditor = <>
		<CodeEditor
			type='css'
			placeholder={__('Additional Tailwind CSS…', 'ska-blocks')}
			value={customCss}
			onChange={nextValue => setAttributes({customCss: nextValue})}
		/>
		{errors}
	</>

	const preview = <>
		<Preview
			key={`${renderQuery.dataUpdatedAt}${compileQuery.dataUpdatedAt}${css}${ssr ? 1 : 0}`}
			content={ssr ? (renderQuery?.data || '') : html}
			css={css}
			isSelected={isSelected}
		/>
		{errors}
	</>

	const tabs = <>
		<ToolbarGroup>
			<ToolbarButton
				className='components-tab-button'
				aria-pressed={tab === 'html'}
				onClick={() => setTab('html')}
				children='HTML'
			/>
			<ToolbarButton
				className='components-tab-button'
				aria-pressed={tab === 'css'}
				onClick={() => setTab('css')}
				children='CSS'
			/>
			<ToolbarButton
				className='components-tab-button'
				aria-pressed={tab === 'preview'}
				onClick={() => setTab('preview')}
				children={__('Preview', 'ska-blocks')}
			/>
		</ToolbarGroup>
	</>

	const content = <>
		{tab === 'html' && htmlEditor}
		{tab === 'css' && cssEditor}
		{tab === 'preview' && preview}
	</>

	const isBusy = renderQuery.fetchStatus !== 'idle' || compileQuery.fetchStatus !== 'idle'
	const compileButton = (
		<Button
			variant='primary'
			size='compact'
			children={`${ssr ? __('Re-render & compile CSS', 'ska-blocks') : __('Re-compile CSS', 'ska-blocks')} ${isStale ? '⏳' : '✅'}`}
			onClick={incrementCompileKey}
			isBusy={isBusy}
			disabled={isBusy || ((ssr ? !canRender : false) && !canCompile)}
		/>
	)

	const convertToBlocks = () => {

		if(!insertBlocksAfter) {
			return
		}

		const blocks = htmlToBlocks(html)
		if(blocks.length > 0) {
			insertBlocksAfter(blocks)
		}

		if(htmlToBlocksRemove) {
			removeBlock(clientId)
		}
	}

	const hasContent = !!html.trim()

	return <>
		<BlockControls>
			{tabs}
			<ToolbarGroup>
				<AIButton
					session={{
						mode: 'html',
						systemPrompt: useHTMLSystemPrompt(),
						value: html || '',
						onResponse: nextValue => {
							setAttributes({
								html: nextValue,
								tab: 'preview',
								ssr: false,
							})
						},
					}}
					closeOnResponse
					className='components-tab-button'
					as={ToolbarButton}
					QueryClientProvider={QueryClientProvider}
				/>
				<ToolbarButton
					className='components-tab-button'
					onClick={convertToBlocks}
					children={__('Convert to blocks', 'ska-blocks')}
					disabled={!hasContent}
				/>
			</ToolbarGroup>
		</BlockControls>
		<InspectorControls>
			<PanelBody>
				<BaseControl __nextHasNoMarginBottom>
					<HStack>
						{compileButton}
						<UserJsonView
							label={__('View generated CSS', 'ska-blocks')}
							disabled={isBusy}
							modalProps={{size: 'fill'}}
							css
							value={css}
						/>
					</HStack>
				</BaseControl>
				<WithTooltip
					tooltip={__('Disable to edit the contents of this block in a modal while only displaying a button in the editor.', 'ska-blocks')}
				>
					<ToggleControl
						label={__('Render block inline', 'ska-blocks')}
						checked={inline}
						onChange={() => setAttributes({inline: !inline})}
						__nextHasNoMarginBottom
					/>
				</WithTooltip>
				<WithTooltip
					tooltip={__('Enable to server side render the HTML contents before generating CSS (for example if the HTML contains WordPress shortcodes that produce HTML that uses Tailwind classes).', 'ska-blocks')}
				>
					<ToggleControl
						label={__('Server side rendering', 'ska-blocks')}
						checked={ssr}
						onChange={() => setAttributes({ssr: !ssr})}
						__nextHasNoMarginBottom
					/>
				</WithTooltip>
				<BaseControl __nextHasNoMarginBottom>
					<HStack>
						<Button
							variant='secondary'
							size='compact'
							children={__('Beautify HTML', 'ska-blocks')}
							onClick={() => setAttributes({html: html_beautify(html, HTML_BEAUTIFY_SETTINGS)})}
							disabled={!customHtml}
						/>
						<Button
							variant='secondary'
							size='compact'
							children={__('Beautify CSS', 'ska-blocks')}
							onClick={() => setAttributes({customCss: css_beautify(customCss, CSS_BEAUTIFY_SETTINGS)})}
							disabled={!customCss}
						/>
					</HStack>
				</BaseControl>
			</PanelBody>
		</InspectorControls>
		<div {...blockProps}>
			<div
				className={cx('wp-block-ska-tailwind__wrapper', {
					'wp-block-ska-tailwind__wrapper--inline': inline,
				})}
			>
				{inline && content}
				{!inline && (
					<ModalButton
						icon={codeIcon}
						modalProps={{
							className: 'ska-tailwind-block__modal',
							title: __('Tailwind', 'ska-blocks'),
							size: 'fill',
						}}
					>
						<HStack className='ska-tailwind-block__modal__toolbar'>
							{tabs}
							{compileButton}
						</HStack>
						<View className='ska-tailwind-block__modal__content'>
							{content}
						</View>
					</ModalButton>
				)}
			</div>
		</div>
	</>
}

const EditComponent: React.FC<tBlockEditProps<TailwindBlockAttributes>> = props => {
	return (
		<QueryClientProvider>
			<Edit {...props} />
		</QueryClientProvider>
	)
}

const Save: React.FC<tBlockSaveProps<TailwindBlockAttributes>> = props => {

	const {
		attributes,
	} = props

	const {
		html,
	} = attributes

	return <RawHTML>{html}</RawHTML>
}

export default (skaBlocks: SkaBlocks): BlockModule => ({
	metadata,
	settings: {
		icon: TailwindIcon,
		edit: EditComponent,
		save: Save,
		transforms: {
			from: [
				{
					type: 'block',
					blocks: ['core/html'],
					transform: ({content}) => {
						return createBlock('ska/tailwind', {
							html: content,
						})
					},
				},
			],
		},
	},
})
