import React from 'react';
import PropTypes from 'prop-types';
import {FormControl as BSFormControl} from 'react-bootstrap';
import classnames from 'classnames';
import {Icon} from '../../Icon';
import _ from 'lodash';
import {isElementFocused} from './datePickerHelpers';
import {FormControl} from '../FormControl';
import OutsideClickHandler from 'react-outside-click-handler';
import {KeyNames} from '../../../../utils/constants';
import {translate} from '../../../../utils';
import {wait} from '../../../utils/functions';

import lang from '../../../../lang/common/index-en_US';

const toFnWithDate = str => ({date}) => translate(str, {date});

let instanceCounter = 0;

const WAIT_TIME_OUT_WHEN_BLUR = 150;

export class DatePickerBase extends FormControl<any> {
    static propTypes = {
        ...FormControl.propTypes,
        inputProps: PropTypes.object,
        ariaLabel: PropTypes.string,
        disabled: PropTypes.bool,
        testAutomationId: PropTypes.string,
        dayPickerPhrase: PropTypes.object
    };

    static defaultProps = {
        ...FormControl.defaultProps,
        ariaLabel: '',
        dayPickerPhrase: {
            get calendarLabel() {
                return lang.DATEPICKER_PHRASE_CALENDAR_LABEL;
            },
            get closeDatePicker() {
                return lang.DATEPICKER_PHRASE_CLOSE_DATE_PICKER;
            },
            get focusStartDate() {
                return lang.DATEPICKER_PHRASE_FOCUS_START_DATE;
            },
            get clearDate() {
                return lang.DATEPICKER_PHRASE_CLEAR_DATE;
            },
            get clearDates() {
                return lang.DATEPICKER_PHRASE_CLEAR_DATES;
            },
            get jumpToPrevMonth() {
                return lang.DATEPICKER_PHRASE_JUMP_TO_PREV_MONTH;
            },
            get jumpToNextMonth() {
                return lang.DATEPICKER_PHRASE_JUMP_TO_NEXT_MONTH;
            },
            get keyboardShortcuts() {
                return lang.DATEPICKER_PHRASE_KEYBOARD_SHORTCUTS;
            },
            get showKeyboardShortcutsPanel() {
                return lang.DATEPICKER_PHRASE_SHOW_KEYBOARD_SHORTCUTS_PANEL;
            },
            get hideKeyboardShortcutsPanel() {
                return lang.DATEPICKER_PHRASE_HIDE_KEYBOARD_SHORTCUTS_PANEL;
            },
            get openThisPanel() {
                return lang.DATEPICKER_PHRASE_OPEN_THIS_PANEL;
            },
            get enterKey() {
                return lang.DATEPICKER_PHRASE_ENTER_KEY;
            },
            get leftArrowRightArrow() {
                return lang.DATEPICKER_PHRASE_LEFT_ARROW_RIGHT_ARROW;
            },
            get upArrowDownArrow() {
                return lang.DATEPICKER_PHRASE_UP_ARROW_DOWN_ARROW;
            },
            get pageUpPageDown() {
                return lang.DATEPICKER_PHRASE_PAGE_UP_PAGE_DOWN;
            },
            get homeEnd() {
                return lang.DATEPICKER_PHRASE_HOME_END;
            },
            get escape() {
                return lang.DATEPICKER_PHRASE_ESCAPE;
            },
            get questionMark() {
                return lang.DATEPICKER_PHRASE_QUESTION_MARK;
            },
            get selectFocusedDate() {
                return lang.DATEPICKER_PHRASE_SELECT_FOCUSED_DATE;
            },
            get moveFocusByOneDay() {
                return lang.DATEPICKER_PHRASE_MOVE_FOCUS_BY_ONE_DAY;
            },
            get moveFocusByOneWeek() {
                return lang.DATEPICKER_PHRASE_MOVE_FOCUS_BY_ONE_WEEK;
            },
            get moveFocusByOneMonth() {
                return lang.DATEPICKER_PHRASE_MOVE_FOCUS_BY_ONE_MONTH;
            },
            get moveFocustoStartAndEndOfWeek() {
                return lang.DATEPICKER_PHRASE_MOVE_FOCUSTO_START_AND_END_OF_WEEK;
            },
            get returnFocusToInput() {
                return lang.DATEPICKER_PHRASE_RETURN_FOCUS_TO_INPUT;
            },
            get keyboardNavigationInstructions() {
                return lang.DATEPICKER_PHRASE_KEYBOARD_NAVIGATION_INSTRUCTIONS;
            },
            get chooseAvailableDate() {
                return lang.DATEPICKER_PHRASE_CHOOSE_AVAILABLE_DATE;
            },
            get chooseAvailableStartDate() {
                return lang.DATEPICKER_PHRASE_CHOOSE_AVAILABLE_START_DATE;
            },
            get chooseAvailableEndDate() {
                return lang.DATEPICKER_PHRASE_CHOOSE_AVAILABLE_ENDDATE;
            },
            get dateIsUnavailable() {
                return lang.DATEPICKER_PHRASE_DATE_IS_UNAVAILABLE;
            },
            get dateIsSelected() {
                return lang.DATEPICKER_PHRASE_DATE_IS_SELECTED;
            }
        }
    };

