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

import {
	createContext,
	useContext,
	useId,
} from '@wordpress/element'

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

import {
	chevronUp,
	chevronDown,
	plus,
} from '@wordpress/icons'

import {
	ButtonGrid,
	type ButtonGridButtonProps,
} from '@ska/components'

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

import './style.scss'

const noop = () => {}

export const RECORD_EDITOR_DUPLICATE_PREFIX = '__ska_duplicate_key'

export const RecordEditorRecordContext = createContext<{
	index: number
	key: string
	value: any
	duplicate: (index: number) => void
}>({
	index: -1,
	key: '',
	value: '',
	duplicate: () => null,
})

export interface RecordEditorStrings {
	keyPlaceholder: string
	valuePlaceholder: string
	moveUpLabel: string
	moveDownLabel: string
	removeLabel: string
	addLabel: string
}
export const RECORD_EDITOR_STRINGS = {
	keyPlaceholder: 'Key…',
	valuePlaceholder: 'Value…',
	moveUpLabel: 'Move up',
	moveDownLabel: 'Move down',
	removeLabel: 'Remove',
	addLabel: 'Add',
}
export const RecordEditorStringsContext = createContext<RecordEditorStrings>(RECORD_EDITOR_STRINGS)

type StringKeyOf<T> = Extract<keyof T, string>
type AnyRecord = Record<string, any>

export interface RecordEditorProps<RecordType extends AnyRecord> {
	id?: string
	label?: string
	hideLabelFromVision?: boolean
	help?: string
	footer?: React.ReactNode
	className?: string
	size?: 'large'
	value: RecordType
	defaultValue?: RecordType['value']
	onChange?: (nextValue: RecordType) => void
	onPaste?: (e: React.ClipboardEvent<HTMLInputElement>) => void
	lockedKeys?: (StringKeyOf<RecordType>)[]
	lockedValues?: (StringKeyOf<RecordType>)[]
	KeyControl?: React.FC<CustomControlProps<StringKeyOf<RecordType>>>
	ValueControl?: React.FC<CustomControlProps<RecordType['value']>> | false
	getButtons?: (buttons: ButtonGridButtonProps[], value: RecordType['value'], key: keyof RecordType) => ButtonGridButtonProps[]
}

export interface CustomControlProps<T> {
	placeholder?: string
	value: T
	onChange: (nextValue: T) => void
	disabled?: boolean
	[key: string]: any
}

const defaultGetButtons = (buttons: ButtonGridButtonProps[]) => buttons

