<template>
  <div
    class="form-group last:tw-mb-0"
    :class="[
      formGroupClass,
      {
        'tw-block': !inline,
        'tw-flex tw-gap-2 tw-items-center': inline,
      },
    ]"
  >
    <div
      class="form-group-description tw-flex tw-gap-x-2 tw-gap-y-1 empty:tw-hidden"
      :class="[
        center ? 'tw-items-center tw-self-center' : 'tw-items-baseline',
        { 'tw-mb-2': !inline },
      ]"
    >
      <label
        v-if="label"
        :for="id"
        class="tw-text-gray-500 tw-font-medium tw-flex tw-gap-2 tw-items-center tw-grow tw-m-0"
      >
        <span>{{ label }}</span>
        <button v-if="withEditButton" type="button" class="btn btn-sm tw-p-0" @click="emit('edit')">
          <icon-system-uicons-pen />
        </button>
      </label>

      <SaveIndicator
        v-if="typeof isSaving === 'boolean'"
        class="tw-transition-opacity tw-ease-in-out tw-duration-150"
        :class="{ 'tw-invisible tw-opacity-0': !isSavingDebounced }"
      />
    </div>
    <div class="tw-flex tw-flex-col">
      <div class="tw-grid tw-order-first">
        <slot v-bind="controlAttributes" />
        <slot name="iconLeft" :class="[iconClasses, 'tw-ml-2']" role="presentation" />
        <slot name="iconRight" :class="[iconClasses, 'tw-mr-2']" role="presentation" />
      </div>
      <div
        v-if="hasErrors"
        :id="errorsId"
        class="invalid-feedback"
        :class="{ 'tw-block': hasErrors }"
      >
        <template v-for="(error, index) in errorList" :key="index">
          <p class="last:tw-mb-0">
            {{
              error.code
                ? t(`error.${error.code}`)
                : error.message
                ? error.message
                : t('error.unknown')
            }}
          </p>
        </template>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { computed, inject, useSlots, watchEffect } from 'vue'
import { computedDebounced, useId } from '@/util'
import { useI18n } from '@/i18n'
import SaveIndicator from '@/components/generic/SaveIndicator.vue'

defineOptions({ compatConfig: { MODE: 3 } })

type Error = {
  code?: string
  message?: string
}

const props = withDefaults(
  defineProps<{
    label?: string
    customControl?: boolean
    errors?: undefined | (Error | undefined | null)[]
    withEditButton?: boolean
    isSaving?: boolean | undefined
    center?: boolean
    inline?: boolean
  }>(),
  {
    isSaving: undefined,
    label: '',
    errors: undefined,
    center: false,
  },
)
const emit = defineEmits<{
  edit: []
}>()

const { t } = useI18n()
const slots = useSlots()
const formGroupClass = inject('formGroupClass', undefined)
const id = useId('form-group-control')
const errorsId = useId('form-group-errors')
const errorList = computed<Error[]>(() => (props.errors ?? []).filter((e): e is Error => !!e))
const hasErrors = computed(() => errorList.value.length > 0)
const iconClasses = 'tw-pointer-events-none tw-grid-area-cover tw-self-center'
const controlAttributes = computed(() => ({
  class: [
    'tw-grid-area-cover',

    {
      'is-invalid': hasErrors.value,
      'form-control': !props.customControl,
      'tw-pl-8': slots.iconLeft,
      'tw-pr-8': slots.iconRight,
    },
  ],
  'aria-describedby': hasErrors.value ? errorsId.value : '',
  id: id.value,
}))

let isSavingStart = new Date()
const isSavingDebounced = computedDebounced(
  () => props.isSaving,
  (isSaving) => {
    if (isSaving) return 0
    const now = new Date()
    const timePassed = (now.getTime() - isSavingStart.getTime()) / 1000
    return timePassed < 1 ? 1 - timePassed : 0
  },
)
watchEffect(() => {
  if (props.isSaving) isSavingStart = new Date()
})
</script>