<template>
  <div
    class="m-stepper container px-0"
    :style="{'--active': activeColor, '--completed': completedColor,
             '--disabled': disabledColor, '--label': labelColor,
             '--default': defaultColor, '--divider': dividerColor,
             '--size': labelSize}">
    <div class="row no-gutters justify-content-between">
      <m-step-counter
        :current-step="value.value"
        :steps="stepsArr.length"
        :is-numeric="isNumeric"
        :completed="completed"
        @previous-step="previous" />

      <m-step
        v-for="(step, $index) in stepsArr"
        :key="$index"
        :step="step"
        :is-numeric="isNumeric"
        :debug="debug"
        :clickable="clickable"
        :index="$index"
        :completed="completed"
        :completed-message="completedMessage"
        :with-divider="true"
        :active="step.index === toIndex(value.value)"
        @change="handleChange" />
    </div>
  </div>
</template>

<script>
/* eslint-disable no-underscore-dangle */

import MStep from './components/MStep.vue';
import MStepCounter from './components/MStepCounter.vue';

/**
 * A steps indicator controller.
 */
export default {
  name: 'MStepper',

  components: {
    MStep,
    MStepCounter,
  },
  inheritAttrs: false,

  props: {
    /**
     * @model
     * Contains the current step value. Very similar to a
     * `value` attribute on an input. In most cases, you'll want
     * to set this as a two-way binding, using the `v-model` directive.
     */
    value: {
      type: Object,
      default: () => ({
        value: 1,
        id: undefined,
      }),
    },

    /**
     * Contains the steps count.
     */
    steps: {
      type: [Array, Number],
      default: 3,
    },

    /**
     * Optional completed stepper message.
     */
    completedMessage: {
      type: String,
      default: '',
    },

    /**
     * Sync state with storage?
     */
    persist: {
      type: Boolean,
      default: false,
    },

    /**
     * Which Storage API to use.
     * Should be used with `persist` prop.
     */
    storekeeper: {
      type: String,
      default: 'localStorage',
      validator(value) {
        return ['localStorage', 'sessionStorage'].includes(value);
      },
    },

    /**
     * Ability to change step at a click in indicator
     */
    clickable: {
      type: Boolean,
      default: false,
    },

    /**
     * Adds/Removes a divider to/from each Step component.
     */
    withDivider: {
      type: Boolean,
      default: true,
    },

    /**
     * Sets up debug mode, which reveals
     * the actual radio-button behind each step.
     */
    debug: {
      type: Boolean,
      default: false,
    },

    /**
     * Sets up width and height of label element.
     */
    labelSize: {
      type: String,
      default: '2.75rem',
    },

    /**
     * Sets up colors for the different states of the step.
     */
    activeColor: {
      type: String,
      default: '#34495E',
    },

    completedColor: {
      type: String,
      default: '#34495E',
    },

    disabledColor: {
      type: String,
      default: '#C1C5C7',
    },

    defaultColor: {
      type: String,
      default: '#636E72',
    },

    dividerColor: {
      type: String,
      default: '#C1C5C7',
    },

    labelColor: {
      type: String,
      default: '#F7F8F9',
    },
  },

  data() {
    const { value: { value } } = this;
    return {
      namespace: 'm-stepper',
      isNumeric: this.isNumericSteps(),
      completed: false,
      stepsArr: this.getStepsArr(),
      index: this.toIndex(value),
    };
  },

  computed: {
    id() {
      return `${this.namespace}-${this._uid}`;
    },
  },

  watch: {
    /**
     * When `value` prop changes, update index.
     */
    value: {
      handler({ value }) {
        this.index = this.toIndex(value);
        if (this.persist) {
          this.setStorage();
        }
      },
    },

    /**
     * When internal property `index` changes,
     * convert to value and update v-model.
     */
    index: {
      handler(index) {
        this.emitValue(this.toValue(index));
      },

    },
  },

  created() {
    /**
       * Update from Storage if persistent.
       */
    if (this.persist) {
      const storage = this.getStorage();
      if (storage) {
        this.stepsArr = storage.stepsArr;
        this.index = storage.index;
      } else {
        this.setStorage();
      }
    }
  },

  destroyed() {
    /**
     * Remove from Storage if persistent.
     */
    if (this.persist) {
      window[this.storekeeper].removeItem(this.id);
    }
  },

  methods: {
    /**
     * Converts index to value.
     */
    toValue(index) {
      return index + 1;
    },

    /**
     * Converts value to index.
     */
    toIndex(value = 0) {
      return value - 1;
    },

    /**
     * Whether a step
     * exists or not.
     */
    doesStepExist(index) {
      return !!this.stepsArr[index];
    },

    /**
     * Handle `change` event and
     * programmatic changes.
     */
    handleChange(index) {
      if (this.clickable && !this.completed) {
        this.changeStep(index);
      }
    },

    /**
     * Changes step by index.
     */
    changeStep(index) {
      const isNext = index === this.index + 1;
      const isPrevious = index === this.index - 1;

      if (isNext || isPrevious) {
        this.changeStepsStates(index);

        this.emitValue(this.toValue(index));
      }
    },

    changeStepsStates(index) {
      this.stepsArr.forEach((step) => {
        if (step.index < index) {
          this.setStep(step.index, 'active', false);
          this.setStep(step.index, 'visited', true);
          this.setStep(step.index, 'disabled', false);
        } else if (step.index === index) {
          this.setStep(step.index, 'active', true);
          this.setStep(step.index, 'disabled', false);
          this.setStep(step.index, 'visited', false);
        } else if (step.index === index + 1) {
          this.setStep(step.index, 'active', false);
          this.setStep(step.index, 'disabled', false);
          this.setStep(step.index, 'visited', false);
        } else if (step.index > index + 1) {
          this.setStep(step.index, 'active', false);
          this.setStep(step.index, 'disabled', true);
          this.setStep(step.index, 'visited', false);
        }
      });
    },

    /**
     * Complete all steps
     */
    completeSteps() {
      this.completed = true;
      this.stepsArr.forEach((step) => {
        this.setStep(step.index, 'active', false);
        this.setStep(step.index, 'visited', true);
        this.setStep(step.index, 'disabled', false);
      });

      this.emitValue(this.stepsArr.length);
    },

    /**
     * Value getter
     */
    getValue() {
      return this.value.value;
    },

    /**
     * Check if steps prop
     * is Numeric or Array.
     */
    isNumericSteps() {
      if (typeof this.steps === 'number') {
        return true;
      }
      return false;
    },

    /**
     * Constructs steps array
     * from `steps` prop.
     */
    getStepsArr() {
      let steps;
      if (this.isNumericSteps()) {
        steps = Array.from(Array(this.steps));
      } else {
        steps = this.steps;
      }
      return steps
        .map((step, index) => {
          const isFirst = index === 0;
          const isNext = index - 1 === 0;
          let disabled;

          if (isFirst || isNext) {
            disabled = false;
          } else {
            disabled = true;
          }
          const visited = false;
          const value = this.toValue(index);
          const isLast = index === (steps.length - 1);

          return {
            ...step,
            index,
            value,
            visited,
            disabled,
            isLast,
          };
        });
    },

    /**
     * Offsets stepper {n} steps.
     */
    offset(offset) {
      const index = this.index + offset;
      if (this.doesStepExist(index)) {
        this.changeStep(index);
      } else if (index === this.stepsArr.length) {
        this.completeSteps();
      }
    },

    /**
     * Goes to next step.
     */
    next() {
      if (!this.completed) {
        this.offset(1);
      }
    },

    /**
     * Goes to previous step.
     */
    previous() {
      if (!this.completed) {
        this.offset(-1);
      }
    },

    /**
     * Complete all steps.
     */
    complete() {
      this.completeSteps();
    },

    /**
     * Resets the stepper.
     */
    reset() {
      this.stepsArr = this.getStepsArr();
      this.index = 0;
      this.completed = false;
      this.$emit('reset');
    },

    /**
     * Sets a step property.
     */
    setStep(index, prop, value) {
      this.$set(this.stepsArr[index], prop, value);
    },

    /**
     * Storage setter.
     * @returns {void}
     */
    setStorage() {
      const { index, stepsArr } = this;
      window[this.storekeeper].setItem(
        this.id,
        JSON.stringify({ index, stepsArr }),
      );
    },
    /**
     * Storage getter.
     * @returns {Object}
     */
    getStorage() {
      return JSON.parse(window[this.storekeeper].getItem(this.id));
    },

    /**
     * Update v-model
     */
    emitValue(value) {
      const { id, completed } = this;
      this.$emit('input', { id, value, completed });
    },
  },
};
</script>
