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

import {
	useState,
	createContext,
} from '@wordpress/element'

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

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

import EditableButton from './EditableButton'
import RadioButton from './RadioButton'

import {
	parseVariantValue,
} from '../../../tailwind/classNames'

import {
	buildOptions,
} from './util'

import {
	wrapArbitraryValue,
	type TailwindFeatureConfig,
	type TailwindFeature,
} from '../..'

import './style.scss'

const noop = () => {}

export interface RadioContextType {
	/** Display value instead of label on radio buttons. */
	showValues: boolean
	/** Display "deselect" button instead of "remove" button next to a custom value */
	deselectable: boolean
	/** Resolved or fallen back to defaults supports. */
	supports: TailwindFeature['supports']
}

export const RadioContext = createContext<RadioContextType>({
	showValues: false,
	deselectable: false,
	supports: {
		variants: false,
		important: false,
		signed: false,
		arbitrary: true,
		variable: true,
		name: false,
		bare: false,
	},
})

export interface RadioProps extends Omit<ButtonGroupRadioProps, 'options'> {

	/** What is this radio handling. */
	type?:
		/** Handles selecting a variant (e.g. `md`, `lg`, `hover`). */
		| 'variants'
		/** Handles selecting an option (e.g. `px`, `16`, `[12px]`). */
		| 'options'

	/** What Tailwind features are supported. */
	supports?: TailwindFeatureConfig['supports']

	/** Non-conforming options will be converted to `ButtonGroupOption`. */
	options: ButtonGroupOption[] | string[] | Map<string, string>

	/** Can be used to determine if a radio button should be displayed as active. */
	isActive?: (value: string) => boolean

	/** Action to perform when double clicking a radio button. */
	onDoubleClick?: (doubleClickedValue: string) => void

	/** Add custom options. */
	onAdd?: (addedValue: string) => void
	/** Edit custom options. */
	onUpdate?: (nextValue: string, prevValue: string) => void
	/** Remove custom options. */
	onRemove?: (removedValue: string) => void
	/** List of option values that are custom (and should render `<EditableButton>` when applicable). */
	arbitraryValues?: string[]

	/** Custom function to toggle important. */
	toggleImportant?: () => string
	/** Custom function to toggle sign. */
	toggleSigned?: () => string
}

interface SubComponents {
	Optimistic: typeof OptimisticRadio
	Tabs: typeof RadioTabs
}

/**
 * Radio buttons specialized for manipulating Tailwind utilities.
 */
