import { extend } from '../../vue';
import { NAME_FORM_RATING_STAR, NAME_FORM_RATING } from '../../constants/components';
import { EVENT_NAME_CHANGE, EVENT_NAME_SELECTED } from '../../constants/events';
import { PROP_TYPE_NUMBER_STRING, PROP_TYPE_BOOLEAN, PROP_TYPE_NUMBER, PROP_TYPE_STRING, PROP_TYPE_ARRAY_STRING } from '../../constants/props';
import { CODE_LEFT, CODE_DOWN, CODE_RIGHT, CODE_UP } from '../../constants/key-codes';
import { SLOT_NAME_ICON_CLEAR, SLOT_NAME_ICON_EMPTY, SLOT_NAME_ICON_HALF, SLOT_NAME_ICON_FULL } from '../../constants/slots';
import { concat, arrayIncludes } from '../../utils/array';
import { attemptFocus, attemptBlur } from '../../utils/dom';
import { stopEvent } from '../../utils/events';
import { identity } from '../../utils/identity';
import { isNull } from '../../utils/inspect';
import { isLocaleRTL } from '../../utils/locale';
import { mathMax, mathMin } from '../../utils/math';
import { makeModelMixin } from '../../utils/model';
import { toInteger, toFloat } from '../../utils/number';
import { sortKeys, omit } from '../../utils/object';
import { makeProp, makePropsConfigurable } from '../../utils/props';
import { toString } from '../../utils/string';
import { props as props$3, formSizeMixin } from '../../mixins/form-size';
import { props as props$1, idMixin } from '../../mixins/id';
import { normalizeSlotMixin } from '../../mixins/normalize-slot';
import { props as props$2 } from '../../mixins/form-control';
import { BIcon } from '../../icons/icon';
import { BIconStar, BIconStarHalf, BIconStarFill, BIconX } from '../../icons/icons';

// --- Constants ---

const {
  mixin: modelMixin,
  props: modelProps,
  prop: MODEL_PROP_NAME,
  event: MODEL_EVENT_NAME
} = makeModelMixin('value', {
  type: PROP_TYPE_NUMBER_STRING,
  event: EVENT_NAME_CHANGE
});
const MIN_STARS = 3;
const DEFAULT_STARS = 5;

// --- Helper methods ---

const computeStars = stars => mathMax(MIN_STARS, toInteger(stars, DEFAULT_STARS));
const clampValue = (value, min, max) => mathMax(mathMin(value, max), min);

// --- Helper components ---

// @vue/component
const BVFormRatingStar = extend({
  name: NAME_FORM_RATING_STAR,
  mixins: [normalizeSlotMixin],
  props: {
    disabled: makeProp(PROP_TYPE_BOOLEAN, false),
    // If parent is focused
    focused: makeProp(PROP_TYPE_BOOLEAN, false),
    hasClear: makeProp(PROP_TYPE_BOOLEAN, false),
    rating: makeProp(PROP_TYPE_NUMBER, 0),
    readonly: makeProp(PROP_TYPE_BOOLEAN, false),
    star: makeProp(PROP_TYPE_NUMBER, 0),
    variant: makeProp(PROP_TYPE_STRING)
  },
  methods: {
    onClick(event) {
      if (!this.disabled && !this.readonly) {
        stopEvent(event, {
          propagation: false
        });
        this.$emit(EVENT_NAME_SELECTED, this.star);
      }
    }
  },
  render(h) {
    const {
      rating,
      star,
      focused,
      hasClear,
      variant,
      disabled,
      readonly
    } = this;
    const minStar = hasClear ? 0 : 1;
    const type = rating >= star ? 'full' : rating >= star - 0.5 ? 'half' : 'empty';
    const slotScope = {
      variant,
      disabled,
      readonly
    };
    return h('span', {
      staticClass: 'b-rating-star',
      class: {
        // When not hovered, we use this class to focus the current (or first) star
        focused: focused && rating === star || !toInteger(rating) && star === minStar,
        // We add type classes to we can handle RTL styling
        'b-rating-star-empty': type === 'empty',
        'b-rating-star-half': type === 'half',
        'b-rating-star-full': type === 'full'
      },
      attrs: {
        tabindex: !disabled && !readonly ? '-1' : null
      },
      on: {
        click: this.onClick
      }
    }, [h('span', {
      staticClass: 'b-rating-icon'
    }, [this.normalizeSlot(type, slotScope)])]);
  }
});

// --- Props ---

