/* eslint-disable camelcase */
import c from 'classnames'
import {ConfiguredComponent} from '@befe/brick-core'
import {InputWrapper} from '@befe/brick-comp-input'
import {Icon} from '@befe/brick-comp-icon'
import {SvgCalendar, SvgDiscX, SvgMarkX} from '@befe/brick-icon'
import {Popper, PopperTrigger} from '@befe/brick-comp-popper'
import {getClosestMappedSize, safeInvoke} from '@befe/brick-utils'
import dayjs, {Dayjs} from 'dayjs'
import {DatePickerProps} from '../create-inner-date-picker'
import {
    TypeDatePickerMode,
    TypePickerSize,
    TypePickerStatus,
} from '../prop-types'
import {extendDayjsPlugins} from '../../utils/extend-dayjs-plugins'
import {MultiDatePanel} from './multi-date-panel'
import {MultiMonthPanel} from './multi-month-panel'
import {MultiQuarterPanel} from './multi-quarter-panel'

const CONST_className_selectedItem = 'brick-multi-date-picker-selected-popper-item'
const CONST_className_closeablePickerIcon = 'brick-multi-date-picker-closeable-icon'
const CONST_className_calendarIcon = 'brick-multi-date-picker-calendar-icon'
const CONST_className_closeIcon = 'brick-multi-date-picker-close-icon'

export interface MultiDatePickerProps {
    /**
     * 自定义 class
     */
    className?: string

    /**
     * 选中的日期值列表 (控制属性)
     * **注意: 此项值与 DatePicker 不一样, 单个值为 string, 而非 Dayjs 格式**
     *
     * e.g.
     * 日期格式 : ['2023-01-01', '2024-10-10', ...]
     * 月份格式 : ['2023-01', '2024-10', ...]
     * 季度格式 : ['2023-Q1', '2024-Q4', ...]
     *
     * @type string[]
     */
    value?: string[]

    /**
     * 选中的日期值 (非控制属性)
     * 值的说明见 value 属性
     *
     * @type string[]
     */
    defaultValue?: string[]

    disabled?: boolean

    /**
     * 整体大小控制 : xs, sm, md, lg, xl
     * 会影响 "输入框" & "面板" 的尺寸
     */
    size?: TypePickerSize

    /**
     * 是否当前出于出错状态
     */
    status?: TypePickerStatus

    /**
     * 选择器的取值类型
     */
    mode?: TypeDatePickerMode

    /**
     * 选中的值, 在 "输入框" 中展示的内容
     * 注意, 这个和 DatePicker 的 itemFormat (dayjs format 模板) 不同
     */
    formatItem?: (dateText: string) => string

    /**
     * @param date 当前计算 disabled 的日期 or 月份 or 季度 (**的第一天**)
     * @param firstDayOfCurrentUnit 今天的日期 or 月份 or 季度 (**的第一天**)
     */
    getDisabledItem?: (date: Dayjs, firstDayOfCurrentUnit: Dayjs) => boolean

    /**
     * 当日期面板打开时, 设置其 "显示的面板位置"
     *
     * 这个可能主要在当没有选择日期时, 用于确定面板月份的显示策略 ( 缺省方案是显示 "当月" 或 "当年" )
     */
    getDefaultDisplayDate?: (today: Dayjs) => Dayjs

    /**
     * 是否显示 : 今日 (注意, 本项在 `月份`, `季度` 模式下无效)
     *
     * @default false
     */
    hideNow?: boolean

    hideSummary?: boolean

    /**
     * 面板的附加 className
     */
    popupClassName?: string

    /**
     * 当选择中一个日期时, 会触发该回调
     *
     * @param selectedList 选中的日期列表, 具体格式见 value 属性
     */
    onChange?: (selectedList: string[]) => void

    /**
     * 日期选择器 input 框的 placeholder
     */
    placeholder?: string
}

interface MultiDatePickerState {
    innerValue: string[]
    isZooming: boolean
    displayedDate: Dayjs | null
}

const CONST_rgxQuarter = /^(\d{4})-Q(\d)$/

/**
 */
export class MultiDatePicker extends ConfiguredComponent<MultiDatePickerProps, MultiDatePickerState> {

    static defaultProps: MultiDatePickerProps = {
        mode: 'date',
        hideNow: false,
    }

