<script setup lang="ts">
import { computed, ref, useAttrs, watch } from 'vue'
import { useFdsI18n } from '../../../plugin/useFdsI18n'
import FdsInput from '../FdsInput/FdsInput.vue'
import { buildCountryOptions, countryCodeToFlag, getCountryOption, sortCountryOptionsByName } from './countries'
import { getPhoneMaskForCountry } from './phoneMask'
import { normalizeNationalPhoneInput, shouldNormalizePhoneInput } from './normalizePhoneInput'
import FdsPhonenumberCountryPicker from './FdsPhonenumberCountryPicker.vue'
import type { FdsPhonenumberEmits, FdsPhonenumberProps } from './types'
import { getPhoneValidationState, validatePhoneNumber } from './validatePhone'

defineOptions({
  inheritAttrs: false,
})

const [nationalNumber] = defineModel<string>({ default: '' })
const country = defineModel<string>('country', { default: 'SE' })
const attrs = useAttrs()

const props = withDefaults(defineProps<FdsPhonenumberProps>(), {
  label: undefined,
  meta: undefined,
  optional: false,
  valid: undefined,
  invalidMessage: undefined,
  id: undefined,
  autocomplete: 'tel',
  required: false,
  placeholder: undefined,
  name: undefined,
  autofocus: false,
  readonly: false,
  defaultCountry: 'SE',
  countries: undefined,
  locale: undefined,
  fixedCountry: undefined,
  hideCountryPicker: false,
  disabled: false,
  dataTestid: undefined,
  selectClass: undefined,
  inputClass: undefined,
})

const emit = defineEmits<FdsPhonenumberEmits>()

const { t, locale: fdsLocale } = useFdsI18n()

const resolvedLocale = computed(() => props.locale ?? fdsLocale.value)
const inputAttrs = computed(() => {
  const { class: _class, style, ...rest } = attrs
  return {
    ...rest,
    ...(style == null ? {} : { style: style as string | Record<string, unknown> }),
  }
})
const forwardedInputProps = computed(() => ({
  id: props.id,
  autocomplete: props.autocomplete,
  required: props.required,
  placeholder: props.placeholder,
  name: props.name,
  autofocus: props.autofocus,
  readonly: props.readonly,
  inputmode: 'tel',
  ...inputAttrs.value,
}))

/** Static dial code — no interactive picker / country list. */
const isCountryLocked = computed(() => props.hideCountryPicker || !!props.fixedCountry)

const lockedCountryCode = computed(
  () => (props.fixedCountry || country.value || props.defaultCountry || 'SE').toUpperCase(),
)

const lockedCountryOption = computed(() => getCountryOption(lockedCountryCode.value, resolvedLocale.value))

const countryItems = computed(() => {
  if (isCountryLocked.value) {
    return []
  }
  if (props.countries?.length) {
    return sortCountryOptionsByName(
      props.countries.map((option) => ({ ...option })),
      resolvedLocale.value,
      props.defaultCountry,
    )
  }
  // Shared singleton catalog per locale — do not clone per instance.
  return buildCountryOptions(resolvedLocale.value, props.defaultCountry)
})

const phoneMask = computed(() => getPhoneMaskForCountry(country.value ?? props.defaultCountry))

const phoneMaskOptions = computed(() => ({
  prepare: (value: string) => applyPhoneNormalization(value, country.value ?? props.defaultCountry),
}))

/** Set after the phone number field has blurred; validation does not run on input. */
const committedValid = ref<boolean | null>(null)

const resolvedLabel = computed(() => (props.label === undefined ? t('FdsPhonenumber.phoneNumber') : props.label))

const resolvedInvalidMessage = computed(() =>
  props.invalidMessage === undefined ? t('FdsPhonenumber.invalidPhone') : props.invalidMessage,
)

const displayValid = computed((): boolean | null | undefined => {
  if (props.valid === false) return false
  if (props.valid === true) return true
  if (committedValid.value === false) return false
  if (committedValid.value === true) return true
  return props.valid
})

const showInvalidMessage = computed(
  () => displayValid.value === false && !props.optional && !!resolvedInvalidMessage.value && !props.disabled,
)

const isInvalid = computed(() => displayValid.value === false && !props.disabled)

const noCountryResults = ref(false)

