<template>
    <!-- Bloc à plat (sans box-shadow) : un filet noir 1px le sépare du suivant
         (pas de bordure sous le dernier bloc), comme sur Starseed. -->
    <div class="pb-5" :class="{ 'border-b border-black': !last }">
        <div class="flex items-center justify-between">
            <!-- Titre = libellé saisi ; repli sur « Adresse N » tant qu'il est vide. -->
            <h3 class="text-[20px] font-semibold text-black">{{ blockTitle }}</h3>
            <MalioButtonIcon
                v-if="removable && !readonly"
                icon="mdi:delete-outline"
                variant="ghost"
                button-class="p-0"
                :aria-label="$t('common.delete')"
                @click="$emit('remove')"
            />
        </div>

        <div class="mt-6 grid grid-cols-4 gap-x-[44px] gap-y-4">
            <MalioInputText
                class="col-span-2"
                :label="$t('directory.addresses.fields.label')"
                :model-value="modelValue.label ?? ''"
                :readonly="readonly"
                @update:model-value="update('label', $event)"
            />

            <!-- On commence par le code postal : il alimente la liste des villes (BAN)
                 et réinitialise ville/rue devenues incohérentes en cas de changement. -->
            <MalioInputText
                :label="$t('directory.addresses.fields.postalCode')"
                :model-value="modelValue.postalCode ?? ''"
                :readonly="readonly"
                @update:model-value="onPostalCodeInput"
            />

            <!-- Ville : select alimenté par le code postal (BAN). En mode dégradé
                 (BAN indispo) ou lecture seule, on bascule en saisie libre. -->
            <MalioSelect
                v-if="!readonly && !degraded"
                :model-value="modelValue.city ?? ''"
                :options="cityOptions"
                :label="$t('directory.addresses.fields.city')"
                empty-option-label=""
                group-class="w-full"
                @update:model-value="onCityChange"
            />
            <MalioInputText
                v-else
                :label="$t('directory.addresses.fields.city')"
                :model-value="modelValue.city ?? ''"
                :readonly="readonly"
                @update:model-value="update('city', $event)"
            />

            <!-- Rue : conditionnée au code postal + ville (comme Starseed). Saisie
                 assistée (BAN) filtrée par le code postal ; désactivée tant que CP et
                 ville ne sont pas renseignés. Champ texte simple en lecture seule. -->
            <div class="col-span-2">
                <MalioInputAutocomplete
                    v-if="!readonly"
                    :model-value="modelValue.street ?? ''"
                    :options="addressOptions"
                    :loading="addressLoading"
                    :min-search-length="3"
                    :allow-create="true"
                    :disabled="!canEditStreet"
                    :hint="canEditStreet ? '' : $t('directory.addresses.streetHint')"
                    :label="$t('directory.addresses.fields.street')"
                    :no-results-text="$t('directory.addresses.streetNotFound')"
                    @update:model-value="(v) => update('street', v === null ? '' : String(v))"
                    @search="onAddressSearch"
                    @select="onAddressSelect"
                />
                <MalioInputText
                    v-else
                    :label="$t('directory.addresses.fields.street')"
                    :model-value="modelValue.street ?? ''"
                    :readonly="readonly"
                    @update:model-value="update('street', $event)"
                />
            </div>

            <MalioInputText
                class="col-span-2"
                :label="$t('directory.addresses.fields.streetComplement')"
                :model-value="modelValue.streetComplement ?? ''"
                :readonly="readonly"
                @update:model-value="update('streetComplement', $event)"
            />
        </div>
    </div>
</template>

<script setup lang="ts">
import type { Address } from '~/modules/directory/services/dto/address'
import {
    useAddressAutocomplete,
    type AddressSuggestion,
} from '~/modules/directory/composables/useAddressAutocomplete'

const props = defineProps<{
    modelValue: Address
    title: string
    removable?: boolean
    readonly?: boolean
    /** Dernier bloc de la liste : supprime le filet de séparation bas. */
    last?: boolean
}>()

const emit = defineEmits<{
    'update:modelValue': [value: Address]
    'remove': []
}>()

const { t } = useI18n()
const toast = useToast()
const autocomplete = useAddressAutocomplete()

type Option = { label: string, value: string | number }

const addressOptions = ref<Option[]>([])
// Villes renvoyées par la BAN pour le code postal courant.
const fetchedCityOptions = ref<Option[]>([])
const addressLoading = ref(false)

// Titre du bloc : le libellé saisi prime ; repli sur « Adresse N » (prop `title`).
const blockTitle = computed(() => (props.modelValue.label ?? '').trim() || props.title)

