<template>
  <label class="cpCheckbox" :class="dynamicCheckboxClasses" :for="checkboxUniqueId">
    <div class="cpCheckbox__wrapper">
      <input
        :id="checkboxUniqueId"
        ref="inputRef"
        class="cpCheckbox__input"
        :class="dynamicInputClasses"
        :autofocus="autofocus"
        :checked="isChecked"
        :disabled="isDisabled"
        :name="groupName"
        type="checkbox"
        :value="checkboxValue"
        @change="onChange"
      />
      <cp-icon :type="icon" />
    </div>
    <div class="cpCheckbox__content">
      <slot>
        <span v-if="checkboxLabel" class="cpCheckbox__label" :class="dynamicLabelClasses">
          {{ checkboxLabel }}
        </span>
      </slot>
      <span v-if="helper" class="cpCheckbox__helper">
        {{ helper }}
      </span>
    </div>
  </label>
</template>

<script setup lang="ts">
import { computed, nextTick, ref, useId, useSlots, watch } from 'vue'

import { ToggleColors } from '@/constants'
import { capitalizeFirstLetter } from '@/helpers'

type EmitType = {
  (e: 'update:modelValue', value: boolean | unknown[]): void
}

export interface Props {
  autofocus?: boolean
  checkboxLabel?: string
  checkboxValue?: string | number
  color?: ToggleColors
  groupName?: string
  helper?: string
  indeterminate?: boolean
  isDisabled?: boolean
  modelValue?: boolean | unknown[]
  reverseLabel?: boolean
  size?: 'md' | 'lg'
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: false,
  checkboxValue: '',
  checkboxLabel: '',
  groupName: '',
  color: 'accent',
  helper: '',
  size: 'md',
})

const emit = defineEmits<EmitType>()

const checkboxUniqueId = useId()
const inputRef = ref<HTMLInputElement>()

// Synchronize native checkbox state with our props
// as it will not reactively update otherwise
watch(
  () => ({ checked: props.modelValue, indeterminate: props.indeterminate }),
  ({ checked, indeterminate }) => {
    nextTick(() => {
      if (inputRef.value) {
        inputRef.value.indeterminate = indeterminate
        if (!Array.isArray(checked)) {
          inputRef.value.checked = checked as boolean
        }
      }
    })
  },
  { immediate: true },
)

const isChecked = computed(() => {
  if (Array.isArray(props.modelValue)) {
    return props.modelValue.includes(props.checkboxValue)
  }
  return props.modelValue
})

const capitalizedColor = computed(() => capitalizeFirstLetter(props.color))
const icon = computed(() => (props.indeterminate ? 'minus' : 'check'))

const slots = useSlots()
const hasDefaultSlot = computed(() => !!slots.default)

const isEmpty = computed(() => !hasDefaultSlot.value && !props.checkboxLabel)

const dynamicCheckboxClasses = computed(() => {
  return [
    {
      'cpCheckbox--isEmpty': isEmpty.value,
      'cpCheckbox--isDisabled': props.isDisabled,
      'cpCheckbox--isReversed': props.reverseLabel,
      'cpCheckbox--isIndeterminate': props.indeterminate,
    },
    `cpCheckbox--is${capitalizedColor.value}`,
    `cpCheckbox--${props.size}`,
  ]
})

const dynamicLabelClasses = computed(() => [ `cpCheckbox__label--${props.size}` ])

const dynamicInputClasses = computed(() => [ `cpCheckbox__input--${props.size}` ])

const onChange = () => {
  if (Array.isArray(props.modelValue)) {
    const currentValues = [...props.modelValue]
    const valueIndex = currentValues.indexOf(props.checkboxValue)

    if (valueIndex > -1) {
      currentValues.splice(valueIndex, 1)
    } else {
      currentValues.push(props.checkboxValue)
    }

    emit('update:modelValue', currentValues)
  } else {
    // Always uncheck when previously indeterminate
    if (props.indeterminate) {
      emit('update:modelValue', false)
    } else {
      emit('update:modelValue', !props.modelValue)
    }
  }
}
</script>

