<template lang="pug">
component(
  ref="formEl"
  :is="formRegister && !wRadios ? 'w-form-element' : 'div'"
  v-bind="formRegister && { validators, inputValue, disabled: isDisabled, readonly: isReadonly, noBlurValidation }"
  v-model:valid="valid"
  @reset="$emit('update:modelValue', inputValue = null);$emit('input', null)"
  :class="classes")
  input(
    ref="input"
    :id="inputId"
    type="radio"
    :name="inputName"
    :checked="inputValue || null"
    :disabled="isDisabled || isReadonly || null"
    :required="required || null"
    :tabindex="tabindex || null"
    @focus="$emit('focus', $event)"
    @change="onInput($event) /* Edge doesn't emit an `input` event on checkbox/radio/select change */"
    :aria-checked="inputValue || 'false'"
    role="radio")
  template(v-if="hasLabel && labelOnLeft")
    label.w-radio__label.w-form-el-shakable.pr2(
      v-if="$slots.default"
      :for="inputId"
      :class="labelClasses")
      slot {{ label }}
    label.w-radio__label.w-form-el-shakable.pr2(
      v-else-if="label"
      :for="inputId"
      :class="labelClasses"
      v-html="label")
  .w-radio__input(
    @click="$refs.input.focus();$refs.input.click()"
    :class="this.color")
  template(v-if="hasLabel && !labelOnLeft")
    label.w-radio__label.w-form-el-shakable.pl2(
      v-if="$slots.default"
      :for="inputId"
      :class="labelClasses")
      slot {{ label }}
    label.w-radio__label.w-form-el-shakable.pl2(
      v-else-if="label"
      :for="inputId"
      :class="labelClasses"
      v-html="label")
</template>

<script>
import FormElementMixin, { useWaveUiFormIds } from '../mixins/form-elements'
import FocusableMixin from '../mixins/focusable'

export default {
  name: 'w-radio',
  expose: ['focus'],
  mixins: [FormElementMixin, FocusableMixin],

  setup () {
    return useWaveUiFormIds()
  },

  inject: { wRadios: { default: null } },

  props: {
    modelValue: { default: false }, // v-model to check or uncheck.
    // When `value` is taken by a v-model and multiple w-radio are plugged on
    // the same v-model, this allows returning a custom value to the v-model.
    returnValue: {},
    label: { type: String },
    labelOnLeft: { type: Boolean },
    color: { type: String, default: 'primary' },
    labelColor: { type: String, default: 'primary' },
    noRipple: { type: Boolean },
    // Sizes.
    xs: { type: Boolean },
    sm: { type: Boolean },
    md: { type: Boolean },
    lg: { type: Boolean },
    xl: { type: Boolean },
    dark: { type: Boolean },
    light: { type: Boolean }
    // Props from mixin: id, name, disabled, readonly, required, tabindex, validators.
    // Computed from mixin: inputId, inputName, isDisabled & isReadonly.
  },

  emits: ['input', 'update:modelValue', 'focus'],

  data: () => ({
    inputValue: false,
    ripple: {
      start: false,
      end: false,
      timeout: null
    }
  }),

  computed: {
    hasLabel () {
      return this.label || this.$slots.default
    },
    presetSize () {
      return (this.xs && 'xs') || (this.sm && 'sm') || (this.md && 'md') || (this.lg && 'lg') || (this.xl && 'xl') || null
    },

    classes () {
      return {
        [`w-radio w-radio--${this.inputValue ? 'checked' : 'unchecked'}`]: true,
        'w-radio--disabled': this.isDisabled,
        'w-radio--readonly': this.isReadonly,
        'w-radio--ripple': this.ripple.start,
        'w-radio--rippled': this.ripple.end,
        'w-radio--dark': this.ripple.dark,
        'w-radio--light': this.ripple.light,
        [`size--${this.presetSize}`]: !!this.presetSize
      }
    }
  },

  methods: {
    toggleFromOutside () {
      this.inputValue = this.returnValue !== undefined ? (this.returnValue === this.modelValue) : this.modelValue
    },

    onInput (e) {
      this.inputValue = e.target.checked // The source of truth is the radio button.
      const returnValue = this.inputValue && this.returnValue !== undefined ? this.returnValue : this.inputValue
      this.$emit('update:modelValue', returnValue)
      this.$emit('input', returnValue)

      if (!this.noRipple) {
        if (this.inputValue) {
          this.ripple.start = true
          this.ripple.timeout = setTimeout(() => {
            this.ripple.start = false
            this.ripple.end = true
            setTimeout(() => (this.ripple.end = false), 100)
          }, 700)
        }
        else {
          this.ripple.start = false
          clearTimeout(this.ripple.timeout)
        }
      }
    }
  },

  created () {
    if (this.modelValue !== undefined) this.toggleFromOutside()
  },

  watch: {
    modelValue () {
      this.toggleFromOutside()
    }
  }
}
</script>

