<!-- eslint-disable vue-a11y/click-events-have-key-events -->
<template>
  <div
    :id="id"
    ref="input"
    :key="componentId"
    :class="inputWrapperClasses"
    tabindex="-1"
    @click="focusMeasureInputAtIndex(0)"
    @keydown.left.prevent="handleMeasureFocusChange('previous')"
    @keydown.right.prevent="handleMeasureFocusChange('next')"
  >
    <div
      v-if="enabledMeasures.length"
      class="d-flex items-baseline gap-1"
    >
      <label
        v-for="measure in enabledMeasures"
        :key="`${componentId}_${measure.key}_input`"
        :for="`${componentId}_${measure.key}_input`"
        :class="[
          'sam-time-amount-input__inputWrapper',
          `${measure.value === 0 ? 'is-empty' : 'is-filled'}`,
        ]"
        @click.stop
      >
        <input
          :id="`${componentId}_${measure.key}_input`"
          :value="measure.value || null"
          :class="['sam-time-amount-input__input']"
          type="number"
          :placeholder="0"
          :min="measure.min || 0"
          @keydown="validateNumberInput"
          @input="updateMeasureValue(measure, $event.target.value || 0, 'input')"
          @change="updateMeasureValue(measure, $event.target.value || 0, 'change')"
        />
        <span class="sam-time-amount-input__inputLabel">{{ measure.label }}</span>
      </label>
    </div>
    <div class="d-flex items-center ml-auto">
      <SamIcon
        v-if="!disabled && clearable && inputHasValue"
        icon="interface-close"
        :size="SAMORA_SIZES.SMALL"
        class="sam-time-amount-input__icon clear-icon"
        @click.native.stop="clearInput"
      />
      <SamIcon
        v-if="statusIcon"
        :icon="statusIcon"
        :size="SAMORA_SIZES.SMALL"
        class="sam-time-amount-input__icon status-icon"
      />
    </div>
  </div>
</template>

<script>
import SAMORA_STYLES from '../../../utils/constants/samora-styles.enum';
import SAMORA_SIZES from '../../../utils/constants/samora-sizes.enum';
import SamIcon from '../SamIcon/SamIcon.vue';

export const INPUT_STYLES = {
  DEFAULT: SAMORA_STYLES.DEFAULT,
  DANGER: SAMORA_STYLES.DANGER,
  SUCCESS: SAMORA_STYLES.SUCCESS,
};

const styleIconMap = new Map([
  [INPUT_STYLES.DANGER, 'alert-exclamation-triangle'],
  [INPUT_STYLES.SUCCESS, 'interface-confirm-circle'],
]);

const DEFAULT_VALUE = {
  hours: 0,
  minutes: 0,
};

const ALLOWED_KEYS = [
  'Backspace',
  'Delete',
  'ArrowLeft',
  'ArrowRight',
  'ArrowUp',
  'ArrowDown',
  'Tab',
];

const MEASURES_DEFAULT_CONFIGS = {
  days: {
    label: 'd',
    min: 0,
    max: null,
  },
  hours: {
    label: 'h',
    min: 0,
    max: 23,
  },
  minutes: {
    label: 'm',
    min: 0,
    max: 59,
  },
  seconds: {
    label: 's',
    min: 0,
    max: 59,
  },
};