    get size(): DatePickerProps['size'] {
        return this.getDefaultValueUsingTheme('size', 'baseSize')
    }

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

    get popupClassName() {
        const {popupClassName} = this.props
        const size = getClosestMappedSize(this.size)
        return c(
            'brick-date-picker-popper',
            {
                [`brick-date-picker-popper-size-${size || ''}`]: size,
            },
            popupClassName
        )
    }

    state: MultiDatePickerState = {
        innerValue: [],
        isZooming: false,
        displayedDate: null,
    }

    constructor(props: MultiDatePickerProps) {
        // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
        super(props)

        extendDayjsPlugins()

        const initValue = typeof this.props.defaultValue === 'undefined'
            ? this.props.value
            : this.props.defaultValue

        this.state.innerValue = initValue || []

        const firstDateText = this.state.innerValue[0]
        const today = dayjs()
        this.state.displayedDate = this.parseDateText(firstDateText)
            || today
    }

    static getDerivedStateFromProps(
        nextProps: MultiDatePickerProps,
        prevState: MultiDatePickerState
    ) {
        const nextValue = nextProps.value
        const nextInnerValue = typeof nextValue !== 'undefined'
            ? nextValue
            : prevState.innerValue

        let hasStateAltered = false
        const partialState: Partial<MultiDatePickerState> = {}

        if (nextInnerValue !== prevState.innerValue) {
            hasStateAltered = true
            partialState.innerValue = nextInnerValue
        }

        if (hasStateAltered) {
            return partialState
        }
        return null
    }

    render() {
        const disabled = this.props.disabled

        const elemInput = (
            <InputWrapper
                className={this.className}
                suffix={this.renderPickerIcon()}
                size={this.size}
                status={this.props.status}
                disabled={disabled}
            >
                {this.renderInputContent()}
            </InputWrapper>
        )

        if (disabled) {
            return elemInput
        }

        return (
            <PopperTrigger
                type={'click'}
                beforeChange={this.handleTogglePopper}
            >
                {elemInput}
                <Popper
                    className={this.popupClassName}
                    placement={'bottom-start'}
                >
                    {this.renderPanelContent()}
                </Popper>
            </PopperTrigger>
        )
    }

    private renderInputContent() {
        const innerValue = this.state.innerValue

        const CONST_className_multiDate_inputContent = 'brick-multi-date-picker-input-content'
        const CONST_className_multiDate_placeholder = 'brick-multi-date-picker-placeholder'

        if (innerValue.length === 0) {
            return (
                <div
                    className={c(
                        CONST_className_multiDate_inputContent,
                        CONST_className_multiDate_placeholder
                    )}
                >
                    {this.props.placeholder || '\u00A0'}
                </div>
            )
        }

        const inputValueText = innerValue
            .map(
                dateText => {
                    if (this.props.formatItem) {
                        return this.props.formatItem(dateText)
                    }
                    return dateText
                }
            ).join(', ')

        return (
            <div
                className={CONST_className_multiDate_inputContent}
            >
                {inputValueText}
            </div>
        )
    }

    private readonly dayjsCached = new Map<string, Dayjs>()
    private readonly parseDateText = (
        dayText?: string
    ) => {
        if (!dayText) {
            return null
        }

        const {
            mode,
        } = this.props

        const dayjsValue = this.dayjsCached.get(dayText)

        if (dayjsValue) {
            return dayjsValue
        }

        if (mode === 'date') {
            const dayjsValue = dayjs(dayText)
            this.dayjsCached.set(dayText, dayjsValue)
            return dayjsValue
        }

        if (mode === 'month') {
            const dayjsValue = dayjs(dayText + '-01')
            this.dayjsCached.set(dayText, dayjsValue)
            return dayjsValue
        }

        if (mode === 'quarter') {
            const quarterMatch = CONST_rgxQuarter.exec(dayText)
            if (!quarterMatch) {
                throw new Error(`Invalid quarter format: ${dayText}`)
            }
            const year = quarterMatch[1]
            const quarter = quarterMatch[2]
            const firstMonthOfQuarter = quarter === '1' ? '01'
                : quarter === '2' ? '04'
                    : quarter === '3' ? '07'
                        : '10'

            const firstDayOfQuarter = dayjs(
                `${year}-${firstMonthOfQuarter}-01`
            )
            this.dayjsCached.set(dayText, firstDayOfQuarter)
            return firstDayOfQuarter
        }

        throw new Error(`Invalid mode: ${mode}`)
    }

