/* eslint-disable camelcase */
import dayjs, {Dayjs} from 'dayjs'
import {ReactNode, MouseEvent} from 'react'
import {createStateOperator} from '@befe/brick-utils'
import {TypeLayoutRenderedItem, UiItemLayout} from '../ui-comps/ui-item-layout'
import {
    getDateRows,
    getWeekTitleRow,
    Type_GetWeekDayTitle,
} from '../utils/date-utils'
import {UiPanel} from '../ui-comps/ui-panel'
import {UiPanelTitle} from '../ui-comps/ui-panel-title'
import {
    TypeItemStatus,
    TypeLayoutItem,
    TypePickerContentType,
} from '../module-defs/ui-types'
import {UILayoutItem} from '../ui-comps/ui-item'
import {
    InnerRangePicker,
    RangePickerState,
} from '../inner-comps/inner-range-picker'
import {
    DatePickerState,
    TypeInnerDatePicker,
} from '../inner-comps/create-inner-date-picker'
import {PanelComp} from '../module-defs/common-types'
import {
    createDisplayedDateSwitcher,
    TypeDisplayedDateSwitcherInitOpts,
} from './create-date-switcher'
import {createInitOpts} from './create-init-opts'

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

export function normalizeRange(
    rangeStart: Dayjs | undefined,
    rangeEnd: Dayjs | undefined
): ({
    normalizedRangeEnd: Dayjs
    normalizedRangeStart: Dayjs
}) {
    if (rangeStart && !rangeEnd) {
        return {
            normalizedRangeStart: rangeStart,
            normalizedRangeEnd: rangeStart,
        }
    } else if (!rangeStart && rangeEnd) {
        return {
            normalizedRangeStart: rangeEnd,
            normalizedRangeEnd: rangeEnd,
        }
    } else if (rangeStart && rangeEnd) {
        return {
            normalizedRangeStart: rangeStart,
            normalizedRangeEnd: rangeEnd,
        }
    }

    throw new Error('rangeStart & rangeEnd 至少应该一个不为空')
}