const RecordEditor = <RecordType extends AnyRecord>(props: RecordEditorProps<RecordType>): React.ReactElement => {

	const {
		id: providedId,
		label,
		hideLabelFromVision = false,
		help,
		footer,
		className,
		size,
		value: _value,
		defaultValue: _defaultValue,
		onChange = noop,
		onPaste,
		lockedKeys = [],
		lockedValues = [],
		KeyControl = TextControl as any as React.FC<CustomControlProps<StringKeyOf<RecordType>>>,
		ValueControl = TextControl as any as React.FC<CustomControlProps<RecordType['value']>>,
		getButtons = defaultGetButtons,
	} = props

	const {
		keyPlaceholder,
		valuePlaceholder,
		moveUpLabel,
		moveDownLabel,
		removeLabel,
		addLabel,
	} = useContext(RecordEditorStringsContext)

	const fallbackId = useId().replaceAll('«', '_').replaceAll('»', '_').replaceAll(':', '_')
	const id = providedId || fallbackId

	const defaultValue = (_defaultValue || '') as RecordType['value']
	const value = Object.keys(_value).length > 0 ? _value : {'': defaultValue} as any as RecordType
	const keys = Object.keys(value) as (StringKeyOf<RecordType>)[]

	const updateKey = (key: StringKeyOf<RecordType>, nextKey: string) => {

		if(
			nextKey.indexOf(RECORD_EDITOR_DUPLICATE_PREFIX) !== -1
			// @ts-ignore
			&& !keys.includes(nextKey.replaceAll(RECORD_EDITOR_DUPLICATE_PREFIX, ''))
		) {
			nextKey = nextKey.replaceAll(RECORD_EDITOR_DUPLICATE_PREFIX, '')
		}

		// @ts-ignore
		if(keys.includes(nextKey as string)) {
			nextKey = `${RECORD_EDITOR_DUPLICATE_PREFIX}${nextKey}`
		}

		onChange(
			keys.reduce((acc, cur) => {
				if(cur === key) {
					acc[nextKey as any as StringKeyOf<RecordType>] = value[cur]
				} else {
					acc[cur as any as StringKeyOf<RecordType>] = value[cur]
				}
				return acc
			}, {} as RecordType)
		)
	}

	const updateValue = (key: StringKeyOf<RecordType>, nextValue: RecordType['value']) => {
		onChange({...value, [key]: nextValue})
	}

	const addRecord = (index: number, duplicate: boolean = false) => {

		const {
			'': _,
			...nextValue
		} = value

		const currentKeys = Object.keys(nextValue)
		const emptyKey = ''

		if(index >= currentKeys.length) {
			onChange({...nextValue, [emptyKey]: defaultValue} as any as RecordType)
			return
		}

		onChange(
			currentKeys.reduce((acc, cur, currentIndex) => {
				// @ts-ignore
				acc[cur] = nextValue[cur]
				if(currentIndex === index) {
					if(duplicate) {
						// @ts-ignore
						acc[`${RECORD_EDITOR_DUPLICATE_PREFIX}${cur}`] = nextValue[cur]
					} else {
						// @ts-ignore
						acc[emptyKey] = defaultValue
					}
				}
				return acc
			}, {} as RecordType)
		)
	}

	const removeRecord = (key: StringKeyOf<RecordType>) => {

		const {
			[key]: _,
			...rest
		} = value

		onChange({...rest as RecordType})
	}

	const moveRecordUp = (key: StringKeyOf<RecordType>, index: number) => {
		onChange(
			keys.filter(k => k !== key).reduce((acc, cur: StringKeyOf<RecordType>, currentIndex) => {
				if(currentIndex === index - 1) {
					acc[key] = value[key]
				}
				acc[cur] = value[cur]
				return acc
			}, {} as RecordType)
		)
	}

	const moveRecordDown = (key: StringKeyOf<RecordType>, index: number) => {
		onChange(
			keys.filter(k => k !== key).reduce((acc, cur: StringKeyOf<RecordType>, currentIndex) => {
				acc[cur] = value[cur]
				if(currentIndex === index) {
					acc[key] = value[key]
				}
				return acc
			}, {} as RecordType)
		)
	}

	const keyIdCx = `ska-record-${id}` // Unique ID but BaseControl doesn't support wrapper props so it's passed as a class as well.

	return (
		<BaseControl
			key={keyIdCx}
			id={keyIdCx}
			className={cx('ska-record-editor', {'ska-record-editor--large': size === 'large'}, keyIdCx, className)}
			label={label}
			help={help}
			hideLabelFromVision={hideLabelFromVision}
			__nextHasNoMarginBottom
		>
			{keys.map((key, index) => {

				const val = value[key] as RecordType['value']

				const isFirst = index === 0
				const isLast = index + 1 === keys.length
				const isOnly = keys.length === 1
				const isBlank = key === '' && value[key] === ''
				const isEmpty = key === ''
				const hasNextRow = keys[index + 1] !== undefined
				const isLocked = lockedKeys.includes(key)

				const buttons = getButtons([
					{
						key: 'move-up',
						label: moveUpLabel,
						icon: chevronUp,
						onClick: () => moveRecordUp(key, index),
						// disabled: isLocked || isFirst || isAfterLocked,
						disabled: isFirst,
					},
					{
						key: 'remove-row',
						label: removeLabel,
						icon: plus,
						iconProps: {
							style: {
								transform: 'rotate(45deg)',
								pointerEvents: 'none',
							},
						},
						onClick: () => removeRecord(key),
						disabled: isLocked || (isOnly && isBlank),
					},
					{
						key: 'move-down',
						label: moveDownLabel,
						icon: chevronDown,
						onClick: () => moveRecordDown(key, index),
						disabled: isLast,
					},
					{
						key: 'add-row',
						label: addLabel,
						icon: plus,
						onClick: () => addRecord(index),
					},
				], val, key)

				/** Applied to both key and value inputs. */
				const onKeyDown = (e: React.KeyboardEvent) => {

					if(e.code === 'Enter') {
						if(!isEmpty) {
							e.preventDefault()
							if(!hasNextRow) {
								addRecord(index)
							}
							focusElement(`.${keyIdCx} .ska-record-editor__row[data-index="${index + 1}"] input[data-type="key"]`, 150)
						}
					}

					if(e.code === 'Backspace') {
						if(isBlank && !isOnly) {
							e.preventDefault()
							removeRecord(key)
							focusElement(`.${keyIdCx} .ska-record-editor__row[data-index="${index - 1}"] input[data-type="value"]`, 150)
						}
					}
				}

				return (
					<div
						key={`${id}-row-${index}`}
						className={cx('ska-record-editor__row', {
							'ska-record-editor__row--single': ValueControl === false,
						})}
						data-index={index}
					>
						<RecordEditorRecordContext.Provider value={{index, key, value: val, duplicate: (index: number) => addRecord(index, true)}}>
							<KeyControl
								key={`${id}-key-${index}`}
								className={cx({
									'ska-record-editor__row--invalid': key.includes(RECORD_EDITOR_DUPLICATE_PREFIX),
								})}
								placeholder={keyPlaceholder}
								value={key.replaceAll(RECORD_EDITOR_DUPLICATE_PREFIX, '') as StringKeyOf<RecordType>}
								autoComplete='off'
								disabled={isLocked}
								onChange={nextKey => {
									updateKey(key, nextKey)
								}}
								onPaste={onPaste}
								onKeyDown={onKeyDown}
								data-type='key'
								__nextHasNoMarginBottom
								__next40pxDefaultSize
							/>
							{ValueControl !== false && (
								<ValueControl
									placeholder={valuePlaceholder}
									value={val}
									autoComplete='off'
									disabled={lockedValues.includes(key)}
									onChange={(nextValue: RecordType['value']) => {
										updateValue(key, nextValue)
									}}
									onPaste={onPaste}
									onKeyDown={onKeyDown}
									data-type='value'
									__nextHasNoMarginBottom
									__next40pxDefaultSize
								/>
							)}
							<ButtonGrid
								columns={Math.ceil(buttons.length / 2)}
								width={size === 'large' ? 20 : 16}
								buttons={buttons}
							/>
						</RecordEditorRecordContext.Provider>
					</div>
				)
			})}
			{footer && (
				<div className='ska-record-editor__footer components-base-control__help'>
					{footer}
				</div>
			)}
		</BaseControl>
	)
}

export default RecordEditor
