/* eslint-disable camelcase */
import dayjs, {Dayjs} from 'dayjs'
import {ReactNode, MouseEvent} from 'react'
import {createStateOperator} from '@befe/brick-utils'
import {UILayoutItem} from '../ui-comps/ui-item'
import {
    TypeItemStatus,
    TypeLayoutItem,
    TypePickerContentType,
} from '../module-defs/ui-types'
import {getMonthRows, Type_GetMonthText} from '../utils/date-utils'
import {UiItemLayout} from '../ui-comps/ui-item-layout'
import {UiPanelTitle} from '../ui-comps/ui-panel-title'
import {UiPanel} from '../ui-comps/ui-panel'
import {InnerRangePicker} from '../inner-comps/inner-range-picker'
import {TypeInnerDatePicker} from '../inner-comps/create-inner-date-picker'
import {PanelComp} from '../module-defs/common-types'
import {createAside} from './create-aside'
import {
    createDisplayedDateSwitcher,
    TypeMonthPanelDisplayProp,
    TypeMonthPanelValueProp,
} from './create-date-switcher'
import {createInitOpts} from './create-init-opts'
import {normalizeRange} from './create-date-panel'


export interface TypeInitOpts {
    onSelected?: (value: Dayjs) => void
    onSelectedRange?: (start: Dayjs, end: Dayjs) => void
    onZoomIn?: () => void
}

const startDisplayProp = 'startDisplayedDate'
const endDisplayProp = 'endDisplayedDate'

const zoomedStartDisplayProp = 'startZoomedDisplayedDate'
const zoomedEndDisplayProp = 'endZoomedDisplayedDate'

