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

import useQuasar from '../../composables/use-quasar/use-quasar.js'
import useDark, {
  useDarkProps
} from '../../composables/private.use-dark/use-dark.js'
import { useSizeProps } from '../../composables/private.use-size/use-size.js'
import useRefocusTarget from '../../composables/private.use-refocus-target/use-refocus-target.js'
import {
  useFormInject,
  useFormProps
} from '../../composables/use-form/private.use-form.js'

import { getOptionSizeStyle } from '../../utils/private.option-sizes/option-sizes.js'
import { stopAndPrevent } from '../../utils/event/event.js'
import { hMergeSlot, hSlot } from '../../utils/private.render/render.js'

export const useCheckboxProps = {
  ...useDarkProps,
  ...useSizeProps,
  ...useFormProps,

  modelValue: {
    required: true,
    default: null
  },
  val: {},

  trueValue: { default: true },
  falseValue: { default: false },
  indeterminateValue: { default: null },

  checkedIcon: String,
  uncheckedIcon: String,
  indeterminateIcon: String,

  toggleOrder: {
    type: String,
    validator: v => v === 'tf' || v === 'ft'
  },
  toggleIndeterminate: Boolean,

  label: String,
  leftLabel: Boolean,

  color: String,
  keepColor: Boolean,
  dense: Boolean,

  disable: Boolean,
  tabindex: [String, Number]
}

export const useCheckboxEmits = ['update:modelValue']

function onKeydown(e) {
  if (e.keyCode === 13 || e.keyCode === 32) {
    stopAndPrevent(e)
  }
}

export default function useCheckbox(type, getInner) {
  const { props, slots, emit, proxy } = getCurrentInstance()
  const $q = useQuasar()

  const isDark = useDark(props, $q)

  const rootRef = ref(null)
  const { refocusTargetEl, refocusTarget } = useRefocusTarget(props, rootRef)

  const modelIsArray = computed(
    () => props.val !== void 0 && Array.isArray(props.modelValue)
  )

  const index = computed(() => {
    const val = toRaw(props.val)
    return modelIsArray.value
      ? props.modelValue.findIndex(opt => toRaw(opt) === val)
      : -1
  })

  const isTrue = computed(() =>
    modelIsArray.value
      ? index.value !== -1
      : toRaw(props.modelValue) === toRaw(props.trueValue)
  )

  const isFalse = computed(() =>
    modelIsArray.value
      ? index.value === -1
      : toRaw(props.modelValue) === toRaw(props.falseValue)
  )

  const isIndeterminate = computed(() => !isTrue.value && !isFalse.value)
  const tabindex = computed(() => (props.disable ? -1 : props.tabindex || 0))
  const classes = computed(
    () =>
      `q-${type} cursor-pointer no-outline row inline no-wrap items-center` +
      (props.disable ? ' disabled' : '') +
      (isDark() ? ` q-${type}--dark` : '') +
      (props.dense ? ` q-${type}--dense` : '') +
      (props.leftLabel ? ' reverse' : '')
  )

  const innerClass = computed(() => {
    const state = isTrue.value ? 'truthy' : isFalse.value ? 'falsy' : 'indet'

    const color =
      props.color !== void 0 &&
      (props.keepColor || (type === 'toggle' ? isTrue.value : !isFalse.value))
        ? ` text-${props.color}`
        : ''

    return `q-${type}__inner relative-position non-selectable q-${type}__inner--${state}${color}`
  })

  const formAttrs = () => {
    const prop = { type: 'checkbox' }

    if (props.name !== void 0) {
      Object.assign(prop, {
        // see https://vuejs.org/guide/extras/render-function.html#creating-vnodes (.prop)
        '.checked': isTrue.value,
        '^checked': isTrue.value ? 'checked' : void 0,
        name: props.name,
        value: modelIsArray.value ? props.val : props.trueValue
      })
    }

    return prop
  }

  const injectFormInput = useFormInject(formAttrs)

  const attributes = computed(() => {
    const attrs = {
      tabindex: tabindex.value,
      role: type === 'toggle' ? 'switch' : 'checkbox',
      'aria-label': props.label,
      // the switch role does not allow "mixed"
      // (AT would map it to "false" anyway)
      'aria-checked': isIndeterminate.value
        ? type === 'toggle'
          ? 'false'
          : 'mixed'
        : isTrue.value
          ? 'true'
          : 'false'
    }

    if (props.disable) {
      attrs['aria-disabled'] = 'true'
    }

    return attrs
  })

  function onClick(e) {
    if (e !== void 0) {
      stopAndPrevent(e)
      refocusTarget(e)
    }

    if (!props.disable) {
      emit('update:modelValue', getNextValue(), e)
    }
  }

  function getNextValue() {
    if (modelIsArray.value) {
      if (isTrue.value) {
        const val = [...props.modelValue]
        val.splice(index.value, 1)
        return val
      }

      return [...props.modelValue, props.val]
    }

    if (isTrue.value) {
      if (props.toggleOrder !== 'ft' || !props.toggleIndeterminate) {
        return props.falseValue
      }
    } else if (isFalse.value) {
      if (props.toggleOrder === 'ft' || !props.toggleIndeterminate) {
        return props.trueValue
      }
    } else {
      return props.toggleOrder !== 'ft' ? props.trueValue : props.falseValue
    }

    return props.indeterminateValue
  }

  function onKeyup(e) {
    if (e.keyCode === 13 || e.keyCode === 32) {
      onClick(e)
    }
  }

  const getInnerContent = getInner(isTrue, isIndeterminate)

  // expose public methods
  Object.assign(proxy, { toggle: onClick })

  return () => {
    const inner = getInnerContent()

    // the native input carries the value for a form submission (when a name
    // is supplied), but it is also what makes a wrapping <label> forward its
    // clicks to us, so it must be rendered even without a name
    if (!props.disable) {
      injectFormInput(
        inner,
        'unshift',
        ` q-${type}__native absolute q-ma-none q-pa-none`
      )
    }

    const child = [
      h(
        'div',
        {
          class: innerClass.value,
          style: getOptionSizeStyle(props.size),
          'aria-hidden': 'true'
        },
        inner
      )
    ]

    if (refocusTargetEl.value !== null) {
      child.push(refocusTargetEl.value)
    }

    const label =
      props.label !== void 0
        ? hMergeSlot(slots.default, [props.label])
        : hSlot(slots.default)

    if (label !== void 0) {
      child.push(
        h(
          'div',
          {
            class: `q-${type}__label q-anchor--skip`
          },
          label
        )
      )
    }

    return h(
      'div',
      {
        ref: rootRef,
        class: classes.value,
        ...attributes.value,
        onClick,
        onKeydown,
        onKeyup
      },
      child
    )
  }
}
