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

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

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

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

import TailwindFeatureGroup from './TailwindFeatureGroup'

import {
	BaseInspectorControls,
	AdvancedInspectorControls,
} from '../inspector-controls'

import {
	inspectorControlsGroup,
} from '../../data'

import type {
	SkaBlocks,
	TailwindFeature,
	TailwindFeatureConfig,
	TailwindFeatureGroupConfig,
} from '../../types'

import type {
	tBlockAttributes,
	tBlockEditProps,
	tBlockName,
	tBlockType,
} from '@ska/shared'

export interface TailwindConfig {
	groups: TailwindFeatureGroupConfig[]
}

/** Stores data about all available features. */
export type TailwindFeaturesList = {
	id: TailwindFeature['id']
	label: TailwindFeature['label']
	groupId: TailwindFeatureGroup['id']
	groupLabel: TailwindFeatureGroup['label']
	hasValue: ReturnType<TailwindFeature['hasValue']>
	config: TailwindFeatureConfig
	options: ReturnType<TailwindFeature['getOptions']>
}[]

export default class TailwindFeatures {

	readonly skaBlocks
	readonly config
	readonly groups

	constructor(skaBlocks: SkaBlocks, config: TailwindConfig) {
		this.skaBlocks = skaBlocks
		this.config = config
		this.groups = this.config.groups.map(groupConfig => new TailwindFeatureGroup(skaBlocks, groupConfig))
		this.register()
	}

	get id(): string {
		return `skaBlocksTailwind`
	}

	get label(): string {
		return __('Tailwind', 'ska-blocks')
	}

	hasSupport(blockType: tBlockType | tBlockName): boolean {
		return this.groups.some(group => group.hasSupport(blockType))
	}

	get features(): TailwindFeature[] {
		return this.groups.flatMap(group => group.features)
	}

	getFeature(id: TailwindFeature['id'] | TailwindFeature['featureId']): TailwindFeature | undefined {
		return this.features.find(feature => feature.id === id || feature.featureId === id)
	}

	getFeaturesList(attributes?: tBlockAttributes | undefined): TailwindFeaturesList {
		return this.groups.map(group => {
			return group.features.map(feature => {
				return {
					id: feature.id,
					label: feature.label,
					groupId: group.id,
					groupLabel: group.label,
					hasValue: attributes ? feature.hasValue(attributes) : false,
					config: feature.config,
					options: feature.getDefaultOptions(),
				}
			})
		}).flatMap(v => v)
	}

	/** Convert Tailwind class names to block attributes. */
	parse(classes: string) {
		return this.skaBlocks.parser.getAttributes(classes)
	}

	createBlockAttributes(classes: string, css?: string): tBlockAttributes {
		return {
			skaBlocks: {
				cx: classes,
				t: 0,
				...(css && {css}),
			},
			...this.parse(classes),
		}
	}

	/** Build Tailwind class names from block attributes. */
	getClassNames(attributes: tBlockAttributes): string {
		/** Class names generated from `skaBlocks*` attributes, stored in `skaBlocks.cx`. */
		return applyFilters('ska.blocks.tailwindClassNames', '', attributes) as string
	}

	/**
	 * Additional class names that should be available in the CSS
	 * stored in `skaBlocks.css` when rendering this block, but not
	 * applied to the block root element automatically.
	 */
	getAdditionalClassNames(attributes: tBlockAttributes): string {
		return applyFilters('ska.blocks.additionalTailwindClassNames', '', attributes) as string
	}

	hasClass(attributes: tBlockAttributes, className: string): boolean {

		const {
			skaBlocks = {},
		} = attributes

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

		return classNames.split(' ').includes(className)
	}

	blockVariationHasClass(className: string): (attributes: tBlockAttributes, variationAttributes: any) => boolean {
		return (attributes: tBlockAttributes, variationAttributes: any) => {
			return this.hasClass(attributes, className)
		}
	}

	/** Pick Tailwind attributes out of all block attributes. */
	getTailwindAttributes(attributes: tBlockAttributes): tBlockAttributes {
		return this.features.reduce((acc, {id: featureId}) => {
			if(attributes[featureId]) {
				acc[featureId] = {...attributes[featureId]}
			}
			return acc
		}, {} as tBlockAttributes)
	}

	get BlockEdit(): any {
		return createHigherOrderComponent(
			(BlockEdit: any) => (props: tBlockEditProps) => {

				if(!props.isSelected || !this.hasSupport(props.name)) {
					return <BlockEdit {...props} />
				}

				const group = inspectorControlsGroup
				if(group === 'hidden') {
					return <BlockEdit {...props} />
				}

				return <>
					<BlockEdit {...props} />
					<InspectorControls group={group}>
						<BaseInspectorControls
							blockProps={props}
							label={this.label}
							groups={this.groups}
						/>
					</InspectorControls>
					<InspectorAdvancedControls>
						<AdvancedInspectorControls {...props} />
					</InspectorAdvancedControls>
				</>
			},
			`with${upperFirst(this.id)}`
		)
	}

	register() {
		addFilter('editor.BlockEdit', `ska-blocks/${this.id}/controls`, this.BlockEdit.bind(this))
	}
}
