<script setup lang="ts">
import { computed, ref, watch, type HTMLAttributes } from 'vue'
import { useAttrsWithDefaults } from '../../../composables/useAttrsWithDefaults'
import { useFdsI18n } from '../../../plugin/useFdsI18n'
import FdsIcon from '../../FdsIcon/FdsIcon.vue'
import type { FdsTextareaEmits, FdsTextareaProps } from './types'

const { t } = useFdsI18n()

defineOptions({
  inheritAttrs: false,
})

// Support both v-model (modelValue) and :value prop for backward compatibility
const modelValue = defineModel<string>()

const props = withDefaults(defineProps<FdsTextareaProps>(), {
  value: undefined,
  label: undefined,
  meta: undefined,
  optional: false,
  valid: undefined,
  modelModifiers: () => ({}),
  dataTestid: undefined,
  inputClass: undefined,
  maxlength: undefined,
})

const emit = defineEmits<FdsTextareaEmits>()

const { id, rows, attrs, disabled } = useAttrsWithDefaults(props)
const externalClass = computed(() => attrs.class as HTMLAttributes['class'] | undefined)

const autoId = `fds-textarea-${Math.random().toString(36).slice(2, 9)}`
const textareaId = computed(() => (id.value && id.value.trim() !== '' ? id.value : autoId))

const labelAttrs = computed(() => {
  const eventHandlers: Record<string, unknown> = {}
  for (const key in attrs) {
    if (key.startsWith('on') && typeof attrs[key] === 'function') {
      eventHandlers[key] = attrs[key]
    }
  }
  return eventHandlers
})

const textareaAttrs = computed(() => {
  const { class: _, id: _id, rows: _rows, maxlength: _maxlength, ...rest } = attrs
  const filtered: Record<string, unknown> = {}
  for (const key in rest) {
    if (!key.startsWith('on') || typeof rest[key] !== 'function') {
      filtered[key] = rest[key]
    }
  }
  return {
    ...filtered,
    id: textareaId.value,
    rows: rows.value ?? 4,
    'aria-invalid': props.valid === false || isOverMaxLength.value ? true : undefined,
    'data-testid': props.dataTestid ?? undefined,
  } as any
})

const isLazy = computed(() => props.modelModifiers?.lazy === true)

const internalValue = computed({
  get: () => (modelValue.value !== undefined ? modelValue.value : (props.value ?? '')),
  set: (newValue: string) => {
    if (modelValue.value !== undefined) {
      modelValue.value = newValue as string
    }
    emit('update:value', newValue)
  },
})

/** Tracks textarea content for character count, updated on every input. */
const liveValue = ref('')

watch(
  () => internalValue.value,
  (value) => {
    if (value !== liveValue.value) {
      liveValue.value = value
    }
  },
  { immediate: true },
)

const showInvalidMessage = computed(() => props.valid === false && !props.optional && props.invalidMessage)
const isInvalid = computed(() => props.valid === false && !props.optional && !disabled.value)
const isValid = computed(() => props.valid === true)

const characterCount = computed(() => liveValue.value.length)
const showCharacterCount = computed(() => props.maxlength != null)
const isOverMaxLength = computed(() => props.maxlength != null && characterCount.value > props.maxlength)

const maxLengthValid = computed((): boolean | null => {
  if (props.maxlength == null) {
    return null
  }
  return characterCount.value <= props.maxlength
})

watch(
  maxLengthValid,
  (value) => {
    emit('valid', value)
  },
  { immediate: true },
)

const inputClasses = computed(() => [
  'block w-full rounded-md border border-gray-500 px-3 py-2',
  'focus:outline-2 focus:outline-blue-500 focus:border-transparent',
  disabled.value
    ? 'text-gray-800 outline-dashed outline-2 -outline-offset-2 outline-gray-400 cursor-not-allowed border-transparent bg-gray-50'
    : 'bg-white',
  isInvalid.value && 'outline-2 -outline-offset-2 outline-red-600',
  isOverMaxLength.value && !disabled.value && 'outline-2 -outline-offset-2 outline-red-600',
  props.inputClass,
])

const validationIconClasses = computed(() => ['absolute right-3 top-3 flex items-center gap-2 pointer-events-none'])

function handleInputChange(ev: Event) {
  const target = ev.target as HTMLTextAreaElement
  liveValue.value = target.value

  if (isLazy.value && ev.type === 'input') {
    return
  }
  if (!isLazy.value && ev.type === 'change') {
    return
  }
  internalValue.value = target.value
  if (ev.type === 'input') {
    emit('input', ev)
  }
}
</script>

<template>
  <div class="w-full mb-6" :class="externalClass">
    <label
      v-if="label"
      :for="textareaId"
      class="block font-bold text-gray-900 cursor-pointer"
      :class="{ 'mb-0': meta, 'mb-1': !meta }"
      v-bind="labelAttrs"
    >
      {{ label }}
    </label>
    <div v-if="meta" class="font-thin mb-1">
      {{ meta }}
    </div>
    <div class="relative">
      <textarea
        :value="liveValue"
        :class="inputClasses"
        v-bind="textareaAttrs"
        @input="handleInputChange"
        @change="handleInputChange"
        @blur="$emit('blur', $event)"
      />
      <div :class="validationIconClasses">
        <FdsIcon v-if="isInvalid" name="alert" class="fill-red-600" />
        <FdsIcon v-if="isValid" name="bigSuccess" />
      </div>
    </div>
    <div v-if="showInvalidMessage" class="text-red-700 text-right font-bold mt-1">
      {{ invalidMessage }}
    </div>
    <p
      v-else-if="showCharacterCount"
      class="mt-1 text-right font-bold"
      :class="isOverMaxLength ? 'text-red-700' : 'text-gray-900'"
    >
      {{ characterCount }} / {{ maxlength }} {{ t('FdsTextarea.characters') }}
    </p>
  </div>
</template>