const Radio: React.FC<RadioProps> & SubComponents = props => {

	const {
		type = 'options',
		supports: providedSupports = {},
		options,
		toggleImportant,
		toggleSigned,
		value: currentValue = '',
		onChange = noop,
		onAdd,
		isSmall = false,
	} = props

	const {
		variants = false,
		important = false,
		signed = false,
		arbitrary = false,
		variable = false,
		name = false,
		bare = false,
	} = providedSupports

	const supports = {
		variants,
		important,
		signed,
		arbitrary,
		variable,
		name,
		bare,
	}

	const OPTIONS = buildOptions(options)

	const {
		isImportant,
		isSigned,
		toggleImportant: defaultToggleImportant,
		toggleSigned: defaultToggleSigned,
	} = parseVariantValue(currentValue)

	const [showValues, setShowValues] = useState(false)

	const prepend = type === 'options' && <>
		<Button
			key='help'
			className='ska-tailwind-radio__char'
			size={isSmall ? 'small' : undefined}
			aria-pressed={showValues}
			label={showValues ? _x('Show names', 'radio', 'ska-blocks') : _x('Show values', 'radio', 'ska-blocks')}
			showTooltip
			children='?'
			onClick={() => setShowValues(!showValues)}
		/>
		{supports.important && (
			<Button
				key='important'
				className='ska-tailwind-radio__char'
				size={isSmall ? 'small' : undefined}
				aria-pressed={isImportant}
				label={_x('Toggle !important', 'radio', 'ska-blocks')}
				showTooltip
				children='!'
				disabled={!currentValue}
				onClick={() => onChange(toggleImportant ? toggleImportant() : defaultToggleImportant())}
			/>
		)}
		{supports.signed && (
			<Button
				key='sign'
				className='ska-tailwind-radio__char'
				size={isSmall ? 'small' : undefined}
				aria-pressed={isSigned}
				label={_x('Toggle negative value', 'radio', 'ska-blocks')}
				showTooltip
				children='-'
				disabled={!currentValue}
				onClick={() => onChange(toggleSigned ? toggleSigned() : defaultToggleSigned())}
			/>
		)}
	</>

	const append = onAdd && <>
		{type === 'options' && <>
			{supports.arbitrary && (
				<EditableButton
					type='arbitrary'
					isSmall={isSmall}
					label={_x('Add arbitrary value', 'radio', 'ska-blocks')}
					onAdd={value => onAdd(wrapArbitraryValue(value, 'arbitrary'))}
				/>
			)}
			{supports.variable && (
				<EditableButton
					type='variable'
					isSmall={isSmall}
					label={_x('Add CSS variable value', 'radio', 'ska-blocks')}
					onAdd={value => onAdd(wrapArbitraryValue(value, 'variable'))}
				/>
			)}
			{supports.name && (
				<EditableButton
					type='name'
					isSmall={isSmall}
					label={_x('Add name', 'radio', 'ska-blocks')}
					onAdd={value => onAdd(wrapArbitraryValue(value, 'name'))}
				/>
			)}
			{supports.bare && (
				<EditableButton
					type='bare'
					isSmall={isSmall}
					label={_x('Add bare value', 'radio', 'ska-blocks')}
					onAdd={onAdd}
				/>
			)}
		</>}
		{type === 'variants' && <>
			<EditableButton
				type='bare'
				isSmall={isSmall}
				label={_x('Add arbitrary variant', 'radio', 'ska-blocks')}
				onAdd={onAdd}
			/>
		</>}
	</>

	return (
		<RadioContext.Provider value={{showValues, deselectable: type === 'options', supports}}>
			<ButtonGroup.Radio
				{...props}
				className={cx('ska-tailwind-radio', props.className)}
				isSmall={isSmall}
				options={OPTIONS}
				value={currentValue}
				onChange={onChange}
				prepend={prepend}
				append={append}
				buttonComponent={RadioButton}
			/>
		</RadioContext.Provider>
	)
}

const OptimisticRadio: React.FC<RadioProps> = props => {

	const {
		value,
		onChange,
	} = props

	const [optimisticValue, setOptimisticValue] = useState(value)
	const handleChange = (nextValue: any) => {
		setOptimisticValue(nextValue)
		setTimeout(() => {
			if(onChange) {
				onChange(nextValue)
			}
		}, 0)
	}

	return (
		<Radio
			{...props}
			value={optimisticValue}
			onChange={handleChange}
		/>
	)
}

Radio.Optimistic = OptimisticRadio

export interface RadioTabsOption extends ButtonGroupOption {
	content: React.ReactNode
}

export interface RadioTabsProps extends Omit<RadioProps, 'options'> {
	options: RadioTabsOption[]
}

const RadioTabs: React.FC<RadioTabsProps> = ({
	className,
	options,
	value,
	...props
}) => {
	return <>
		<Radio
			className={cx('ska-radio-tabs', className)}
			options={options}
			value={value}
			{...props}
		/>
		{options
			.filter(({value: v, content}) => v === value && content)
			.map(({value, content}) => (
			<BaseControl
				key={value}
				className='ska-radio-tabs__tab'
				children={content}
				__nextHasNoMarginBottom
			/>
		))}
	</>
}

Radio.Tabs = RadioTabs

export default Radio
