<template>
  <div
    :class="[
      cn.b(),
      cn.is('loading', loading),
      cn.is('checked', checked),
      cn.is('focused', focused),
      cn.is('disabled', disabled),
    ]"
  >
    <div
      :class="cn.e('control')"
      @click="switchValue"
    >
      <input
        type="checkbox"
        :class="cn.e('input')"
        :disabled="disabled"
        :checked="checked"
        :value="trueValue"
        @focus="focused = true"
        @blur="focused = false"
        @change="handleChange"
      >
      <div :class="cn.e('action')">
        <UiSpinner
          v-if="loading && !disabled"
          :class="cn.e('spinner')"
        />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, ref, toRef } from 'vue'
import { isPromise } from '@vue/shared'
import { useField } from 'vee-validate'
import useClassname from '../../composables/use-classname'

import UiSpinner from '../spinner/index.vue'

export interface Props {
  id: string
  disabled?: boolean
  modelValue?: boolean | string | number
  trueValue?: boolean | string | number
  falseValue?: boolean | string | number
  dummy?: boolean
  loading?: boolean
  beforeChange?: () => Promise<boolean> | boolean
}

defineOptions({
  name: 'UiSwitch',
})

const props = withDefaults(defineProps<Props>(), {
  disabled: false,
  trueValue: true,
  falseValue: false,
  dummy: false,
  modelValue: undefined,
  loading: false,
})

const emit = defineEmits<{
  'update:modelValue': [state: boolean | string | number]
  change: [state: boolean | string | number]
  focus: []
  blur: []
}>()

const cn = useClassname('ui-switch')

const { checked, handleChange: fieldHandleChange } = useField(
  toRef(props, 'id'),
  null,
  {
    type: 'checkbox',
    initialValue: props.modelValue ?? props.falseValue,
    checkedValue: props.trueValue,
    uncheckedValue: props.falseValue,
    syncVModel: true,
  },
)

const focused = ref(false)

const handleChange = computed<(e: Event) => void>(() => {
  return props.dummy ? handleChangeDummy : fieldHandleChange
})

function handleChangeDummy() {
  emit('change', checked.value ? props.falseValue : props.trueValue)
}

async function switchValue(e: Event) {
  if (props.disabled || props.loading)
    return

  const { beforeChange } = props

  if (!beforeChange) {
    handleChange.value(e)
    return
  }

  const shouldChange = beforeChange()

  if (isPromise(shouldChange)) {
    try {
      const result = await shouldChange

      result && handleChange.value(e)
    }
    catch {}
  }
  else if (shouldChange) {
    handleChange.value(e)
  }
}
</script>

<style lang="scss">
@use 'styles';
</style>
