<template>
    <div :class="wrapperClasses">
        <component 
        :is="panel" 
        ref="pickerPanel" 
        :information="information" 
        :calendarWidth='calendarWidth' 
        :steps="steps" 
        :value="internalValue" 
        :picker-type="type" 
        :multiple="multiple" 
        :focused-date="focusedDate" 
        :on-panel-date-change="onPanelDateChange" 
        v-bind="ownPickerProps" 
        @on-pick="onPick" 
        @on-pick-clear="handleClear" 
        @on-pick-success="onPickSuccess" 
        @on-pick-click="disableClickOutSide = true"></component>
    </div>
</template>
<script>
import Drop from '../../components/select/dropdown.vue';
import { directive as clickOutside } from 'v-click-outside-x';
import { oneOf } from '../../utils/assist';
import {
  DEFAULT_FORMATS,
  RANGE_SEPARATOR,
  TYPE_VALUE_RESOLVER_MAP
} from './util';
import { findComponentsDownward } from '../../utils/assist';
import Emitter from '../../mixins/emitter';

const prefixCls = 'ivu-calendar';
const isEmptyArray = val =>
  val.reduce(
    (isEmpty, str) =>
      (isEmpty && !str) || (typeof str === 'string' && str.trim() === ''),
    true
  );

const pulseElement = el => {
    const pulseClass = 'ivu-calendar-btn-pulse';
    el.classList.add(pulseClass);
    setTimeout(() => el.classList.remove(pulseClass), 200);
};

