import {Component, FC, ReactNode, FocusEvent} from 'react'
import c from 'classnames'
import {Popper} from '@befe/brick-comp-popper'
import {createListenerGroup, getClosestMappedSize, Ref, StateOperation} from '@befe/brick-utils'

import {Input} from '@befe/brick-comp-input'
import {pick} from 'lodash-es'
import {getDefaultValueUsingContextTheme} from '@befe/brick-core';
import {UiTimePanel} from '../ui-comps/ui-time-panel'
import {SafeChainInvoker} from '../utils/safe-chain-invoker'
import {assign} from '../utils/assign'
import {isAllUndefined} from '../utils/has-undefined'
import {getColNames} from '../defs/types'
import {TimeUserInput} from './comp-time-user-input'

export interface PartialInputProps {
    disabled?: boolean

    status?: 'normal' | 'error'

    /**
     * default 值 : 请选择时间
     */
    placeholder?: string
}

export interface TimePickerProps extends PartialTimePickerIconProps,
    PartialInputProps {
    /**
     * 自定义 class
     */
    className?: string

    /**
     * 当前时间，使用 TimePicker.TimeValue 创建
     */
    value?: TimeValue

    /**
     * 默认时间，使用 TimePicker.TimeValue 创建
     */
    defaultValue?: TimeValue

    /**
     * 时间选择器面板的额外 className
     */
    popupClassName?: string

    /**
     * 尺寸
     */
    size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'

    /**
     * 是否显示秒
     */
    showSecond?: boolean

    /**
     * 值变化时的回调
     */
    onChange?: (value: TimeValue) => void

    clearable?: boolean

    onFocus?: (e: FocusEvent) => void
    onBlur?: (e: FocusEvent) => void
}

export interface TimePickerState {
    hour?: number
    minute?: number
    second?: number

    open: boolean
    isUserInput: boolean
}

export interface PartialTimePickerIconProps {
    iconSvg?: FC
    iconNode?: ReactNode
}

function padding(num: number) {
    return (num < 10 ? '0' : '') + String(num)
}

function getTimeText(
    showSecond: boolean,
    hour?: number,
    minute?: number,
    second?: number
) {
    const isSecondUndefined = typeof second === 'undefined'
    const isMinuteUndefined = typeof minute === 'undefined'
    const isHourUndefined = typeof hour === 'undefined'

    if (
        isHourUndefined
        && isMinuteUndefined
        && isSecondUndefined
    ) {
        return ''
    }

    const h = padding(hour || 0)
    const m = padding(minute || 0)

    if (!showSecond) {
        return `${h}:${m}`
    }

    const s = padding(second || 0)
    return `${h}:${m}:${s}`
}

const RGX_INTEGER = /^\d{0,2}$/;
export function validateTimeText(text: string) {
    text = text || '';
    const [hourText, minuteText, secondText] = text.split(':').map(num => num?.trim());

    const hour = Number(hourText);
    if (isNaN(hour) || !RGX_INTEGER.test(hourText) || hour < 0 || hour > 23) {
        return false;
    }

    const minute = Number(minuteText);
    if (isNaN(minute) || !RGX_INTEGER.test(minuteText) || minute < 0 || minute > 59) {
        return false;
    }

    if (typeof secondText !== 'undefined') {
        const second = Number(secondText);
        if (isNaN(second) || !RGX_INTEGER.test(secondText) || second < 0 || second > 59) {
            return false;
        }
    }

    return true;
}

function extractFromTimeText(text: string) {
    if (!validateTimeText(text)) {
        return {}
    }

    const [hour, minute, second] = text.split(':')
        .map(i => ~~i.trim())

    return {
        hour,
        minute,
        second,
    }
}

export class TimeValue {
    showSecond: boolean
    hour?: number
    minute?: number
    second?: number

    constructor(
        hour?: number,
        minute?: number,
        second?: number,
        showSecond?: boolean
    ) {
        /**
         * second 不为空时, 默认也是要 "show second"
         */
        this.showSecond = showSecond || typeof second !== 'undefined'
        this.hour = hour
        this.minute = minute
        this.second = second
    }

    static isValid(text: string) {
        return validateTimeText(text)
    }

    static fromText(text: string, showSecond = false) {
        const {hour, minute, second} = extractFromTimeText(text)
        return new TimeValue(hour, minute, second, showSecond)
    }

    toText() {
        return getTimeText(this.showSecond, this.hour, this.minute, this.second)
    }