    constructor(props, ctx) {
        super(props, ctx);
        this.state = {
            isDayPickerFocused: false,
            isDayPickerVisible: false,
            isPreventDayPickerClosed: false,
            isInputFocusedFromOutside: true
        };
        this.instanceId = instanceCounter++;
    }

    componentDidMount() {
        this.setState({isInputFocusedFromOutside: true});
        this.calendarContainer.addEventListener('keydown', event => {
            if (this.state.isDayPickerVisible && event.key === KeyNames.Escape) {
                event.stopPropagation();
            }
        });
    }

    instanceId: any;
    calendarContainer: any = null;

    onKeyDown = _.throttle(event => {
        switch (event.key) {
            case KeyNames.ArrowDown:
                this.onInputKeyDownArrowDown();
                break;

            case KeyNames.Escape:
            case KeyNames.Tab:
                this.closeDayPicker();
                break;

            default:
                break;
        }
    });

    onCalendarKeyUp = _.throttle(event => {
        if (event.key === KeyNames.Escape) {
            this.closeDayPicker();
            this.focusToInput();
        }
    });

    /** prevent dayPicker being closed after react-dates trigger blur on monthChange, yearChange */
    isPreventCloseDayPickerOnBlur = false;

    onInputKeyDownArrowDown = () => {
        this.showDayPicker();
        this.focusOnDateCell();
    };

    focusOnDateCell = () => {
        return this.setStatePromise({isDayPickerFocused: false}).then(() =>
            this.setStatePromise({isDayPickerFocused: true})
        );
    };

    blurDateCell = () => {
        return this.setStatePromise({isDayPickerFocused: false});
    };

    setStatePromise = state => {
        return new Promise(resolve => {
            this.setState(state, resolve);
        });
    };

    closeDayPicker() {
        return this.setStatePromise({
            isDayPickerVisible: false,
            isDayPickerFocused: false
        });
    }

    showDayPicker = () => {
        if (!this.state.isDayPickerVisible) {
            this.setState({
                isDayPickerVisible: true,
                isDayPickerFocused: false
            });
        }
    };

    handleFocusInput = () => {
        const {isInputFocusedFromOutside} = this.state;

        if (isInputFocusedFromOutside) {
            this.showDayPicker();
        }
    };

    handleToggleDayPickerDisplay = e => {
        if (!_.isEmpty(e)) {
            e.stopPropagation();
        }

        const {isDayPickerVisible} = this.state;

        if (isDayPickerVisible) {
            this.closeDayPicker();
        } else {
            this.showDayPicker();
        }
    };

    isFocusedInsideDayPickerContainer = () => isElementFocused(this.calendarContainer);

    focusToInput = () => this.focus();

    handleBlurDatePickerControl = () => {
        wait(WAIT_TIME_OUT_WHEN_BLUR).then(() => {
            if (!this.isPreventCloseDayPickerOnBlur && !this.isFocusedInsideDayPickerContainer()) {
                this.toggleIsInputFocusedFromOutside(true);
                this.closeDayPicker();
            }
        });
    };

    toggleIsInputFocusedFromOutside = isInputFocusedFromOutside => {
        return this.setStatePromise({isInputFocusedFromOutside});
    };

    handleMouseDownPreventCloseDayPicker = () => {
        this.isPreventCloseDayPickerOnBlur = true;
    };

    handleCloseDayPickerOnBlur = () => {
        this.isPreventCloseDayPickerOnBlur = false;
    };

    setContainerRef = el => (this.calendarContainer = el);

    handleOutsideClick = () => {
        this.toggleIsInputFocusedFromOutside(true);
        this.closeDayPicker();
    };