export default {
  name: 'SamTimeAmountInput',
  components: {
    SamIcon,
  },
  inheritAttrs: false,
  props: {
    /**
     * The component's id
     */
    id: {
      type: String,
      required: false,
      default: undefined,
    },
    /**
     * The bound model variable
     * @model
     */
    value: {
      type: Object,
      required: false,
      default: () => ({ ...DEFAULT_VALUE }),
    },
    /**
     * The time amount input configuration
     */
    config: {
      type: Object,
      required: false,
      default: () => MEASURES_DEFAULT_CONFIGS,
    },
    /**
     * If the input is disabled
     * @values true, false
     */
    disabled: {
      type: Boolean,
      required: false,
      default: false,
    },
    /**
     * If the input is clearable
     * @values true, false
     */
    clearable: {
      type: Boolean,
      required: false,
      default: true,
    },
    /**
     * The input's samora style to apply
     * @values default, danger, success
     */
    samStyle: {
      type: String,
      required: false,
      default: SAMORA_STYLES.DEFAULT,
      validator: (val) => Object.values(INPUT_STYLES).includes(val),
    },
  },
  data() {
    return {
      SAMORA_SIZES,
    };
  },
  computed: {
    componentId() {
      // eslint-disable-next-line no-underscore-dangle
      return this.id || `samora-time-amount-input__${this._uid}`;
    },
    inputWrapperClasses() {
      return [
        `sam-time-amount-input is-${this.samStyle}`,
        { 'is-disabled': this.disabled },
        { 'is-empty': !this.inputHasValue },
      ];
    },
    statusIcon() {
      return styleIconMap.get(this.samStyle);
    },
    inputHasValue() {
      const measureKeys = Object.keys(MEASURES_DEFAULT_CONFIGS);
      return !!Object.entries(this.value).some(
        ([key, value]) => measureKeys.includes(key) && value > 0
      );
    },
    measuresConfig() {
      return Object.entries(MEASURES_DEFAULT_CONFIGS).reduce((accConfig, [key, value]) => {
        // eslint-disable-next-line no-param-reassign
        accConfig[key] = { ...value, ...(this.config[key] || {}) };
        return accConfig;
      }, {});
    },
    enabledMeasures() {
      return Object.entries(this.value).reduce((accEnabledMeasures, [key, value]) => {
        const measureConfig = this.measuresConfig[key];
        if (measureConfig) accEnabledMeasures.push({ ...measureConfig, key, value });
        return accEnabledMeasures;
      }, []);
    },
  },
  methods: {
    validateNumberInput(event) {
      if (!/^[0-9]$/.test(event.key) && !ALLOWED_KEYS.includes(event.key)) {
        event.preventDefault();
      }
    },
    getInputWrapperNodeList() {
      return this.$refs.input?.querySelectorAll('.sam-time-amount-input__inputWrapper') ?? [];
    },
    handleMeasureFocusChange(direction) {
      const nodeArray = Array.from(this.getInputWrapperNodeList());
      const focusedInputIndex = nodeArray.findIndex((input) => input.matches(':focus-within'));

      if (focusedInputIndex < 0) return;

      if (direction === 'next' && focusedInputIndex < nodeArray.length - 1) {
        this.focusMeasureInputAtIndex(focusedInputIndex + 1);
        return;
      }

      if (direction === 'previous' && focusedInputIndex > 0) {
        this.focusMeasureInputAtIndex(focusedInputIndex - 1);
      }
    },
    updateMeasureValue(measure, value, eventTrigger) {
      const validatedValue = this.validateMeasureValue(value, [measure.min, measure.max]);
      const newValue = { ...this.value, [measure.key]: validatedValue ?? this.value[measure.key] };
      this.$emit(eventTrigger, newValue);
    },
    validateMeasureValue(value, [min, max]) {
      const number = parseInt(value, 10);
      if (Number.isNaN(number)) return null;
      if (min && number < +min) return null;
      if (max && number > +max) return null;
      return number;
    },
    focusMeasureInputAtIndex(index = 0) {
      const input = this.getInputWrapperNodeList()?.[index]?.querySelector('input');
      if (!input) return;
      input.focus();
      // To make sure the cursor is placed at the end:
      const val = input.value;
      input.value = null;
      input.value = val;
    },
    clearInput() {
      const newValue = Object.keys(this.value).reduce((acc, key) => {
        // eslint-disable-next-line no-param-reassign
        acc[key] = 0;
        return acc;
      }, {});
      this.$emit('input', newValue);
      this.$emit('change', newValue);
      this.$emit('clear', newValue);
    },
  },
};
</script>

<style lang="scss" scoped>
.sam-time-amount-input {
  @extend .px-4, .py-3, .d-flex, .items-center;
  border: $size--0--fourth solid $neutral--60;
  border-radius: $size--1;
  position: relative;
  box-sizing: border-box;
  width: 100%;
  cursor: pointer;

  &:hover,
  &:focus-within {
    border-color: $informative--main;
  }

  &:focus-within {
    outline: 3px solid $informative--border;
  }

  &:not(.is-empty) {
    &:not(:focus-within) .sam-time-amount-input__inputWrapper.is-empty {
      display: none;
    }
  }

  input.sam-time-amount-input__input {
    @extend .text--body-1, .m-0;
    border: none;
    outline: none;
    background-color: transparent;
    white-space: nowrap;
    text-overflow: ellipsis;
    color: $neutral--90;
    box-sizing: border-box;
    display: inline-block;
    position: relative;
    field-sizing: content;
    -moz-appearance: textfield;

    &::-webkit-input-placeholder,
    &::-moz-placeholder,
    &:-ms-input-placeholder,
    &::-webkit-input-placeholder,
    &::placeholder {
      color: $neutral--60;
    }

    &::-webkit-outer-spin-button,
    &::-webkit-inner-spin-button {
      -webkit-appearance: none;
      margin: 0;
    }
  }

  .sam-time-amount-input__inputWrapper {
    @extend .d-flex, .text--body-1, .items-baseline;
    position: relative;

    * {
      cursor: pointer;
    }

    &.is-empty,
    &.is-empty .sam-time-amount-input__input {
      color: $neutral--60;
    }

    &.is-filled,
    &.is-filled .sam-time-amount-input__input {
      color: $neutral--90;
    }
  }

  &__icon {
    &.clear-icon {
      cursor: pointer;

      &:not(:last-child) {
        @extend .pr-2;
      }
    }
  }

  &.is-danger {
    border-color: $error--main;

    &:hover {
      border-color: $error--hover;
    }

    &:focus-within {
      outline-color: $error--border;
    }

    .sam-time-amount-input__icon.status-icon {
      color: $error--main;
    }
  }

  &.is-success {
    border-color: $success--main;

    &:hover {
      border-color: $success--hover;
    }

    &:focus-within {
      outline-color: $success--border;
    }

    .sam-time-amount-input__icon.status-icon {
      color: $success--main;
    }
  }

  &.is-disabled {
    pointer-events: none;
    background-color: $neutral--20;
    border-color: $neutral--40;

    .sam-time-amount-input__input,
    .sam-time-amount-input__inputLabel {
      color: $neutral--60 !important;
    }

    &.is-empty {
      .sam-time-amount-input__input,
      .sam-time-amount-input__inputLabel {
        color: $neutral--40 !important;
      }

      .sam-time-amount-input__input {
        &::-webkit-input-placeholder,
        &::-moz-placeholder,
        &:-ms-input-placeholder,
        &::-webkit-input-placeholder,
        &::placeholder {
          color: $neutral--40 !important;
        }
      }
    }

    .sam-time-amount-input__icon.status-icon {
      color: $neutral--60 !important;
    }
  }
}
</style>