const lockedDialClasses = computed(() => [
  'relative flex h-12 w-32 shrink-0 items-center rounded-md border border-gray-500 py-2 pl-10 pr-3 text-base leading-6 tabular-nums',
  props.disabled
    ? 'cursor-not-allowed border-transparent bg-gray-50 text-gray-800 outline-dashed outline-2 outline-gray-400'
    : 'bg-white text-gray-900',
  isInvalid.value && 'outline-2 -outline-offset-2 outline-red-600',
  props.selectClass,
])

function runValidation() {
  const validationState = getPhoneValidationState(nationalNumber.value ?? '', country.value ?? 'SE')
  committedValid.value = validationState
  emit('valid', validationState)

  const result = validatePhoneNumber(nationalNumber.value ?? '', country.value ?? 'SE')
  emit('update:e164', result.isValid && result.phoneNumber ? result.phoneNumber : '')
}

function applyPhoneNormalization(value: string, countryCode = country.value ?? props.defaultCountry): string {
  if (!shouldNormalizePhoneInput(value, countryCode)) {
    return value
  }
  return normalizeNationalPhoneInput(value, countryCode)
}

watch(
  () => props.fixedCountry,
  (fixed) => {
    if (!fixed) {
      return
    }
    const normalized = fixed.toUpperCase()
    if (country.value !== normalized) {
      country.value = normalized
    }
  },
  { immediate: true },
)

watch(nationalNumber, (value) => {
  const normalized = applyPhoneNormalization(value ?? '')
  if (normalized !== (value ?? '')) {
    nationalNumber.value = normalized
  }
})

watch(country, () => {
  if (committedValid.value === null) {
    return
  }
  runValidation()
})

function handleBlur(ev: FocusEvent) {
  const normalized = applyPhoneNormalization(nationalNumber.value ?? '')
  if (normalized !== (nationalNumber.value ?? '')) {
    nationalNumber.value = normalized
  }
  runValidation()
  emit('blur', ev)
}

function onNoCountryResults(value: boolean) {
  noCountryResults.value = value
  emit('noCountryResults', value)
}
</script>

<template>
  <div class="w-full mb-6">
    <label
      v-if="resolvedLabel"
      class="block font-bold text-gray-900 cursor-pointer"
      :class="{ 'mb-0': meta, 'mb-1': !meta }"
    >
      {{ resolvedLabel }}
    </label>
    <div v-if="meta" class="font-thin mb-1">
      {{ meta }}
    </div>
    <div class="flex flex-wrap items-start gap-x-2">
      <div
        v-if="isCountryLocked"
        :class="lockedDialClasses"
        :aria-label="t('FdsPhonenumber.countryCode')"
      >
        <span class="absolute left-3 top-1/2 -translate-y-1/2 text-xl leading-none" aria-hidden="true">
          {{ countryCodeToFlag(lockedCountryOption?.value ?? lockedCountryCode) }}
        </span>
        {{ t('common.plus') }}{{ lockedCountryOption?.countryCode ?? '' }}
      </div>
      <FdsPhonenumberCountryPicker
        v-else
        v-model="country"
        :items="countryItems"
        :valid="displayValid"
        :disabled="disabled"
        :ariaLabel="t('FdsPhonenumber.countryCode')"
        :data-testid="dataTestid ? `${dataTestid}-country` : undefined"
        :class="['mb-0! shrink-0', selectClass ?? '']"
        @no-country-results="onNoCountryResults"
      />
      <FdsInput
        :model-value="nationalNumber"
        @update:model-value="nationalNumber = $event ?? ''"
        :mask="phoneMask"
        :mask-options="phoneMaskOptions"
        :valid="displayValid"
        :disabled="disabled"
        :optional="optional"
        :ariaLabel="t('FdsPhonenumber.phoneNumber')"
        :data-testid="dataTestid ? `${dataTestid}-number` : undefined"
        :class="['mb-0! min-w-0 flex-1', inputClass ?? '']"
        v-bind="forwardedInputProps"
        @blur="handleBlur"
      />
    </div>
    <div v-if="noCountryResults" class="text-red-700 font-bold mt-1">
      {{ t('FdsPhonenumber.noCountryResults') }}
    </div>
    <div v-if="showInvalidMessage" class="text-red-700 font-bold mt-1">
      {{ resolvedInvalidMessage }}
    </div>
  </div>
</template>