const props = makePropsConfigurable(sortKeys({
  ...props$1,
  ...modelProps,
  ...omit(props$2, ['required', 'autofocus']),
  ...props$3,
  // CSS color string (overrides variant)
  color: makeProp(PROP_TYPE_STRING),
  iconClear: makeProp(PROP_TYPE_STRING, 'x'),
  iconEmpty: makeProp(PROP_TYPE_STRING, 'star'),
  iconFull: makeProp(PROP_TYPE_STRING, 'star-fill'),
  iconHalf: makeProp(PROP_TYPE_STRING, 'star-half'),
  inline: makeProp(PROP_TYPE_BOOLEAN, false),
  // Locale for the formatted value (if shown)
  // Defaults to the browser locale. Falls back to `en`
  locale: makeProp(PROP_TYPE_ARRAY_STRING),
  noBorder: makeProp(PROP_TYPE_BOOLEAN, false),
  precision: makeProp(PROP_TYPE_NUMBER_STRING),
  readonly: makeProp(PROP_TYPE_BOOLEAN, false),
  showClear: makeProp(PROP_TYPE_BOOLEAN, false),
  showValue: makeProp(PROP_TYPE_BOOLEAN, false),
  showValueMax: makeProp(PROP_TYPE_BOOLEAN, false),
  stars: makeProp(PROP_TYPE_NUMBER_STRING, DEFAULT_STARS, value => {
    return toInteger(value) >= MIN_STARS;
  }),
  variant: makeProp(PROP_TYPE_STRING)
}), NAME_FORM_RATING);

// --- Main component ---