export function createDatePanel(
    comp: InnerRangePicker | TypeInnerDatePicker,
    {
        isRange = false,

        valueProp = 'value',

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

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


        isStartPanel = true,
        startDisplayProp = 'startDisplayedDate',
        endDisplayProp = 'endDisplayedDate',

        onMouseDown,

        displayProp = 'displayedDate',
        getTitle,
        getWeekDayText,

        renderPanelSideContent,
    }: {
        isRange?: boolean
        valueProp?: 'value'

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

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

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

        onMouseDown?: (e: MouseEvent) => void
        displayProp?: 'displayedDate'

        getTitle: (date: Dayjs) => string
        getWeekDayText: Type_GetWeekDayTitle

        renderPanelSideContent?: () => ReactNode
    }
) {

    const _comp = comp as PanelComp
    const initOpts = createInitOpts<TypeInitOpts>('DatePanel')

    const displayedSwitcher = createDisplayedDateSwitcher(
        comp,
        {
            displayProp: !isRange ? displayProp
                : isStartPanel ? startDisplayProp
                    : endDisplayProp,
        }
    )

    const [
        getState,
        setState,
    ] = createStateOperator(_comp)

    function getDisplayed() {
        if (isRange) {
            if (isStartPanel) {
                return getState(startDisplayProp)
            }
            return getState(endDisplayProp)
        }
        return getState(displayProp)
    }

    function getPanelTitleText(): string {
        return getTitle(getDisplayed() as Dayjs)
    }

    const handleZoomOut = () => {
        initOpts.get().onZoomOut()
    }

    const handleSelectDate = (selectedTimestamp?: number) => {
        const selectedDate = dayjs(selectedTimestamp)
        _comp.setState({
            [valueProp]: selectedDate,
        } as DatePickerState & RangePickerState)

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

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

        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)
        }
    }

    const setRangeHoveredDate = (timestamp?: number) => {
        if (getState(isSelectingProp)) {
            setState({
                [secondHoveringDateProp]: dayjs(timestamp).startOf('day'),
            })
        }
    }

    const handleClickOutside = (timestamp?: number) => {
        _comp.setState({
            [displayProp]: dayjs(timestamp).startOf('month'),
        })
    }

    function renderContentItem(
        {
            today,

            selectedValue,

            isRange = false,
            rangeStart,
            rangeEnd,
        }: {
            today: Dayjs

            selectedValue?: number | null

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

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

        const itemDate = item.data!.date

        // 处理已选择情况
        let selected
        if (isRange) {
            selected = false
        } else if (selectedValue) {
            selected = itemDate.isSame(selectedValue, 'date')
        }

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

        const shouldSetGapClass = !data.outside && itemDate.get('date') !== 1

        if (isRange && (rangeStart || rangeEnd)) {
            const {
                normalizedRangeStart,
                normalizedRangeEnd,
            } = normalizeRange(rangeStart, rangeEnd)

            /**
             * 日历渲染范围 (range) 中, 对显示的类型进行匹配处理,
             * 非 Outside 类别的 item, 会有特定的样式处理
             *
             * - St : Start
             * - Ed : End
             * - In : Inside
             * - Sg : Single
             * - X : Outside
             *
             * 1. 当 rangeStart < rangeEnd 时
             *
             * X X X [St] In In In In [Ed] X X X
             *
             * 2. 当 rangeStart === rangeEnd 时
             *
             * X X X [Sg] X X X
             */
            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={itemDate.valueOf()}
                text={item.text}
                outside={data.outside}
                selected={selected}
                disabled={disabled}
                today={item.key === today.valueOf()}
                rangeType={rangeType}
                onClick={isRange ? handleRangeClickOnItem : handleSelectDate}
                onMouseEnter={isRange ? setRangeHoveredDate : undefined}
                onClickOutside={isRange ? handleRangeClickOnItem : handleClickOutside}
            />,
        }
    }

    function renderPanelSideContentWrapper(sideContent?: ReactNode | null) {
        if (!sideContent) {
            return null
        }

        return (
            <div className={'brick-date-picker-panel-side-content-wrapper'}>
                {sideContent}
            </div>
        )
    }

    function renderPanelContent(sideContent?: ReactNode | null) {
        const displayed = getDisplayed() as Dayjs
        return (
            <>
                <UiItemLayout
                    titleRow={getWeekTitleRow({getWeekDayText})}
                    rows={
                        getDateRows(
                            displayed.year(),
                            displayed.month()
                        )
                    }
                    /* eslint-disable-next-line react/jsx-no-bind */
                    renderItem={
                        renderContentItem.bind(
                            null,
                            {
                                today: dayjs().startOf('day'),
                                selectedValue: getState(valueProp) as unknown as number,
                            }
                        )
                    }
                />
                {renderPanelSideContentWrapper(sideContent)}
            </>
        )
    }

    function renderRangePanelContent(): ReturnType<typeof renderPanelContent> {
        const displayed = getDisplayed() as Dayjs
        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
                titleRow={getWeekTitleRow({getWeekDayText})}
                rows={getDateRows(
                    displayed.year(),
                    displayed.month()
                )}
                /* eslint-disable-next-line react/jsx-no-bind */
                renderItem={
                    renderContentItem.bind(
                        null,
                        {
                            today: dayjs().startOf('day'),
                            isRange: true,

                            rangeStart: rangeStart,
                            rangeEnd: rangeEnd,
                        }
                    )
                }
            />
        )
    }

    function renderPanelTitle() {
        const {
            goBackwardYear,
            goForwardYear,
            goBackwardMonth,
            goForwardMonth,
        } = displayedSwitcher

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


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

    return {
        init(
            panelOpts: TypeInitOpts,
            switcherOpts?: TypeDisplayedDateSwitcherInitOpts
        ) {
            initOpts.init(panelOpts)
            if (switcherOpts) {
                displayedSwitcher.init(switcherOpts)
            }
        },

        getRenderElem() {
            const renderedElem = {
                titleElem: renderPanelTitle(),
                contentElem: isRange ? renderRangePanelContent() : renderPanelContent(),
                contentType: 'date',
                asideElem: undefined,
            }

            return renderedElem as {
                titleElem: ReturnType<typeof renderPanelTitle>
                contentElem: ReturnType<typeof renderPanelContent>
                contentType: TypePickerContentType
                asideElem: undefined
            }
        },

        renderPanel() {
            const sideContent = (!isRange && renderPanelSideContent) ? renderPanelSideContent() : null
            const panelClassName = sideContent
                ? 'panel-with-side-content'
                : ''

            return (
                <UiPanel
                    className={panelClassName}
                    titleElem={renderPanelTitle()}
                    contentElem={
                        isRange
                            ? renderRangePanelContent()
                            : renderPanelContent(sideContent)
                    }
                    contentType={'date'}
                    onMouseDown={onMouseDown}
                />
            )
        },
    }
}
