<template>
  <div :class="mergedGroupClass">
    <div :class="mergedControlClass">
      <label :for="inputId" class="radio-indicator relative flex cursor-pointer items-center p-3">
        <input
          :id="inputId"
          :name="name"
          :value="value"
          :checked="isChecked"
          :required="required"
          :disabled="disabled"
          :aria-invalid="!!error"
          :aria-describedby="describedBy"
          :class="mergedInputClass"
          v-bind="attrs"
          type="radio"
          @click="onClick"
          @change="onChange"
        >
        <span class="radio-dot pointer-events-none absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-black opacity-0">
          <svg viewBox="0 0 16 16" fill="currentColor" class="h-[10px]">
            <circle cx="8" cy="8" r="8" />
          </svg>
        </span>
      </label>

      <label
        v-if="label"
        :for="inputId"
        :class="mergedLabelClass"
      >
        {{ label }}
      </label>
    </div>

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

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

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

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

const attrs = useAttrs()
const generatedId = useId()
const localValue = ref<string | number | boolean | null | undefined>(undefined)

const inputId = computed(() => props.id?.toString() || `malio-radio-${generatedId}`)
const isControlled = computed(() => props.modelValue !== undefined)
const isChecked = computed(() =>
  isControlled.value ? props.modelValue === props.value : localValue.value === props.value,
)
const hasError = computed(() => !!props.error)
const hasSuccess = computed(() => !!props.success && !hasError.value)
const disabled = computed(() => props.disabled)
const shouldShowMessage = computed(() => !!(props.hint || hasError.value || hasSuccess.value))

const describedBy = computed(() => {
  if (!shouldShowMessage.value) return undefined
  return `${inputId.value}-describedby`
})

const mergedGroupClass = computed(() =>
  twMerge(
    'radio-item mt-4 w-full',
    props.groupClass,
  ),
)

const mergedControlClass = computed(() =>
  twMerge(
    'radio-control flex items-center',
    hasError.value ? 'is-error' : '',
    hasSuccess.value ? 'is-success' : '',
    disabled.value ? 'is-disabled' : '',
  ),
)

const mergedInputClass = computed(() =>
  twMerge(
    'h-5 w-5 cursor-pointer appearance-none rounded-full border-2 border-m-muted checked:border-black',
    props.inputClass,
  ),
)

const mergedLabelClass = computed(() =>
  twMerge(
    'radio-text mt-px cursor-pointer',
    isChecked.value ? 'text-black' : 'text-m-muted',
    hasError.value ? 'text-m-danger' : '',
    hasSuccess.value ? 'text-m-success' : '',
    disabled.value ? 'cursor-not-allowed text-black/60' : '',
    props.labelClass,
  ),
)

const mergedMessageClass = computed(() =>
  twMerge(
    'radio-message ml-3 -mt-1 text-xs',
    hasError.value
      ? 'text-m-danger'
      : hasSuccess.value
        ? 'text-m-success'
        : 'text-m-muted',
  ),
)

const emit = defineEmits<{
  (event: 'update:modelValue', value: string | number | boolean | null | undefined): void
}>()

const onClick = (event: MouseEvent) => {
  if (!props.readonly) return

  event.preventDefault()
}

const onChange = (event: Event) => {
  if (props.readonly) {
    const target = event.target as HTMLInputElement
    target.checked = isChecked.value
    return
  }

  if (!isControlled.value) {
    localValue.value = props.value
  }

  emit('update:modelValue', props.value)
}
</script>

<style scoped>
.radio-control input[type='radio']:checked + .radio-dot {
  opacity: 1;
}

.radio-control.is-error input[type='radio'] {
  border-color: rgb(var(--m-danger) / 1);
}

.radio-control.is-error .radio-dot {
  color: rgb(var(--m-danger) / 1);
}

.radio-control.is-success input[type='radio'] {
  border-color: rgb(var(--m-success) / 1);
}

.radio-control.is-success .radio-dot {
  color: rgb(var(--m-success) / 1);
}

.radio-control.is-disabled .radio-indicator,
.radio-control.is-disabled .radio-text {
  cursor: not-allowed;
  opacity: 0.6;
}

.radio-item:has(+ .radio-item) .radio-message {
  display: none;
}
</style>