// @vue/component
const BFormRating = /*#__PURE__*/extend({
  name: NAME_FORM_RATING,
  components: {
    BIconStar,
    BIconStarHalf,
    BIconStarFill,
    BIconX
  },
  mixins: [idMixin, modelMixin, formSizeMixin],
  props,
  data() {
    const value = toFloat(this[MODEL_PROP_NAME], null);
    const stars = computeStars(this.stars);
    return {
      localValue: isNull(value) ? null : clampValue(value, 0, stars),
      hasFocus: false
    };
  },
  computed: {
    computedStars() {
      return computeStars(this.stars);
    },
    computedRating() {
      const value = toFloat(this.localValue, 0);
      const precision = toInteger(this.precision, 3);
      // We clamp the value between `0` and stars
      return clampValue(toFloat(value.toFixed(precision)), 0, this.computedStars);
    },
    computedLocale() {
      const locales = concat(this.locale).filter(identity);
      const nf = new Intl.NumberFormat(locales);
      return nf.resolvedOptions().locale;
    },
    isInteractive() {
      return !this.disabled && !this.readonly;
    },
    isRTL() {
      return isLocaleRTL(this.computedLocale);
    },
    formattedRating() {
      const precision = toInteger(this.precision);
      const showValueMax = this.showValueMax;
      const locale = this.computedLocale;
      const formatOptions = {
        notation: 'standard',
        minimumFractionDigits: isNaN(precision) ? 0 : precision,
        maximumFractionDigits: isNaN(precision) ? 3 : precision
      };
      const stars = this.computedStars.toLocaleString(locale);
      let value = this.localValue;
      value = isNull(value) ? showValueMax ? '-' : '' : value.toLocaleString(locale, formatOptions);
      return showValueMax ? `${value}/${stars}` : value;
    }
  },
  watch: {
    [MODEL_PROP_NAME](newValue, oldValue) {
      if (newValue !== oldValue) {
        const value = toFloat(newValue, null);
        this.localValue = isNull(value) ? null : clampValue(value, 0, this.computedStars);
      }
    },
    localValue(newValue, oldValue) {
      if (newValue !== oldValue && newValue !== (this.value || 0)) {
        this.$emit(MODEL_EVENT_NAME, newValue || null);
      }
    },
    disabled(newValue) {
      if (newValue) {
        this.hasFocus = false;
        this.blur();
      }
    }
  },
  methods: {
    // --- Public methods ---
    focus() {
      if (!this.disabled) {
        attemptFocus(this.$el);
      }
    },
    blur() {
      if (!this.disabled) {
        attemptBlur(this.$el);
      }
    },
    // --- Private methods ---
    onKeydown(event) {
      const {
        keyCode
      } = event;
      if (this.isInteractive && arrayIncludes([CODE_LEFT, CODE_DOWN, CODE_RIGHT, CODE_UP], keyCode)) {
        stopEvent(event, {
          propagation: false
        });
        const value = toInteger(this.localValue, 0);
        const min = this.showClear ? 0 : 1;
        const stars = this.computedStars;
        // In RTL mode, LEFT/RIGHT are swapped
        const amountRtl = this.isRTL ? -1 : 1;
        if (keyCode === CODE_LEFT) {
          this.localValue = clampValue(value - amountRtl, min, stars) || null;
        } else if (keyCode === CODE_RIGHT) {
          this.localValue = clampValue(value + amountRtl, min, stars);
        } else if (keyCode === CODE_DOWN) {
          this.localValue = clampValue(value - 1, min, stars) || null;
        } else if (keyCode === CODE_UP) {
          this.localValue = clampValue(value + 1, min, stars);
        }
      }
    },
    onSelected(value) {
      if (this.isInteractive) {
        this.localValue = value;
      }
    },
    onFocus(event) {
      this.hasFocus = !this.isInteractive ? false : event.type === 'focus';
    },
    // --- Render methods ---
    renderIcon(icon) {
      return this.$createElement(BIcon, {
        props: {
          icon,
          variant: this.disabled || this.color ? null : this.variant || null
        }
      });
    },
    iconEmptyFn() {
      return this.renderIcon(this.iconEmpty);
    },
    iconHalfFn() {
      return this.renderIcon(this.iconHalf);
    },
    iconFullFn() {
      return this.renderIcon(this.iconFull);
    },
    iconClearFn() {
      return this.$createElement(BIcon, {
        props: {
          icon: this.iconClear
        }
      });
    }
  },
  render(h) {
    const {
      disabled,
      readonly,
      name,
      form,
      inline,
      variant,
      color,
      noBorder,
      hasFocus,
      computedRating,
      computedStars,
      formattedRating,
      showClear,
      isRTL,
      isInteractive,
      $scopedSlots
    } = this;
    const $content = [];
    if (showClear && !disabled && !readonly) {
      const $icon = h('span', {
        staticClass: 'b-rating-icon'
      }, [($scopedSlots[SLOT_NAME_ICON_CLEAR] || this.iconClearFn)()]);
      $content.push(h('span', {
        staticClass: 'b-rating-star b-rating-star-clear flex-grow-1',
        class: {
          focused: hasFocus && computedRating === 0
        },
        attrs: {
          tabindex: isInteractive ? '-1' : null
        },
        on: {
          click: () => this.onSelected(null)
        },
        key: 'clear'
      }, [$icon]));
    }
    for (let index = 0; index < computedStars; index++) {
      const value = index + 1;
      $content.push(h(BVFormRatingStar, {
        staticClass: 'flex-grow-1',
        style: color && !disabled ? {
          color
        } : {},
        props: {
          rating: computedRating,
          star: value,
          variant: disabled ? null : variant || null,
          disabled,
          readonly,
          focused: hasFocus,
          hasClear: showClear
        },
        on: {
          selected: this.onSelected
        },
        scopedSlots: {
          empty: $scopedSlots[SLOT_NAME_ICON_EMPTY] || this.iconEmptyFn,
          half: $scopedSlots[SLOT_NAME_ICON_HALF] || this.iconHalfFn,
          full: $scopedSlots[SLOT_NAME_ICON_FULL] || this.iconFullFn
        },
        key: index
      }));
    }
    if (name) {
      $content.push(h('input', {
        attrs: {
          type: 'hidden',
          value: isNull(this.localValue) ? '' : computedRating,
          name,
          form: form || null
        },
        key: 'hidden'
      }));
    }
    if (this.showValue) {
      $content.push(h('b', {
        staticClass: 'b-rating-value flex-grow-1',
        attrs: {
          'aria-hidden': 'true'
        },
        key: 'value'
      }, toString(formattedRating)));
    }
    return h('output', {
      staticClass: 'b-rating form-control align-items-center',
      class: [{
        'd-inline-flex': inline,
        'd-flex': !inline,
        'border-0': noBorder,
        disabled,
        readonly: !disabled && readonly
      }, this.sizeFormClass],
      attrs: {
        id: this.safeId(),
        dir: isRTL ? 'rtl' : 'ltr',
        tabindex: disabled ? null : '0',
        disabled,
        role: 'slider',
        'aria-disabled': disabled ? 'true' : null,
        'aria-readonly': !disabled && readonly ? 'true' : null,
        'aria-live': 'off',
        'aria-valuemin': showClear ? '0' : '1',
        'aria-valuemax': toString(computedStars),
        'aria-valuenow': computedRating ? toString(computedRating) : null
      },
      on: {
        keydown: this.onKeydown,
        focus: this.onFocus,
        blur: this.onFocus
      }
    }, $content);
  }
});

export { BFormRating, props };
