<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, type HTMLAttributes } from 'vue'
// @ts-ignore - IMask types may not be resolved in build
import IMask from 'imask'
import { useAttrsWithDefaults } from '../../../composables/useAttrsWithDefaults'
import { useFdsI18n } from '../../../plugin/useFdsI18n'
import FdsButtonIcon from '../../Buttons/FdsButtonIcon/FdsButtonIcon.vue'
import FdsButtonMinor from '../../Buttons/FdsButtonMinor/FdsButtonMinor.vue'
import FdsIcon from '../../FdsIcon/FdsIcon.vue'
import FdsMeta from '../../Typography/FdsMeta/FdsMeta.vue'
import type { FdsInputProps } from './types'

defineOptions({
  inheritAttrs: false,
})

const modelValue = defineModel<string>()

const props = withDefaults(defineProps<FdsInputProps>(), {
  value: undefined,
  label: undefined,
  valid: undefined,
  disabled: false,
  optional: false,
  required: false,
  labelLeft: false,
  meta: undefined,
  clearButton: false,
  mask: undefined,
  maskOptions: undefined,
  class: undefined,
  pattern: undefined,
  modelModifiers: () => ({}),
  ariaLabel: undefined,
  dataTestid: undefined,
  inputClass: undefined,
})

const emit = defineEmits<{
  (e: 'input', ev: Event): void
  (e: 'autocomplete', ev: Event): void
  (e: 'clearInput'): void
  (e: 'update:value', value: string): void
  (e: 'keyup', ev: KeyboardEvent): void
  (e: 'keyup.enter', ev: KeyboardEvent): void
  (e: 'blur', ev: FocusEvent): void
}>()

const { id, type, maxlength, attrs } = useAttrsWithDefaults(props)
const { t } = useFdsI18n()

// Get type from attrs first (user-provided), then props, with 'text' as fallback.
// IMask only supports type="text".
const inputType = computed(() => {
  if (props.mask) return 'text'
  return type.value ?? props.type ?? 'text'
})

const autoId = `fds-input-${Math.random().toString(36).slice(2, 9)}`
const inputId = computed(() => id.value ?? autoId)
const labelId = computed(() => `${inputId.value}-label`)
const metaId = computed(() => `${inputId.value}-meta`)

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 inputAttrs = computed(() => {
  const { class: _, type: _type, id: _id, disabled: _disabled, tabindex: _tabindex, ...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: inputId.value,
    'data-testid': props.dataTestid ?? undefined,
  } as any
})

const clearButtonLabel = computed(() => t('FdsInput.clearInput'))
const passwordButtonShowLabel = computed(() => t('FdsInput.showPassword'))
const passwordButtonHideLabel = computed(() => t('FdsInput.hidePassword'))
const dateButtonLabel = computed(() => t('FdsInput.openDatePicker'))
const todayLabel = computed(() => t('FdsInput.today'))

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

const showPasswordToggle = computed(
  () =>
    inputType.value === 'password' &&
    internalValue.value !== undefined &&
    internalValue.value !== null &&
    String(internalValue.value).length > 0,
)

const showClearButton = computed(() => props.clearButton && !!internalValue.value && !props.disabled)

const showValidationIcon = computed(() => isInvalid.value || isValid.value)
const showDateIcon = computed(() => inputType.value === 'date')
const todayIsoDate = computed(() => new Date().toISOString().slice(0, 10))
const showTodaySuffixInField = computed(
  () => inputType.value === 'date' && String(internalValue.value ?? '') === todayIsoDate.value,
)
const todaySuffixClasses = computed(() => [
  'pointer-events-none absolute top-1/2 -translate-y-1/2 text-base leading-6',
  props.disabled ? 'text-gray-800' : 'text-gray-900',
])
const todaySuffixStyle = computed(() => ({
  left: 'calc(0.75rem + 10ch + 0.25rem)',
}))

const rightIconCount = computed(() => {
  let count = 0
  if (showDateIcon.value) count++
  if (showValidationIcon.value) count++
  if (showClearButton.value) count++
  if (props.searchIcon) count++
  // Password toggle har text och är bredare, räkna som 2 ikoner
  if (showPasswordToggle.value) count += 3
  return count
})