<style lang="scss">
@mixin cp-checkbox-style($color, $bgHoverColor, $className) {
  &--is#{$className} input:checked,
  &--is#{$className} input:indeterminate {
    border-color: $color;
    background-color: $color;
  }

  &--is#{$className}:hover input:not(:disabled) {
    border-color: var(--cp-border-soft-hover);
    background-color: var(--cp-background-primary-hover);
  }

  &--is#{$className}:hover input:checked:not(:disabled),
  &--is#{$className}:hover input:indeterminate:not(:disabled) {
    border-color: $bgHoverColor;
    background-color: $bgHoverColor;
  }

  &--is#{$className} input:focus-visible {
    position: relative;
    z-index: 1;
    outline: var(--cp-dimensions-0_5) solid $color;
    outline-offset: var(--cp-dimensions-0_5);
  }
}

.cpCheckbox {
  position: relative;
  display: flex;
  align-items: flex-start;
  cursor: pointer;

  @include cp-checkbox-style(var(--cp-background-accent-solid), var(--cp-background-accent-solid-hover), 'Accent');
  @include cp-checkbox-style(var(--cp-background-blue-solid), var(--cp-background-blue-solid-hover), 'Blue');
  @include cp-checkbox-style(
    var(--cp-background-accent-solid),
    var(--cp-background-accent-solid-hover),
    'Purple'
  ); // TODO: Should be replace by ACCENT

  &--md:not(.cpCheckbox--isEmpty) {
    gap: var(--cp-spacing-md);
  }

  &--lg:not(.cpCheckbox--isEmpty) {
    gap: var(--cp-spacing-lg);
  }

  &__wrapper {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    padding: var(--cp-spacing-sm);
  }

  &__input {
    flex-shrink: 0;
    border: var(--cp-dimensions-0_25) solid var(--cp-border-soft);
    border-radius: var(--cp-radius-sm);
    appearance: none;
    background-color: var(--cp-background-primary);
    box-shadow: var(--cp-shadows-3xs);
    cursor: pointer;
    transition:
      background-color 0.1s ease-out,
      transform 0.1s linear;

    & + i {
      position: absolute;
      z-index: 2;
      width: var(--cp-dimensions-3);
      height: var(--cp-dimensions-3);
      color: var(--cp-foreground-white);
      opacity: 0;
      transition: opacity 0.2s linear 0.1s;
    }

    & + i svg {
      stroke-width: 3;
    }

    &:checked + i,
    &:indeterminate + i {
      opacity: 1;
      visibility: visible;
    }

    &:disabled {
      background-color: var(--cp-background-disabled);
      box-shadow: none;
      cursor: not-allowed;
    }

    &:checked:disabled,
    &:indeterminate:disabled {
      border-color: var(--cp-border-disabled);
      background-color: var(--cp-background-disabled);

      & + i {
        color: var(--cp-foreground-disabled);
      }
    }

    &:active:not(:disabled) {
      transform: scale(0.8);
    }

    &--md {
      width: var(--cp-dimensions-4);
      height: var(--cp-dimensions-4);
      border-radius: var(--cp-radius-sm);
    }

    &--lg {
      width: var(--cp-dimensions-5);
      height: var(--cp-dimensions-5);
      border-radius: var(--cp-radius-sm-md);
    }
  }

  &__label,
  &__helper {
    display: block;
  }

  &__helper {
    line-height: var(--cp-line-height-md);
  }


  &__label {
    font-weight: 500;
    padding-block: var(--cp-spacing-xs);

    &--md {
      font-size: var(--cp-text-size-sm);
      line-height: var(--cp-line-height-sm);
    }

    &--lg {
      font-size: var(--cp-text-size-md);
      line-height: var(--cp-line-height-md);
    }

    &::first-letter {
      text-transform: capitalize;
    }
  }

  &__helper {
    color: var(--cp-text-secondary);
  }

  &--isDisabled {
    cursor: not-allowed;
  }

  &--isDisabled &__label,
  &--isDisabled &__helper {
    color: var(--cp-text-disabled);
  }

  &--isReversed {
    flex-direction: row-reverse;
  }

  &__content {
    flex: 1;
  }
}
</style>