    /** @abstract */
    getDatePickerContainerClassName() {
        return '';
    }

    /** @abstract */
    renderDatePicker() {
        return null;
    }

    /** @abstract */
    getDisplayValue = () => null;

    /** @abstract */
    getPhrases() {
        const {
            calendarLabel,
            closeDatePicker,
            focusStartDate,
            clearDate,
            clearDates,
            jumpToPrevMonth,
            jumpToNextMonth,
            keyboardShortcuts,
            showKeyboardShortcutsPanel,
            hideKeyboardShortcutsPanel,
            openThisPanel,
            enterKey,
            leftArrowRightArrow,
            upArrowDownArrow,
            pageUpPageDown,
            homeEnd,
            escape,
            questionMark,
            selectFocusedDate,
            moveFocusByOneDay,
            moveFocusByOneWeek,
            moveFocusByOneMonth,
            moveFocustoStartAndEndOfWeek,
            returnFocusToInput,
            keyboardNavigationInstructions,

            chooseAvailableDate,
            chooseAvailableStartDate,
            chooseAvailableEndDate,
            dateIsUnavailable,
            dateIsSelected
        } = this.props.dayPickerPhrase;

        const phrases = {
            calendarLabel,
            closeDatePicker,
            focusStartDate,
            clearDate,
            clearDates,
            jumpToPrevMonth,
            jumpToNextMonth,
            keyboardShortcuts,
            showKeyboardShortcutsPanel,
            hideKeyboardShortcutsPanel,
            openThisPanel,
            enterKey,
            leftArrowRightArrow,
            upArrowDownArrow,
            pageUpPageDown,
            homeEnd,
            escape,
            questionMark,
            selectFocusedDate,
            moveFocusByOneDay,
            moveFocusByOneWeek,
            moveFocusByOneMonth,
            moveFocustoStartAndEndOfWeek,
            returnFocusToInput,
            keyboardNavigationInstructions,

            chooseAvailableDate: toFnWithDate(chooseAvailableDate),
            chooseAvailableStartDate: toFnWithDate(chooseAvailableStartDate),
            chooseAvailableEndDate: toFnWithDate(chooseAvailableEndDate),
            dateIsUnavailable: toFnWithDate(dateIsUnavailable),
            dateIsSelected: toFnWithDate(dateIsSelected)
        };

        _.forEach(phrases, (value, key) => {
            if (value === undefined) {
                phrases[key] = DatePickerBase.defaultProps[key];
            }
        });

        return phrases;
    }

    /** @abstract */
    handleMouseUpDayPickerContainer = () => {};

    handlePreventDefault = e => e.preventDefault();

    render() {
        const {testAutomationId, disabled, inputProps, dayPickerPhrase, ariaLabel} = this.props;
        const {keyboardNavigationInstructions} = dayPickerPhrase;
        const ariaDescribeId = `date-picker-input-describe-message-${this.instanceId}`;

        return (
            <OutsideClickHandler onOutsideClick={this.handleOutsideClick}>
                <div
                    ref={this.setContainerRef}
                    data-test-automation-id={testAutomationId}
                    className={classnames('rc-datepicker', {'rc-datepicker-disabled': disabled})}
                    onMouseDown={this.handleMouseDownPreventCloseDayPicker}
                    onKeyDown={this.handleCloseDayPickerOnBlur}
                    onKeyUp={this.handleCloseDayPickerOnBlur}
                    onBlur={this.handleBlurDatePickerControl}>
                    <Icon name="calendar" onClick={this.handleToggleDayPickerDisplay} />
                    <BSFormControl
                        onKeyDown={this.onKeyDown}
                        onFocus={this.handleFocusInput}
                        type="text"
                        ref="input"
                        value={this.getDisplayValue()}
                        onChange={this.handlePreventDefault}
                        aria-describedby={ariaDescribeId}
                        aria-label={ariaLabel}
                        {...inputProps}
                    />
                    <p className="sr-only" id={ariaDescribeId}>
                        {keyboardNavigationInstructions}
                    </p>
                    <div
                        data-test-automation-id="datePickerContainer"
                        className={`rc-datepicker-container ${this.getDatePickerContainerClassName()}`}
                        onKeyUp={this.onCalendarKeyUp}
                        onMouseUp={this.handleMouseUpDayPickerContainer}>
                        {this.renderDatePicker()}
                    </div>
                </div>
            </OutsideClickHandler>
        );
    }
}