    private renderPickerIcon() {
        if (this.props.hideSummary) {
            return this.renderPickerIcon_plainIcon()
        }
        return this.renderPickerIcon_withSelectedList()
    }

    private renderPickerIcon_withSelectedList() {
        const dateCount = this.state.innerValue.length
        if (dateCount > 0) {
            return (
                <span onClick={e => e.stopPropagation()}>
                    <PopperTrigger
                        type={'hover'}
                    >
                        <span>
                            <Icon
                                svg={SvgCalendar}
                            />
                            <span
                                className={'brick-multi-date-picker-date-count'}
                            >
                                {dateCount}
                            </span>
                        </span>
                        <Popper
                            className={'brick-multi-date-picker-selected-popper'}
                        >
                            {this.renderSelectedList_clearAll(dateCount)}
                            {this.renderSelectedList()}
                        </Popper>
                    </PopperTrigger>
                </span>
            )
        }
        return (
            <Icon
                svg={SvgCalendar}
            />
        )
    }

    private renderSelectedList_clearAll(dateCount: number) {
        const {
            disabled,
        } = this.props

        if (disabled) {
            return null
        }

        return (
            <div
                className={c(
                    CONST_className_selectedItem,
                    'brick-multi-date-picker-selected-popper-clear-all'
                )}
                onClick={this.clearAllSelected}
            >
                清除 {dateCount} 个选择
            </div>
        )
    }

    private renderPickerIcon_plainIcon() {
        const {
            disabled,
        } = this.props

        if (disabled) {
            return <Icon svg={SvgCalendar} />
        }

        const {
            innerValue,
        } = this.state

        if (innerValue.length === 0) {
            return <Icon svg={SvgCalendar} />
        }

        return (
            <span
                className={CONST_className_closeablePickerIcon}
                onClick={this.clearAllSelected}
            >
                <Icon
                    className={CONST_className_calendarIcon}
                    svg={SvgCalendar}
                />
                <Icon
                    className={CONST_className_closeIcon}
                    svg={SvgDiscX}
                />
            </span>
        )
    }

    private renderSelectedList() {
        const disabled = this.props.disabled
        return this.state
            .innerValue
            .slice()
            .sort()
            .map(dateText => {
                const renderDeleteIcon = () => {
                    if (disabled) {
                        return null
                    }
                    return (
                        <Icon
                            svg={SvgMarkX}
                            onClick={() => this.removeSelectedDate(dateText)}
                        />
                    )
                }

                return (
                    <div
                        className={CONST_className_selectedItem}
                        key={dateText}
                    >
                        <span>
                            {dateText}
                        </span>
                        {renderDeleteIcon()}
                    </div>
                )
            })
    }

    private renderPanelContent() {
        const {
            mode,
        } = this.props

        const {
            isZooming,
        } = this.state

        if (mode === 'date') {
            if (isZooming) {
                return this.renderDateZoomedMonthPanel()
            }
            return this.renderDatePanel()
        }

        if (mode === 'month') {
            return this.renderMonthPanel()
        }

        return this.renderQuarterPanel()
    }

    private renderQuarterPanel() {
        return (
            <MultiQuarterPanel
                value={this.getValueList()}
                displayedDate={this.state.displayedDate!}
                hideNow
                getDisabledItem={this.props.getDisabledItem}
                onPrevYear={this.handlePrevDisplayedYear}
                onNextYear={this.handleNextDisplayedYear}
                onDisplayedDateChange={this.handleDisplayedDateChange}
                onClick={this.handleDateClick}
            />
        )
    }

    private renderDatePanel() {
        return (
            <MultiDatePanel
                value={this.getValueList()}
                displayedDate={this.state.displayedDate!}
                hideNow={this.props.hideNow}
                getDisabledItem={this.props.getDisabledItem}
                onPrevYear={this.handlePrevDisplayedYear}
                onNextYear={this.handleNextDisplayedYear}
                onPrevMonth={this.handlePrevDisplayedMonth}
                onNextMonth={this.handleNextDisplayedMonth}
                onToggleZooming={this.handleToggleZooming}
                onClick={this.handleDateClick}
            />
        )
    }