export default {
    mixins: [Emitter],
    components: { Drop },
    directives: { clickOutside},
    props: {
        format: {
            type: String
        },
        readonly: {
            type: Boolean,
            default: false
        },
        disabled: {
            type: Boolean,
            default: false
        },
        editable: {
            type: Boolean,
            default: true
        },
        clearable: {
            type: Boolean,
            default: true
        },
        open: {
            type: Boolean,
            default: null
        },
        multiple: {
            type: Boolean,
            default: false
        },
        splitPanels: {
            type: Boolean,
            default: false
        },
        showWeekNumbers: {
            type: Boolean,
            default: false
        },
        startDate: {
            type: Date
        },
        name: {
            type: String
        },
        elementId: {
            type: String
        },
        steps: {
            type: Array,
            default: () => []
        },
        value: {
            type: [Date, String, Array]
        },
        options: {
            type: Object,
            default: () => ({})
        }
    },
    data() {
        const isRange = this.type.includes('range');
        const emptyArray = isRange ? [null, null] : [null];
        const initialValue = isEmptyArray(
      (isRange ? this.value : [this.value]) || []
    )
      ? emptyArray
      : this.parseDate(this.value);

        return {
            prefixCls: prefixCls,
            showClose: false,
            visible: false,
            internalValue: initialValue,
            disableClickOutSide: false, // fixed when click a date,trigger clickoutside to close picker
            disableCloseUnderTransfer: false, // transfer 模式下，点击Drop也会触发关闭,
            selectionMode: this.onSelectionModeChange(this.type),
            forceInputRerender: 1,
            isFocused: false,
            focusedDate: initialValue[0] || this.startDate || new Date(),
            focusedTime: {
                column: 0, // which column inside the picker
                picker: 0, // which picker
                active: false
            },
            internalFocus: false
        };
    },
    computed: {
        wrapperClasses() {
            return [
                prefixCls,
                {
                    [prefixCls + '-focused']: this.isFocused
                }
            ];
        },
        publicVModelValue() {
            if (this.multiple) {
                return this.internalValue.slice();
            } else {
                const isRange = this.type.includes('range');
                let val = this.internalValue.map(
          date => (date instanceof Date ? new Date(date) : date || '')
        );

                if (this.type.match(/^time/)) val = val.map(this.formatDate);
                return isRange || this.multiple ? val : val[0];
            }
        },
        publicStringValue() {
            const { formatDate, publicVModelValue, type } = this;
            if (type.match(/^time/)) return publicVModelValue;
            if (this.multiple) return formatDate(publicVModelValue);
            return Array.isArray(publicVModelValue)
        ? publicVModelValue.map(formatDate)
        : formatDate(publicVModelValue);
        },
        opened() {
            return this.open === null ? this.visible : this.open;
        },
        iconType() {
            let icon = 'ios-calendar-outline';
            if (this.type === 'time' || this.type === 'timerange')
                icon = 'ios-time-outline';
            if (this.showClose) icon = 'ios-close-circle';
            return icon;
        },
        transition() {
            const bottomPlaced = this.placement.match(/^bottom/);
            return bottomPlaced ? 'slide-up' : 'slide-down';
        },
        visualValue() {
            return this.formatDate(this.internalValue);
        },
        isConfirm() {
            return (
        this.confirm ||
        this.type === 'datetime' ||
        this.type === 'datetimerange' ||
        this.multiple
            );
        }
    },
    methods: {
        onSelectionModeChange(type) {
            if (type.match(/^date/)) type = 'date';
            this.selectionMode =
        oneOf(type, ['year', 'month', 'date', 'time']) && type;
            return this.selectionMode;
        },

        handleKeydown(e) {
            const keyCode = e.keyCode;
            const arrows = [37, 38, 39, 40];
            if (!this.visible && arrows.includes(keyCode)) {
                this.visible = true;
                return;
            }
            if (!arrows.includes(keyCode)) return; // ignore rest of keys
        },
        reset() {
            this.$refs.pickerPanel.reset && this.$refs.pickerPanel.reset();
        },
        navigateTimePanel(direction) {
            this.focusedTime.active = true;
            const horizontal = direction.match(/left|right/);
            const vertical = direction.match(/up|down/);
            const timePickers = findComponentsDownward(this, 'TimeSpinner');

            const maxNrOfColumns =
        (timePickers[0].showSeconds ? 3 : 2) * timePickers.length;
            const column = (currentColumn => {
                const incremented =
          currentColumn + (horizontal ? (direction === 'left' ? -1 : 1) : 0);
                return (incremented + maxNrOfColumns) % maxNrOfColumns;
            })(this.focusedTime.column);

            const columnsPerPicker = maxNrOfColumns / timePickers.length;
            const pickerIndex = Math.floor(column / columnsPerPicker);
            const col = column % columnsPerPicker;

            if (horizontal) {
                this.focusedTime = {
                    ...this.focusedTime,
                    column: column
                };
                timePickers.forEach((instance, i) => {
                    if (i === pickerIndex) instance.updateFocusedTime(col);
                    else instance.updateFocusedTime(-1, instance.focusedTime);
                });
            }

            if (vertical) {
                const increment = direction === 'up' ? 1 : -1;
                const timeParts = ['hours', 'minutes', 'seconds'];

                const pickerPossibleValues =
          timePickers[pickerIndex][`${timeParts[col]}List`];
                const nextIndex =
          pickerPossibleValues.findIndex(
            ({ text }) => this.focusedTime.time[pickerIndex][col] === text
          ) + increment;
                const nextValue =
          pickerPossibleValues[nextIndex % pickerPossibleValues.length].text;
                const times = this.focusedTime.time.map((time, i) => {
                    if (i !== pickerIndex) return time;
                    time[col] = nextValue;
                    return time;
                });
                this.focusedTime = {
                    ...this.focusedTime,
                    time: times
                };

                timePickers.forEach((instance, i) => {
                    if (i === pickerIndex) instance.updateFocusedTime(col, times[i]);
                    else instance.updateFocusedTime(-1, instance.focusedTime);
                });
            }
        },
        navigateDatePanel(direction, shift) {

            if (shift) {
                if (this.type === 'year') {
                    this.focusedDate = new Date(
            this.focusedDate.getFullYear() +
            this.focusedDate.getMonth(),
            this.focusedDate.getDate()
          );
                } else {
                    this.focusedDate = new Date(
            this.focusedDate.getDate()
          );
                }

                const position = direction.match(/left|down/) ? 'prev' : 'next';
                const double = direction.match(/up|down/) ? '-double' : '';
                const button = this.$refs.drop.$el.querySelector(
          `.ivu-calendar-${position}-btn-arrow${double}`
        );
                if (button) pulseElement(button);
                return;
            }

            const initialDate =
        this.focusedDate ||
        (this.internalValue && this.internalValue[0]) ||
        new Date();
            const focusedDate = new Date(initialDate);

            this.focusedDate = focusedDate;
        },
        handleIconClick(e) {
            if (this.showClose) {
                if (e) e.stopPropagation();
                this.handleClear();
            } else if (!this.disabled) {
                this.handleFocus();
            }
        },
        handleClear() {
            this.visible = false;
            this.internalValue = this.internalValue.map(() => null);
            this.$emit('on-clear');
            this.dispatch('FormItem', 'on-form-change', '');
            this.emitChange(this.type);
            this.reset();
            setTimeout(
        () => this.onSelectionModeChange(this.type),
        500 // delay to improve dropdown close visual effect
      );
        },
        emitChange(type) {
            this.$nextTick(() => {
                this.$emit('on-change', this.publicStringValue, type);
                this.dispatch('FormItem', 'on-form-change', this.publicStringValue);
            });
        },
        parseDate(val) {
            const isRange = this.type.includes('range');
            const type = this.type;
            const parser = (
        TYPE_VALUE_RESOLVER_MAP[type] || TYPE_VALUE_RESOLVER_MAP['default']
      ).parser;
            const format = this.format || DEFAULT_FORMATS[type];
            const multipleParser = TYPE_VALUE_RESOLVER_MAP['multiple'].parser;

            if (val && type === 'time' && !(val instanceof Date)) {
                val = parser(val, format);
            } else if (this.multiple && val) {
                val = multipleParser(val, format);
            } else if (isRange) {
                if (!val) {
                    val = [null, null];
                } else {
                    if (typeof val === 'string') {
                        val = parser(val, format);
                    } else if (type === 'timerange') {
                        val = parser(val, format).map(v => v || '');
                    } else {
                        const [start, end] = val;
                        if (start instanceof Date && end instanceof Date) {
                            val = val.map(date => new Date(date));
                        } else if (typeof start === 'string' && typeof end === 'string') {
                            val = parser(val.join(RANGE_SEPARATOR), format);
                        } else if (!start || !end) {
                            val = [null, null];
                        }
                    }
                }
            } else if (typeof val === 'string' && type.indexOf('time') !== 0) {
                val = parser(val, format) || null;
            }

            return isRange || this.multiple ? val || [] : [val];
        },
        formatDate(value) {
            const format = DEFAULT_FORMATS[this.type];

            if (this.multiple) {
                const formatter = TYPE_VALUE_RESOLVER_MAP.multiple.formatter;
                return formatter(value, this.format || format);
            } else {
                const { formatter } =
          TYPE_VALUE_RESOLVER_MAP[this.type] ||
          TYPE_VALUE_RESOLVER_MAP['default'];
                return formatter(value, this.format || format);
            }
        },
        onPick(dates, visible = false, type) {
            if (this.multiple) {
                const pickedTimeStamp = dates.getTime();
                const indexOfPickedDate = this.internalValue.findIndex(
          date => date && date.getTime() === pickedTimeStamp
        );
                const allDates = [...this.internalValue, dates].filter(Boolean);
                const timeStamps = allDates
          .map(date => date.getTime())
          .filter(
            (ts, i, arr) => arr.indexOf(ts) === i && i !== indexOfPickedDate
          ); 
                this.internalValue = timeStamps.map(ts => new Date(ts));
            } else {
                dates = this.parseDate(dates);
                this.internalValue = Array.isArray(dates) ? dates : [dates];
            }

            if (this.internalValue[0]) this.focusedDate = this.internalValue[0];
            this.focusedTime = {
                ...this.focusedTime
            };

            if (!this.isConfirm) this.onSelectionModeChange(this.type); // reset the selectionMode
            if (!this.isConfirm) this.visible = visible;
            this.emitChange(type);
        },
        onPickSuccess() {
            this.visible = false;
            this.$emit('on-ok');
            this.focus();
            this.reset();
        }
    },
    watch: {
        visible(state) {
            if (state === false) {
                this.$refs.drop.destroy();
            }
            this.$refs.drop.update();
            this.$emit('on-open-change', state);
        },
        value(val) {
            this.internalValue = this.parseDate(val);
        },
        open(val) {
            this.visible = val === true;
        },
        type(type) {
            this.onSelectionModeChange(type);
        },
    },
};
</script>