const inputPaddingRight = computed(() => {
  if (rightIconCount.value === 0) return ''
  if (rightIconCount.value === 1) return 'pr-12'
  if (rightIconCount.value === 2) return 'pr-20'
  if (rightIconCount.value === 3) return 'pr-24'
  if (rightIconCount.value === 4) return 'pr-32'
  return 'pr-40' // 5+ ikoner
})

const externalClass = computed(() => (props.class ?? attrs.class) as HTMLAttributes['class'] | undefined)

const inputPaddingClasses = 'px-3 py-[calc(0.75rem-1px)]'

const inputClasses = computed(() => [
  'block rounded-md border border-gray-500',
  maxlength.value ? '' : 'w-full',
  inputPaddingClasses,
  inputPaddingRight.value,
  'focus:outline-2 focus:outline-blue-500 -outline-offset-2 focus:border-transparent',
  showDateIcon.value &&
    '[&::-webkit-calendar-picker-indicator]:opacity-0 [&::-webkit-calendar-picker-indicator]:pointer-events-none',
  props.disabled
    ? 'text-gray-800 outline-dashed outline-2 outline-gray-400 cursor-not-allowed border-transparent'
    : 'text-gray-900 bg-white',
  isInvalid.value && 'outline-2 outline-red-600',
  props.inputClass,
])

const inputStyle = computed(() => {
  if (!maxlength.value) return undefined
  return {
    width: `calc(${maxlength.value}ch + 1.5rem + 0.25rem)`,
    maxWidth: '100%',
  }
})

const rightIconsContainerClasses = computed(() => [
  'absolute flex gap-2 top-1/2 -translate-y-1/2 items-center justify-end',
  showClearButton.value ? 'right-2' : 'right-3',
])

const inputBindings = computed(() => ({
  ...inputAttrs.value,
  ...(props.mask ? {} : { value: internalValue.value }),
}))

// Use modelValue if bound via v-model, otherwise use value prop
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)
  },
})

function onClear() {
  internalValue.value = ''
  if (maskInstance) {
    maskInstance.value = ''
  }
  emit('clearInput')
}

function onDateIconClick() {
  if (!showDateIcon.value || props.disabled) return
  inputRef.value?.showPicker?.()
}

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

function toggleShowPassword() {
  showPassword.value = !showPassword.value
}

function handleInputChange(ev: Event) {
  const target = ev.target as HTMLInputElement
  // For lazy modifier, only update on change event
  // For normal v-model, update on input event
  if (isLazy.value && ev.type === 'input') {
    return
  }
  if (!isLazy.value && ev.type === 'change') {
    return
  }
  // If mask is active, let IMask handle the value update via 'accept' event
  // Only update internalValue directly if no mask is active
  if (!props.mask) {
    internalValue.value = target.value
  }
  if (ev.type === 'input') {
    emit('input', ev)
  }
}

// IMask setup
const inputRef = ref<HTMLInputElement | null>(null)

let maskInstance: any = null

const createMask = () => {
  if (maskInstance) {
    maskInstance.destroy()
    maskInstance = null
  }

  if (props.mask && inputRef.value) {
    maskInstance = IMask(inputRef.value, {
      mask: props.mask,
      ...props.maskOptions,
      lazy: props.maskOptions?.lazy ?? true,
    } as any)

    if (internalValue.value) {
      maskInstance.value = internalValue.value
    }

    maskInstance.on('accept', () => {
      if (maskInstance) {
        internalValue.value = maskInstance.value as string
      }
    })
  }
}

onMounted(() => {
  nextTick(() => {
    createMask()
  })
})

onBeforeUnmount(() => {
  if (maskInstance) {
    maskInstance.destroy()
    maskInstance = null
  }
})

const ariaLabelledBy = computed(() => {
  const describedBy = attrs['aria-labelledby']
  if (typeof describedBy === 'string' && describedBy.trim().length > 0) {
    return props.label ? `${describedBy} ${labelId.value}` : describedBy
  }
  return props.label ? labelId.value : undefined
})

const ariaDescribedBy = computed(() => {
  const describedBy = attrs['aria-describedby']
  if (typeof describedBy === 'string' && describedBy.trim().length > 0) {
    return props.meta ? `${describedBy} ${metaId.value}` : describedBy
  }
  return props.meta ? metaId.value : undefined
})