    private renderMonthPanel() {
        return (
            <MultiMonthPanel
                value={this.getValueList()}
                displayedDate={this.state.displayedDate!}
                hideNow
                getDisabledItem={this.props.getDisabledItem}
                onPrevYear={this.handlePrevDisplayedYear}
                onNextYear={this.handleNextDisplayedYear}
                onDisplayedDateChange={this.handleDisplayedDateChange}
                onClick={this.handleDateClick}
            />
        )
    }

    private renderDateZoomedMonthPanel() {
        const selectedDisplayed = this.state.displayedDate
            ? [this.state.displayedDate.startOf('month').valueOf()]
            : []

        return (
            <MultiMonthPanel
                value={selectedDisplayed}
                displayedDate={this.state.displayedDate!}
                hideNow
                onPrevYear={this.handlePrevDisplayedYear}
                onNextYear={this.handleNextDisplayedYear}
                onDisplayedDateChange={this.handleDisplayedDateChange}
                onClick={this.handleDateForZoomingClick}
                onToggleZooming={this.handleToggleZooming}
            />
        )
    }

    private getValueList() {
        return this.state.innerValue.map(
            date => this.parseDateText(date)!.valueOf()
        )
    }

    private readonly handleDateForZoomingClick = (dateValue?: number) => {
        if (!dateValue) {
            return
        }

        const displayedDate = dayjs(dateValue)
        this.setState({
            isZooming: false,
            displayedDate,
        })
    }

    private readonly handleDateClick = (dateValue?: number) => {
        if (!dateValue) {
            return
        }

        const doesExist = this.state.innerValue.some(
            dateText => {
                return this.parseDateText(dateText)!.valueOf() === dateValue
            }
        )

        if (doesExist) {
            const nextInnerValue = this.state.innerValue.filter(
                dateText => {
                    return this.parseDateText(dateText)!.valueOf() !== dateValue
                }
            )
            this.handleChange(nextInnerValue)
            return
        }

        this.handleChange([
            this.toDateText(dateValue),
            ...this.state.innerValue,
        ])
    }

    private toDateText(dateValue: number) {
        const mode = this.props.mode
        if (mode === 'date') {
            return dayjs(dateValue).format('YYYY-MM-DD')
        }
        if (mode === 'month') {
            return dayjs(dateValue).format('YYYY-MM')
        }
        return dayjs(dateValue).format('YYYY-[Q]Q')
    }

    private readonly handlePrevDisplayedYear = () => {
        const nextDisplayedDate = this.state.displayedDate!.subtract(1, 'year')
        this.setState({
            displayedDate: nextDisplayedDate,
        })
    }

    private readonly handleNextDisplayedYear = () => {
        const nextDisplayedDate = this.state.displayedDate!.add(1, 'year')
        this.setState({
            displayedDate: nextDisplayedDate,
        })
    }

    private readonly handlePrevDisplayedMonth = () => {
        const nextDisplayedDate = this.state.displayedDate!.subtract(1, 'month')
        this.setState({
            displayedDate: nextDisplayedDate,
        })
    }

    private readonly handleNextDisplayedMonth = () => {
        const nextDisplayedDate = this.state.displayedDate!.add(1, 'month')
        this.setState({
            displayedDate: nextDisplayedDate,
        })
    }

    private readonly handleToggleZooming = () => {
        const nextIsZooming = !this.state.isZooming
        this.setState({
            isZooming: nextIsZooming,
        })
    }

    private readonly handleTogglePopper = (visible: boolean) => {
        if (visible) {
            const today = dayjs()
            const firstDateText = this.state.innerValue[0]
            const nextDisplayedDate = this.parseDateText(firstDateText)
                || (this.props.getDefaultDisplayDate && this.props.getDefaultDisplayDate(today))
                || today

            this.setState({
                displayedDate: nextDisplayedDate,
            })
        }
    }

    private readonly handleDisplayedDateChange = (date: Dayjs) => {
        this.setState({
            displayedDate: date,
        })
    }

    private removeSelectedDate(dateText: string) {
        const nextInnerValue = this.state.innerValue.filter(
            eachDateText => eachDateText !== dateText
        )
        this.handleChange(nextInnerValue)
    }

    private handleChange(nextInnerValue: string[]) {
        this.setState({
            innerValue: nextInnerValue,
        })
        safeInvoke(
            this.props.onChange,
            nextInnerValue
        )
    }

    private readonly clearAllSelected = () => {
        this.handleChange([])
    }
}
