<template>
  <div class="flex flex-col space-y-1">
    <label
      :for="forId"
      :class="[
        'block text-sm font-medium transition-colors duration-200',
        errors.length ? 'text-red-500' : 'text-gray-700',
      ]"
      >{{ label }}</label
    >
    <slot
      name="input"
      :for-id="forId"
      :type="type"
      :id="forId"
      :fieldName="name"
      :value="value"
      :placeholder="placeholder"
      :invalid="!!errors?.length"
      :onInput="onInput"
      :onChange="onChange"
      :onFocus="onFocus"
      :onBlur="onBlur"
      :setValue="setValue"
    >
      <input
        :type="type"
        :id="forId"
        :name="name"
        :value="value"
        :class="inputClass"
        :placeholder="placeholder"
        :disabled="disabled"
        :readonly="readonly"
        @input="onInput"
        @change="onChange"
        @focus="onFocus"
        @blur="onBlur"
      />
    </slot>
    <slot />
    <span
      class="text-sm text-red-500"
      v-for="error of errors"
      aria-live="assertive"
      >{{ error }}</span
    >
  </div>
</template>

<script setup lang="ts">
import set from 'lodash.set';
import get from 'lodash.get';
import { computed, ref, Ref } from 'vue';
import { inject } from 'vue';
import { FormData, FormErrors, InputType } from './form.types';

const props = withDefaults(
  defineProps<{
    type?: InputType;
    forIdPrefix?: string;
    label: string;
    name: string;
    disabled?: boolean;
    readonly?: boolean;
    placeholder?: string;
  }>(),
  {
    type: 'text',
    disabled: false,
    readonly: false,
  }
);

const emit = defineEmits<{
  (e: 'change', value: unknown): void;
  (e: 'focus'): void;
  (e: 'blur'): void;
}>();

const formData = inject<Ref<FormData>>('formData');
const formErrors = inject<Ref<FormErrors>>('formErrors');
const formGroup = inject<Ref<string>>('formGroup', ref(''));
const fieldChange = inject<(field: string) => void>('fieldChange');

const fieldName = computed(() =>
  formGroup?.value ? `${formGroup.value}.${props.name}` : props.name
);

const forId = computed(
  () => `${props.forIdPrefix || 'form'}-${fieldName.value}`
);
const value = computed(() => get(formData?.value, fieldName.value));
const errors = computed(() => formErrors?.value[fieldName.value] || []);

const onInput = (ev: Event) => {
  if (!formData) return;
  const value = (ev.target as HTMLInputElement).value;
  const cleanValue = props.type === 'number' ? parseFloat(value) : value;
  setValue(cleanValue);
};

const onChange = (ev: Event) => {
  if (!formData) return;
  if (props.type === 'checkbox' || props.type === 'radio') {
    const cleanValue = (ev.target as HTMLInputElement).checked;
    setValue(cleanValue);
  }
};

const setValue = (value: unknown) => {
  if (!formData) return;
  set(formData.value, fieldName.value, value);
  emit('change', value);
  fieldChange?.(fieldName.value);
};

const onFocus = () => emit('focus');

const onBlur = () => {
  emit('blur');
  fieldChange?.(fieldName.value);
};

const inputClass = computed(() => {
  return [
    errors.value.length
      ? 'border-red-300 focus:border-red-500 focus:ring-red-500'
      : 'border-gray-300 focus:border-blue-500 focus:ring-blue-500',
    `mt-1 block w-full rounded-md shadow-sm sm:text-sm transition-colors duration-200`,
  ];
});
</script>
