<template>
  <div :class="mergedGroupClass">
    <input
      :id="inputId"
      :name="name"
      :checked="isChecked"
      :required="required"
      :disabled="disabled"
      :aria-invalid="!!error"
      :aria-describedby="describedBy"
      :class="mergedInputClass"
      v-bind="attrs"
      type="checkbox"
      @change="onChange"
    >

    <label
      v-if="label"
      :for="inputId"
      :class="mergedLabelClass"
    >
      <span>
        <svg width="12" height="10" viewBox="0 0 12 10" aria-hidden="true">
          <polyline points="1.5 6 4.5 9 10.5 1"/>
        </svg>
      </span>
      <span>
        {{ label }}
      </span>
    </label>

    <p
      v-if="hint || hasError || hasSuccess"
      :id="`${inputId}-describedby`"
      :class="mergedMessageClass"
    >
      {{ error || success || hint }}
    </p>
  </div>
</template>

<script setup lang="ts">
import {computed, useAttrs, useId} from 'vue'
import {twMerge} from 'tailwind-merge'

defineOptions({name: 'MalioCheckbox', inheritAttrs: false})

const props = withDefaults(
  defineProps<{
    id?: string
    label?: string
    name?: string
    modelValue?: boolean | null | undefined
    inputClass?: string
    labelClass?: string
    groupClass?: string
    required?: boolean
    disabled?: boolean
    readonly?: boolean
    hint?: string
    error?: string
    success?: string
  }>(),
  {
    id: '',
    label: '',
    name: '',
    modelValue: undefined,
    inputClass: '',
    labelClass: '',
    groupClass: '',
    required: false,
    disabled: false,
    readonly: false,
    hint: '',
    error: '',
    success: '',
  },
)

const attrs = useAttrs()
const generatedId = useId()

const inputId = computed(() => props.id?.toString() || `malio-checkbox-${generatedId}`)
const isChecked = computed(() => !!props.modelValue)
const hasError = computed(() => !!props.error)
const hasSuccess = computed(() => !!props.success && !hasError.value)
const disabled = computed(() => props.disabled)

const describedBy = computed(() => {
  if (!props.hint && !hasError.value && !hasSuccess.value) return undefined
  return `${inputId.value}-describedby`
})

const mergedGroupClass = computed(() =>
  twMerge(
    'checkbox-wrapper-4 w-full',
    props.groupClass,
  ),
)

const mergedInputClass = computed(() =>
  twMerge(
    'inp-cbx peer ',
    props.inputClass,
  ),
)

const mergedLabelClass = computed(() =>
  twMerge(
    'cbx text-black text-lg',
    disabled.value ? 'cursor-not-allowed text-black/60' : '',
    hasError.value ? 'text-m-error' : '',
    hasSuccess.value ? 'text-m-success' : '',
    props.labelClass,
  ),
)

const mergedMessageClass = computed(() =>
  twMerge(
    'text-xs',
    hasError.value
      ? 'text-m-error'
      : hasSuccess.value
        ? 'text-m-success'
        : 'text-m-muted',
  ),
)

const emit = defineEmits<{
  (event: 'update:modelValue', value: boolean): void
}>()

const onChange = (event: Event) => {
  const target = event.target as HTMLInputElement

  if (props.readonly) {
    target.checked = isChecked.value
    return
  }

  emit('update:modelValue', target.checked)
}
</script>

<style scoped>
.cbx {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}

.cbx span {
  display: inline-flex;
  align-items: center;
}

.cbx span:first-child {
  position: relative;
  width: 18px;
  height: 18px;
  flex: 0 0 18px;
  transform: scale(1);
  border: 2px solid rgb(0, 0, 0);
  transition: all 0.1s ease;
}

.cbx span:first-child svg {
  position: absolute;
  top: 2px;
  left: 1px;
  fill: none;
  stroke: #000000;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 16px;
  stroke-dashoffset: 16px;
  transition: all 0.125s ease;
}

.cbx span:last-child {
  padding-left: 12px;
  line-height: 18px;
}

.inp-cbx {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.inp-cbx:checked + .cbx span:first-child svg {
  stroke-dashoffset: 0;
}

.inp-cbx + .cbx.text-m-error span:first-child {
  border-color: rgb(var(--m-error) / 1);
}
.cbx.text-m-error span:first-child svg {
  stroke: rgb(var(--m-error) / 1);
}
.inp-cbx:checked + .cbx.text-m-error span:first-child {
  border-color: rgb(var(--m-error) / 1);
}

.inp-cbx + .cbx.text-m-success span:first-child {
  border-color: rgb(var(--m-success) / 1);
}
.cbx.text-m-success span:first-child svg {
  stroke: rgb(var(--m-success) / 1);
}
.inp-cbx:checked + .cbx.text-m-success span:first-child {
  border-color: rgb(var(--m-success) / 1);
}

.inp-cbx:disabled + .cbx {
  cursor: not-allowed;
  opacity: 0.6;
}
</style>