// La rue n'est éditable qu'une fois le code postal (5 chiffres) ET la ville
// renseignés — conditionnement métier repris de Starseed.
const canEditStreet = computed(() => {
    const digits = (props.modelValue.postalCode ?? '').replace(/\D/g, '')
    return digits.length === 5 && !!(props.modelValue.city ?? '').trim()
})

// Le select Ville n'affiche que les valeurs présentes dans ses options : on
// garantit donc que la ville déjà enregistrée (chargement d'une fiche) ou
// pré-remplie par l'autocomplétion d'adresse figure toujours dans la liste,
// même avant toute recherche par code postal — sinon elle s'afficherait vide.
const cityOptions = computed<Option[]>(() => {
    const current = (props.modelValue.city ?? '').trim()
    const options = [...fetchedCityOptions.value]
    if (current && !options.some(o => o.value === current)) {
        options.unshift({ value: current, label: current })
    }
    return options
})
// Mode dégradé : BAN indisponible → la ville passe en saisie libre.
const degraded = ref(false)
let lastAddressSuggestions: AddressSuggestion[] = []
let notified = false

function update(field: keyof Address, value: string): void {
    emit('update:modelValue', { ...props.modelValue, [field]: value === '' ? null : value })
}

// Avertit une seule fois que l'autocomplétion est indisponible (saisie libre).
function notifyUnavailable(): void {
    if (notified) return
    notified = true
    toast.info({ title: '', message: t('directory.addresses.autocompleteUnavailable') })
}

/**
 * Sélection d'une ville → vide rue + complément (devenus incohérents avec la
 * nouvelle ville). Ne réagit qu'à un vrai changement de valeur.
 */
function onCityChange(value: string | number | null): void {
    const next = value === null ? '' : String(value)
    if (next === (props.modelValue.city ?? '')) return
    addressOptions.value = []
    lastAddressSuggestions = []
    emit('update:modelValue', {
        ...props.modelValue,
        city: next === '' ? null : next,
        street: null,
        streetComplement: null,
    })
}

/** Recherche d'adresse assistée (event de MalioInputAutocomplete). */
async function onAddressSearch(query: string): Promise<void> {
    if (query.trim().length < 3) {
        addressOptions.value = []
        return
    }
    addressLoading.value = true
    try {
        const postalCode = (props.modelValue.postalCode ?? '').replace(/\D/g, '') || undefined
        const suggestions = await autocomplete.searchAddress(query, postalCode)
        lastAddressSuggestions = suggestions
        addressOptions.value = suggestions.map(s => ({ value: s.street, label: s.label }))
    }
    catch {
        addressOptions.value = []
        notifyUnavailable()
    }
    finally {
        addressLoading.value = false
    }
}

/** Sélection d'une suggestion → remplit rue + ville + code postal. */
function onAddressSelect(option: Option | null): void {
    if (option === null) return
    // Matching par `label` (adresse complète, unique côté BAN) plutôt que par
    // rue : deux communes peuvent partager le même libellé de voie.
    const suggestion = lastAddressSuggestions.find(s => s.label === option.label)
    if (!suggestion) {
        update('street', String(option.value))
        return
    }
    emit('update:modelValue', {
        ...props.modelValue,
        street: suggestion.street,
        city: suggestion.city || props.modelValue.city,
        postalCode: suggestion.postalCode || props.modelValue.postalCode,
    })
}

/**
 * Saisie du code postal → réinitialise ville/rue/complément quand le CP est
 * complet (5 chiffres) ET réellement modifié, puis interroge la BAN pour les
 * villes. Sinon simple mise à jour du champ (correction partielle).
 */
async function onPostalCodeInput(value: string): Promise<void> {
    const digits = (value ?? '').replace(/\D/g, '')
    const previousDigits = (props.modelValue.postalCode ?? '').replace(/\D/g, '')

    if (digits.length === 5 && digits !== previousDigits) {
        addressOptions.value = []
        lastAddressSuggestions = []
        emit('update:modelValue', {
            ...props.modelValue,
            postalCode: value,
            city: null,
            street: null,
            streetComplement: null,
        })
    }
    else {
        update('postalCode', value)
    }

    if (digits.length < 5) return
    try {
        const suggestions = await autocomplete.searchCity(digits)
        fetchedCityOptions.value = suggestions.map(s => ({ value: s.city, label: s.city }))
        degraded.value = false
    }
    catch {
        degraded.value = true
        notifyUnavailable()
    }
}
</script>