    toValue() {
        const isHourUndefined = typeof this.hour === 'undefined'
        const isMinuteUndefined = typeof this.minute === 'undefined'
        const isSecondUndefined = typeof this.second === 'undefined'

        if (isHourUndefined && isMinuteUndefined && (isSecondUndefined || !this.showSecond)) {
            return {hour: undefined, minute: undefined, second: undefined}
        }

        return {
            hour: this.hour || 0,
            minute: this.minute || 0,
            second: this.showSecond ? (this.second || 0) : undefined,
        }
    }
}

/**
 * 暂时保留, 后续可用
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function debug(...args: any) {
    console.warn('@debug', ...args);
}

export function createTimePicker(
    {
        iconSvg: defaultIconSvg,
        iconNode: defaultIconNode,
    }: PartialTimePickerIconProps
) {

    return class TimePicker extends Component<TimePickerProps, TimePickerState> {
        static displayName = 'TimePicker'
        // static propTypes = {}
        static defaultProps: TimePickerProps = {
            className: '',
            showSecond: false,
            clearable: true,
            disabled: false,
            status: 'normal',
            placeholder: '请选择时间',
        }

        static TimeValue = TimeValue

        static getTimeText = getTimeText
        static validateTimeText = validateTimeText
        static extractFromTimeText = extractFromTimeText

        static getInitialSelect = (enabled: boolean) => {
            return {
                enabled,
                hour: !enabled,
                minute: !enabled,
                second: !enabled,
            }
        }

        state: TimePickerState = {
            isUserInput: false,
            open: false,
        }

        isClickedOnPanel = false;
        initialSelect = TimePicker.getInitialSelect(false);

        listenerGroup = createListenerGroup()

        stateUserInput = new StateOperation<boolean, TimePickerState>(
            this, 'isUserInput'
        )

        stateOpen = new StateOperation<boolean, TimePickerState>(
            this, 'open'
        )

        refInput = new Ref<Input>()
        refInputWrapper = new Ref<HTMLDivElement>()
        refPanel = new Ref<UiTimePanel>()
        refPanelWrapper = new Ref<HTMLDivElement>()



        checkClickOnInput = new SafeChainInvoker(
            'refInputWrapper.elem.contains',
            this
        )

        checkClickOnPanel = new SafeChainInvoker(
            'refPanelWrapper.elem.contains',
            this
        )

        constructor(props: never) {
            super(props)
            const {value, defaultValue} = this.props

            if (!value && defaultValue) {
                const {
                    hour = 0,
                    minute = 0,
                    second = 0,
                } = defaultValue;
                assign(this.state, {hour, minute, second});
            }
        }

        isInitialSelected = () => {
            const {enabled, hour, minute, second} = this.initialSelect
            const showSecond = this.props.showSecond

            return enabled
                && (
                    hour
                    && minute
                    && (!showSecond || second)
                )
        }

        get size(): TimePickerProps['size'] {
            return getDefaultValueUsingContextTheme(this, 'size', 'baseSize')
        }

        static getDerivedStateFromProps(
            nextProps: TimePickerProps
        ) {
            const partialState: Partial<TimePickerState> = {}
            const {value} = nextProps

            if (typeof value !== 'undefined') {
                assign(partialState, value)
            }

            return partialState
        }

        get className() {
            const {className} = this.props;
            const size = this.size;
            return c(
                'brick-time-picker',
                {
                    [`brick-time-picker-size-${size || ''}`]: size,
                },
                className
            )
        }

        get popupClassName() {
            const {
                popupClassName,
            } = this.props;

            const size = getClosestMappedSize(this.size);

            return c(
                /**
                 * @note: popper vs. popup 的话术问题, 值得思考
                 */
                'brick-time-picker-popper',
                {
                    [`brick-time-picker-popper-size-${size || ''}`]: size,
                },
                popupClassName
            );
        }

        get shouldShowPanel() {
            return this.state.open
                || this.state.isUserInput
        }

        checkCouldClosePanel = (propName: 'hour' | 'minute' | 'second') => {
            if (this.initialSelect.enabled) {
                this.initialSelect[propName] = true;
            }

            this.isClickedOnPanel = true;
            // debug('this.isClickedOnPanel = true;')
            void Promise.resolve().then(() => {
                // debug('this.isClickedOnPanel = false;')
                this.isClickedOnPanel = false;
            })

            // debug('this.isInitialSelected()', this.isInitialSelected());
            if (this.isInitialSelected()) {
                this.initialSelect.enabled = false;

                this.stateOpen.set(false)
                return;
            }
        }

        triggerChange(
            state: {
                hour?: number
                minute?: number
                second?: number
            }
        ) {
            const {onChange, showSecond = false} = this.props
            let {
                hour,
                minute,
                second,
            } = assign(
                pick(this.state, 'hour', 'minute', 'second'),
                state
            )

            const shouldFormalizeTimeProp = !isAllUndefined(
                {hour, minute, second},
                getColNames(showSecond)
            )

            if (shouldFormalizeTimeProp) {
                hour = hour || 0;
                minute = minute || 0;
                second = showSecond ? (second || 0) : undefined;
            }

            this.setState({hour, minute, second});
            onChange && onChange(new TimeValue(
                hour, minute, second,
                showSecond
            ))
        }

        handleTextChange = (text: string) => {
            if (!text.trim()) {
                const state = {
                    hour: undefined,
                    minute: undefined,
                    second: undefined,
                }

                this.triggerChange(state)
                return
            }

            if (!validateTimeText(text)) {
                return
            }

            const state = extractFromTimeText(text)
            this.triggerChange(state)

            const panelElem = this.refPanel.elem
            panelElem?.scrollToSelected()
        }

        handleFocusOnInput = (e: FocusEvent) => {
            const onFocus = this.props.onFocus
            onFocus && onFocus(e)

            this.stateOpen.set(true)
        }

        handleBlurFromInput = (e: FocusEvent) => {
            const onBlur = this.props.onBlur
            onBlur && onBlur(e)
        }

        handlePanelChange: UiTimePanel['props']['onChange'] = (
            propName: string,
            value
        ) => {
            this.checkCouldClosePanel(propName as 'hour' | 'minute' | 'second')
            this.triggerChange({[propName]: value})
        }

        getSnapshotBeforeUpdate = (): any | null => {
            if (!this.isClickedOnPanel) {
                // debug('if (!this.isClickedOnPanel) {', this.isClickedOnPanel)
                /**
                 * @note: 此处直接使用 state 的 value,
                 * 因为在 getDerivedStateFromProps 处,
                 * 已经对 state 进行了规范化,
                 *
                 * 所以此处 state.value, 必然是一个 TimeValue 对象
                 */
                const {hour, minute, second} = this.state;
                const {showSecond} = this.props;
                if (isAllUndefined({hour, minute, second}, getColNames(showSecond))) {
                    this.initialSelect = TimePicker.getInitialSelect(true);
                } else {
                    this.initialSelect = TimePicker.getInitialSelect(false);
                }
            }

            return null
        }

        componentDidMount(): void {
            this.listenerGroup.add(document, {
                mousedown: (e: MouseEvent) => {
                    const target = e.target as Node

                    if (
                        this.state.open
                        && !this.checkClickOnInput.call(target)
                        && !this.checkClickOnPanel.call(target)
                    ) {
                        this.stateOpen.set(false)
                    }
                },
            })
        }

        componentWillUnmount(): void {
            this.listenerGroup.removeAll()
        }

        componentDidUpdate(): void {
            // @note: do nothing as React complains about getSnapshotBeforeUpdate pairing problem
        }

        render() {
            const {hour, minute, second} = this.state
            const props = this.props
            const {
                showSecond,
                iconSvg,
                iconNode,
            } = props

            const computedIconSvg = iconSvg || (iconNode ? undefined : defaultIconSvg)
            const computedIconNode = iconNode || (iconSvg ? undefined : defaultIconNode)

            const {
                enabled,
                hour: isHourSelected,
                minute: isMinuteSelected,
                second: isSecondSelected,
            } = this.initialSelect;

            return (
                <div
                    className={this.className}
                    ref={this.refInputWrapper.ref}
                >

                    <TimeUserInput
                        refInput={this.refInput.ref}
                        value={getTimeText(
                            showSecond || false,
                            hour,
                            minute,
                            second
                        )}
                        onChange={this.handleTextChange}
                        stateUserInput={this.stateUserInput}
                        stateOpen={this.stateOpen}
                        onFocus={this.handleFocusOnInput}
                        onBlur={this.handleBlurFromInput}
                        iconSvg={computedIconSvg}
                        iconNode={computedIconNode}
                        size={this.size}
                        clearable={props?.clearable || false}
                        status={props.status}
                        disabled={props.disabled}
                        placeholder={props.placeholder}
                    />

                    <Popper
                        className={this.popupClassName}
                        visible={this.shouldShowPanel}
                        placement={'bottom-start'}
                        target={
                            this.refInput.elem
                            && this.refInput.elem.elemInput
                            || undefined
                        }
                    >
                        <UiTimePanel
                            hour={(enabled && !isHourSelected) ? undefined : hour}
                            minute={(enabled && !isMinuteSelected) ? undefined : minute}
                            second={(enabled && !isSecondSelected) ? undefined : second}
                            showSecond={showSecond}
                            onChange={this.handlePanelChange}
                            ref={this.refPanel.ref}
                            refWrapper={this.refPanelWrapper.ref}
                        />
                    </Popper>
                </div>
            )
        }
    }
}