export function createMonthPanel(
    comp: TypeInnerDatePicker | InnerRangePicker,
    {
        isForDateZooming = false,
        isRange = false,
        valueProp,

        isSelectingProp = 'isSelecting',
        firstSelectedDateProp = 'firstSelectedDate',
        secondHoveringDateProp = 'secondHoveringDate',

        startValueProp = 'startValue',
        endValueProp = 'endValue',

        isStartPanel = false,

        onMouseDown,

        displayProp,
        getTitle,
        getMonthText,

        syncRangePickersYearAside,
    }: {
        isForDateZooming?: boolean
        isRange?: boolean
        valueProp?: TypeMonthPanelValueProp

        isSelectingProp?: 'isSelecting'
        firstSelectedDateProp?: 'firstSelectedDate'
        secondHoveringDateProp?: 'secondHoveringDate'

        startValueProp?: 'startValue'
        endValueProp?: 'endValue'

        isStartPanel?: boolean
        startDisplayProp?: 'startDisplayedDate'
        endDisplayProp?: 'endDisplayedDate'

        onMouseDown?: (e: MouseEvent) => void
        displayProp?: TypeMonthPanelDisplayProp

        getTitle: (date: Dayjs) => string
        getMonthText: Type_GetMonthText

        syncRangePickersYearAside?: () => void
    }
) {
    const [
        getState,
        setState,
    ] = createStateOperator(comp as PanelComp)

    const initOpts = createInitOpts<TypeInitOpts>('MonthPanel')

    const getDisplayProp = () => {
        if (!isRange) {
            return displayProp!
        }

        if (isStartPanel) {
            if (isForDateZooming) {
                return zoomedStartDisplayProp
            }
            return startDisplayProp
        } else {
            if (isForDateZooming) {
                return zoomedEndDisplayProp
            }
            return endDisplayProp
        }
    }

    const displayedSwitcher = createDisplayedDateSwitcher(
        comp,
        {
            displayProp: getDisplayProp(),
        }
    )

    const aside = createAside(
        comp,
        {
            displayProp: getDisplayProp(),
            onYearChange: () => {
                const shouldSyncDisplayPair = isRange && !isForDateZooming
                if (!shouldSyncDisplayPair) {
                    return
                }

                const startDisplayed = getState(startDisplayProp) as Dayjs
                const endDisplayed = getState(endDisplayProp) as Dayjs

                if (!startDisplayed.isBefore(endDisplayed)) {
                    if (isStartPanel) {
                        setState({
                            [endDisplayProp]: startDisplayed.add(1, 'year'),
                        })
                    }
                    else {
                        setState({
                            [startDisplayProp]: endDisplayed.subtract(1, 'year'),
                        })
                    }
                    syncRangePickersYearAside && syncRangePickersYearAside()
                }
            },
        }
    )

    const getDisplayed = (): Dayjs => {
        // @ts-ignore generic getter
        return getState(getDisplayProp())
    }

    const getPanelTitleText = () => {
        return getTitle(getDisplayed())
    }

    const handleZoomIn = () => {
        initOpts.get().onZoomIn!()
    }

    const getSelectedValueTimestamp = (): null | number => {
        const value = getState(valueProp!)
        // @ts-ignore generic getter

        // eslint-disable-next-line max-len
        // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
        return value && value.startOf('month').valueOf()
    }

    const handleSelectDate = (selectedTimestamp?: number) => {
        const selectedDate = dayjs(selectedTimestamp)

        setState({
            [valueProp!]: selectedDate,
        })

        initOpts.get().onSelected!(selectedDate)
    }

    function handleRangeClickOnItem(timestamp?: number) {
        const isSelecting = getState(isSelectingProp)
        const clickedDate = dayjs(timestamp).startOf('month')

        if (!isSelecting) {
            setState({
                [isSelectingProp]: true,
                [firstSelectedDateProp]: clickedDate,
                [secondHoveringDateProp]: null,
            })
        } else {
            let first = getState(firstSelectedDateProp) as Dayjs
            let second = clickedDate

            if (first.isAfter(second)) {
                [first, second] = [second, first]
            }

            setState({
                [isSelectingProp]: false,
                [startValueProp]: first,
                [endValueProp]: second,
            })

            initOpts.get().onSelectedRange!(first, second)
        }
    }

    function setRangeHoveringDate(timestamp?: number) {
        if (getState(isSelectingProp)) {
            setState({
                [secondHoveringDateProp]: dayjs(timestamp).startOf('month'),
            })
        }
    }

    const renderContentItem = (
        {
            today,

            selectedValueTimestamp,

            isRange = false,
            rangeStart,
            rangeEnd,

        }: {
            today: Dayjs

            selectedValueTimestamp?: number | null

            isRange?: boolean
            rangeStart?: Dayjs
            rangeEnd?: Dayjs
        },
        item: TypeLayoutItem
    ) => {
        const data = item.data!
        let disabled = false

        if (!isForDateZooming && comp.props.getDisabledItem) {
            disabled = comp.props.getDisabledItem(data.date, today)
        }

        const itemDate = item.data!.date
        const itemValue = item.data!.date.valueOf()

        let selected
        if (isRange) {
            selected = false
        } else if (selectedValueTimestamp) {
            selected = itemValue === selectedValueTimestamp
        }

        // 处理范围样式 & 间隙的样式
        let rangeType: TypeItemStatus['rangeType']
        let prevGapClassName

        const shouldSetGapClass = !data.outside
            && (itemDate.month() % 3 !== 0)

        /**
         * 此处可参考 : components/date-picker/src/modules/create-date-panel.tsx
         */
        if ((isRange && !isForDateZooming) && (rangeStart || rangeEnd)) {
            const {
                normalizedRangeStart,
                normalizedRangeEnd,
            } = normalizeRange(rangeStart, rangeEnd)

            if (
                normalizedRangeEnd.isSame(normalizedRangeStart)
                && itemDate.isSame(normalizedRangeStart)
            ) {
                rangeType = 'single'
            } else if (itemDate.isSame(normalizedRangeStart)) {
                rangeType = 'start'
            } else if (itemDate.isSame(normalizedRangeEnd)) {
                prevGapClassName = shouldSetGapClass ? 'inside-range' : undefined
                rangeType = 'end'
            } else if (
                itemDate.isAfter(normalizedRangeStart)
                && itemDate.isBefore(normalizedRangeEnd)
            ) {
                prevGapClassName = shouldSetGapClass ? 'inside-range' : undefined
                rangeType = 'inside'
            } else {
                rangeType = undefined
            }
        }

        return {
            prevGapClassName,
            elem: <UILayoutItem
                key={item.key}
                value={itemValue}
                text={item.text}
                outside={false}
                selected={selected}
                disabled={disabled}
                rangeType={rangeType}
                onClick={isRange && !isForDateZooming ? handleRangeClickOnItem : handleSelectDate}
                onMouseEnter={isRange && !isForDateZooming ? setRangeHoveringDate : undefined}
            />,
        }
    }

    const renderPanelContent = () => {
        const displayed = getDisplayed()
        return (
            <UiItemLayout
                rows={
                    getMonthRows(
                        getMonthText,
                        displayed.year()
                    )
                }
                /* eslint-disable-next-line react/jsx-no-bind */
                renderItem={
                    renderContentItem.bind(
                        null,
                        {
                            today: dayjs().startOf('month'),
                            selectedValueTimestamp: getSelectedValueTimestamp(),
                        }
                    )
                }
            />
        )
    }

    const renderPanelTitle = () => {
        // eslint-disable-next-line @typescript-eslint/unbound-method
        const {
            goBackwardYear,
            goForwardYear,
            goBackwardMonth,
            goForwardMonth,
        } = displayedSwitcher

        const [startDisplay, endDisplay] = getState(
            [startDisplayProp, endDisplayProp]
        ) as [Dayjs, Dayjs]

        return (
            <UiPanelTitle
                text={getPanelTitleText()}
                withZoomingIcon={isForDateZooming}
                isZooming
                shouldShowInner={false}
                rangeControl={
                    !isRange || isForDateZooming ? 'none'
                        : isStartPanel ? 'right' : 'left'
                }
                rangeShowType={!isRange || isForDateZooming
                    ? 'both'
                    : startDisplay.add(1, 'year').isBefore(endDisplay)
                        ? 'both'
                        : 'none'
                }
                onFastLeftClick={goBackwardYear}
                onFastRightClick={goForwardYear}
                onPlainLeftClick={goBackwardMonth}
                onPlainRightClick={goForwardMonth}
                onZoomingClick={handleZoomIn}
            />
        )
    }


    function renderRangeContent(): ReturnType<typeof renderPanelContent> {
        const displayed = getDisplayed()
        const isSelecting = getState(isSelectingProp)

        let rangeStart = isSelecting
            ? getState(firstSelectedDateProp) as Dayjs
            : getState(startValueProp) as Dayjs

        let rangeEnd = isSelecting
            ? getState(secondHoveringDateProp) as Dayjs
            : getState(endValueProp) as Dayjs

        if (rangeStart && rangeEnd && rangeStart.isAfter(rangeEnd)) {
            [rangeEnd, rangeStart] = [rangeStart, rangeEnd]
        }

        return (
            <UiItemLayout
                rows={getMonthRows(getMonthText, displayed.year())}
                /* eslint-disable-next-line react/jsx-no-bind */
                renderItem={renderContentItem.bind(
                    null, {
                        today: dayjs().startOf('month'),
                        isRange: true,
                        rangeStart: rangeStart,
                        rangeEnd: rangeEnd,
                    }
                )}
            />
        )
    }

    const api = {
        init(
            opts: TypeInitOpts
        ) {
            initOpts.init(opts)

            const switcherOpts = {
                onDisplayDateChange: (displayedDate: Dayjs) => {
                    aside.setYearRangeAnchor(displayedDate.year())
                    aside.syncAsideScroll()
                },
            }
            displayedSwitcher.init(switcherOpts)
        },

        getRenderElem() {
            const renderedElem: {
                titleElem: ReactNode
                contentElem: ReactNode
                asideElem: ReactNode
                contentType: TypePickerContentType
            } = {
                titleElem: renderPanelTitle(),
                contentElem: isRange ? renderRangeContent() : renderPanelContent(),
                asideElem: aside.render(),
                contentType: 'month',
            }

            return renderedElem
        },

        renderPanel() {
            return (
                <UiPanel
                    titleElem={renderPanelTitle()}
                    contentElem={renderPanelContent()}
                    asideElem={aside.render()}
                    contentType={'month'}
                    onMouseDown={onMouseDown}
                />
            )
        },
    }

    return aside.extendApi(api)
}

export type TypeModuleMonthPanel = ReturnType<typeof createMonthPanel>