<style lang="scss">
$outline-width: 2px;
$inactive-color: #666;

.w-radio {
  // --_size resolves --w-size if set (e.g. via size class or inline style), otherwise the global default.
  --_size: var(--w-size, var(--w-small-form-el-size));
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  // Contain the hidden radio button, so browser doesn't pan to it when outside of the screen.
  position: relative;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;

  @include themeable;

  &--disabled {
    cursor: not-allowed;
    -webkit-tap-highlight-color: transparent;
  }

  // The hidden real radio button.
  input[type="radio"] {
    position: absolute;
    opacity: 0;
    z-index: -100;
    outline: none;
  }

  // The fake radio button to substitute.
  &__input {
    position: relative;
    border-radius: 100%;
    width: var(--_size);
    aspect-ratio: 1;
    display: flex;
    flex: 0 0 auto; // Prevent stretching width or height.
    align-items: center;
    justify-content: center;
    border: $outline-width solid $inactive-color;
    transition: 0.3s ease-in-out;
    cursor: inherit;

    .w-radio--disabled & {border-color: var(--w-disabled-color);}

    // Checked state.
    :checked ~ & {border-color: currentColor;}
    .w-radio--disabled :checked ~ & {border-color: var(--w-disabled-color);}
  }

  // The inner bullet - visible when checked.
  &__input:after {
    content: '';
    position: absolute;
    border-radius: 100%;
    border: 0 solid $inactive-color;
    // Prevents a tiny hole while animating and in some browser zoom levels.
    background-color: $inactive-color;
    transition: var(--w-transition-duration);

    :checked ~ & {
      border-width: 4px;
      border-color: currentColor;
      // Prevents a tiny hole while animating and in some browser zoom levels.
      background-color: currentColor;
    }
    .w-radio--disabled & {
      border-color: var(--w-disabled-color);
      // Prevents a tiny hole while animating and in some browser zoom levels.
      background-color: var(--w-disabled-color);
    }
  }

  // The focus outline & ripple on check action.
  &__input:before {
    content: "";
    position: absolute;
    width: inherit;
    aspect-ratio: 1;
    background-color: currentColor;
    border-radius: 100%;
    transform: scale(0);
    opacity: 0;
    pointer-events: none;
    transition: 0.25s ease-in-out;
  }

  &--ripple &__input:before {
    background-color: transparent;
    animation: w-radio-ripple 0.55s 0.15s ease;
  }

  :focus ~ &__input:before,
  :active ~ &__input:before {
    transform: scale(1.8);
    opacity: 0.2;
  }

  // After ripple reset to default state, then remove the class via js and the
  // `:focus + &__input:before` will re-transition to normal focused outline.
  &--rippled &__input:before {
    transition: none;
    transform: scale(0);
    opacity: 0;
  }

  &__label {
    display: flex;
    align-items: center;
    cursor: inherit;
    user-select: none;

    .w-radio--disabled & {opacity: 0.7;}
  }

  // Sizes.
  // ------------------------------------------------------
  &.size--xs {--w-size: round(nearest, calc(0.86 * var(--w-base-font-size)), 2px);}
  &.size--xs &__input {border-width: 1px;}
  &.size--xs :checked ~ &__input:after {border-width: 3px;}
  &.size--sm {--w-size: round(nearest, calc(1.14 * var(--w-base-font-size)), 2px);}
  &.size--sm :checked ~ &__input:after {border-width: 3px;}
  &.size--lg {--w-size: round(nearest, calc(1.43 * var(--w-base-font-size)), 2px);}
  &.size--lg :checked ~ &__input:after {border-width: 5px;}
  &.size--xl {--w-size: round(nearest, calc(1.71 * var(--w-base-font-size)), 2px);}
  &.size--xl :checked ~ &__input:after {border-width: 6px;}
}

@keyframes w-radio-ripple {
  0% {opacity: 1;transform: scale(1);background-color: currentColor;} // Start with visible ripple.
  100% {opacity: 0;transform: scale(2.8);} // Propagate ripple to max radius and fade out.
}
</style>
