import { h, ref, computed, watch, getCurrentInstance } from 'vue'

import { useFormAttrs } from '../../composables/use-form/private.use-form.js'

import useSlider, {
  useSliderProps,
  useSliderEmits,
  keyCodes
} from './use-slider.js'

import { createComponent } from '../../utils/private.create/create.js'
import { between } from '../../utils/format/format.js'
import { stopAndPrevent } from '../../utils/event/event.js'

const getNodeData = () => ({})

export default createComponent({
  name: 'QSlider',

  props: {
    ...useSliderProps,

    modelValue: {
      required: true,
      default: null,
      validator: v => typeof v === 'number' || v === null
    },

    labelValue: [String, Number]
  },

  emits: useSliderEmits,

  setup(props, { emit }) {
    const {
      proxy: { $q }
    } = getCurrentInstance()

    const { state, methods } = useSlider({
      updateValue,
      updatePosition,
      getDragging,
      formAttrs: useFormAttrs(props)
    })

    const rootRef = ref(null)
    const curRatio = ref(0)
    const model = ref(0)

    function normalizeModel() {
      model.value =
        props.modelValue === null
          ? state.innerMin.value
          : between(
              props.modelValue,
              state.innerMin.value,
              state.innerMax.value
            )
    }

    watch(
      () =>
        `${props.modelValue}|${state.innerMin.value}|${state.innerMax.value}`,
      normalizeModel
    )

    normalizeModel()

    const modelRatio = computed(() => methods.convertModelToRatio(model.value))
    const ratio = computed(() =>
      state.active.value === true ? curRatio.value : modelRatio.value
    )

    const selectionBarStyle = computed(() => {
      const acc = {
        [state.positionProp.value]: `${100 * state.innerMinRatio.value}%`,
        [state.sizeProp.value]:
          `${100 * (ratio.value - state.innerMinRatio.value)}%`
      }
      if (props.selectionImg !== void 0) {
        acc.backgroundImage = `url(${props.selectionImg}) !important`
      }
      return acc
    })

    const getThumb = methods.getThumbRenderFn({
      focusValue: true,
      getNodeData,
      ratio,
      label: computed(() =>
        props.labelValue !== void 0 ? props.labelValue : model.value
      ),
      thumbColor: computed(() => props.thumbColor || props.color),
      labelColor: computed(() => props.labelColor),
      labelTextColor: computed(() => props.labelTextColor)
    })

    const trackContainerEvents = computed(() => {
      if (state.editable.value !== true) {
        return {}
      }

      return $q.platform.is.mobile === true
        ? { onClick: methods.onMobileClick }
        : {
            onMousedown: methods.onActivate,
            onFocus,
            onBlur: methods.onBlur,
            onKeydown,
            onKeyup: methods.onKeyup
          }
    })

    function updateValue(change) {
      if (model.value !== props.modelValue) {
        emit('update:modelValue', model.value)
      }

      if (change === true) emit('change', model.value)
    }

    function getDragging() {
      return rootRef.value.getBoundingClientRect()
    }

    function updatePosition(event, dragging = state.dragging.value) {
      const localRatio = methods.getDraggingRatio(event, dragging)

      model.value = methods.convertRatioToModel(localRatio)

      curRatio.value =
        props.snap !== true || props.step === 0
          ? localRatio
          : methods.convertModelToRatio(model.value)
    }

    function onFocus() {
      state.focus.value = true
    }

    function onKeydown(evt) {
      if (keyCodes.includes(evt.keyCode) === false) return

      stopAndPrevent(evt)

      const stepVal =
          ([34, 33].includes(evt.keyCode) ? 10 : 1) * state.keyStep.value,
        offset =
          ([34, 37, 40].includes(evt.keyCode) ? -1 : 1) *
          (state.isReversed.value === true ? -1 : 1) *
          (props.vertical === true ? -1 : 1) *
          stepVal

      model.value = between(
        state.roundValueFn.value(model.value + offset),
        state.innerMin.value,
        state.innerMax.value
      )

      updateValue()
    }

    return () => {
      const content = methods.getContent(
        selectionBarStyle,
        state.tabindex,
        trackContainerEvents,
        node => {
          node.push(getThumb())
        }
      )

      return h(
        'div',
        {
          ref: rootRef,
          class:
            state.classes.value +
            (props.modelValue === null ? ' q-slider--no-value' : ''),
          ...state.attributes.value,
          'aria-valuenow': props.modelValue
        },
        content
      )
    }
  }
})