// Watch for mask-related prop changes and recreate mask
watch(
  () => [props.mask, props.maskOptions] as const,
  () => {
    nextTick(() => {
      createMask()
    })
  },
  { deep: true },
)

function shouldSyncMaskFromModel(newValue: string | undefined, currentMaskedValue: string): boolean {
  if (newValue === undefined) {
    return false
  }
  if (document.activeElement !== inputRef.value) {
    return true
  }
  const newDigits = String(newValue).replace(/\D/g, '')
  const currentDigits = String(currentMaskedValue ?? '').replace(/\D/g, '')
  return newDigits !== currentDigits
}

// Watch for external value changes and update mask
watch(
  () => props.value,
  (newValue) => {
    if (maskInstance && newValue !== undefined && newValue !== maskInstance.value) {
      if (shouldSyncMaskFromModel(newValue, maskInstance.value)) {
        maskInstance.value = newValue
      }
    }
  },
)

watch(
  () => modelValue.value,
  (newValue) => {
    if (maskInstance && newValue !== undefined && newValue !== maskInstance.value) {
      if (shouldSyncMaskFromModel(newValue, maskInstance.value)) {
        maskInstance.value = newValue
      }
    }
  },
)
</script>

<template>
  <div class="w-full mb-6" :class="externalClass">
    <div :class="{ 'flex flex-row gap-4': props.labelLeft }">
      <div :class="{ 'text-right': props.labelLeft }">
        <label
          v-if="props.label"
          :for="inputId"
          :id="labelId"
          class="block font-bold text-gray-900 cursor-pointer"
          :class="{ 'mb-0': props.meta, 'mb-1': !props.meta }"
          v-bind="labelAttrs"
        >
          {{ props.label }}
        </label>
        <FdsMeta v-if="props.meta" :id="metaId" :class="{ 'mb-1': !props.labelLeft }">
          {{ props.meta }}
        </FdsMeta>
      </div>
      <div :class="{ 'flex-1': props.labelLeft }">
        <div class="relative">
          <input
            ref="inputRef"
            :type="inputType === 'password' ? (showPassword ? 'text' : 'password') : inputType"
            :required="props.required"
            :disabled="props.disabled"
            :tabindex="props.disabled ? -1 : undefined"
            :aria-invalid="props.valid === false ? 'true' : undefined"
            :class="inputClasses"
            :style="inputStyle"
            :aria-label="props.ariaLabel"
            :aria-labelledby="ariaLabelledBy"
            :aria-describedby="ariaDescribedBy"
            :autocomplete="props.autocomplete"
            :placeholder="props.placeholder"
            :pattern="props.pattern"
            :searchIcon="props.searchIcon"
            v-bind="inputBindings"
            @input="handleInputChange"
            @change="handleInputChange"
            @autocomplete="$emit('autocomplete', $event)"
            @blur="$emit('blur', $event)"
            @keyup="
              (ev) => {
                $emit('keyup', ev)
                if (ev.key === 'Enter') $emit('keyup.enter', ev)
              }
            "
          />
          <div :class="rightIconsContainerClasses">
            <FdsButtonIcon
              v-if="showDateIcon"
              :aria-label="dateButtonLabel"
              @click="onDateIconClick"
              icon="calendar"
              :size="28"
              class="fill-blue-500"
            />
            <FdsIcon v-if="isInvalid" name="alert" class="fill-red-700" />
            <FdsIcon v-if="isValid" name="bigSuccess" />
            <FdsIcon v-if="props.searchIcon" name="search" :size="24" class="fill-blue-500" />
            <FdsButtonIcon
              v-if="showClearButton"
              icon="cross"
              v-bind="{ 'aria-label': clearButtonLabel }"
              @click="onClear"
              :size="30"
            />
            <FdsButtonMinor
              v-if="showPasswordToggle"
              :icon="showPassword ? 'viewOff' : 'viewOn'"
              :text="showPassword ? passwordButtonHideLabel : passwordButtonShowLabel"
              @click="toggleShowPassword"
            />
          </div>
          <span v-if="showTodaySuffixInField" :class="todaySuffixClasses" :style="todaySuffixStyle">
            {{ todayLabel }}
          </span>
        </div>
      </div>
    </div>
    <div v-if="showInvalidMessage" class="text-red-700 font-bold mt-1">
      {{ props.invalidMessage }}
    </div>
  </div>
</template>
