<template>
    <div class="relative grid grid-cols-4 gap-x-[44px] gap-y-4 bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]">
        <!-- ariaLabel via v-bind objet (prop camelCase ; aria-* serait un attribut HTML). -->
        <MalioButtonIcon
            v-if="removable && !readonly"
            icon="mdi:delete-outline"
            variant="ghost"
            button-class="absolute top-3 right-3"
            v-bind="{ ariaLabel: t('commercial.clients.form.address.remove') }"
            @click="$emit('remove')"
        />

        <!-- Usage de l'adresse : Select unique (plus simple pour l'utilisateur)
             remplacant les 3 cases. Les options encodent les combinaisons valides
             (exclusivite Prospect, RG-1.06/07/08) ; le back recoit toujours les
             drapeaux isProspect / isDelivery / isBilling (aucune RG modifiee). -->
        <!-- Erreur portee sur `isProspect` cote back (Callback type obligatoire +
             exclusivite prospect) -> affichee sous le select Type d'adresse. -->
        <MalioSelect
            :model-value="addressType"
            :options="addressTypeOptions"
            :label="t('commercial.clients.form.address.addressType')"
            :readonly="readonly"
            :required="true"
            :error="errors?.isProspect"
            @update:model-value="onAddressTypeChange"
        />

        <!-- Sites Starseed : multiselect a tags (>= 1 obligatoire, RG-1.10). -->
        <MalioSelectCheckbox
            :model-value="model.siteIris"
            :options="siteOptions"
            :label="t('commercial.clients.form.address.sites')"
            :display-tag="true"
            :readonly="readonly"
            :required="true"
            :error="errors?.sites"
            @update:model-value="(v: (string | number)[]) => update('siteIris', v.map(String))"
        />

        <MalioSelectCheckbox
            :model-value="model.contactIris"
            :options="contactOptions"
            :label="t('commercial.clients.form.address.contacts')"
            :display-tag="true"
            :readonly="readonly"
            @update:model-value="(v: (string | number)[]) => update('contactIris', v.map(String))"
        />

        <!-- Email(s) de facturation : visible/obligatoire seulement si Facturation
             (RG-1.11). Le « + » revele un 2e email optionnel (max 2, pendant du
             telephone secondaire) qui coule dans la grille. Sinon un filler comble
             la colonne pour que Categorie reparte au debut de la ligne suivante. -->
        <MalioInputEmail
            v-if="isBillingEmailRequired(model)"
            :model-value="model.billingEmail"
            :label="t('commercial.clients.form.address.billingEmail')"
            :required="true"
            :readonly="readonly"
            :lowercase="true"
            :error="errors?.billingEmail"
            :addable="!model.hasSecondaryBillingEmail && !readonly"
            :add-button-label="t('commercial.clients.form.address.addBillingEmail')"
            @update:model-value="(v: string) => update('billingEmail', v)"
            @add="revealSecondaryBillingEmail"
        />
        <div v-else aria-hidden="true" />

        <MalioInputEmail
            v-if="isBillingEmailRequired(model) && model.hasSecondaryBillingEmail"
            :model-value="model.billingEmailSecondary"
            :label="t('commercial.clients.form.address.billingEmailSecondary')"
            :readonly="readonly"
            :lowercase="true"
            :error="errors?.billingEmailSecondary"
            @update:model-value="(v: string) => update('billingEmailSecondary', v)"
        />

        <MalioSelectCheckbox
            :model-value="model.categoryIris"
            :options="categoryOptions"
            :label="t('commercial.clients.form.address.categories')"
            :display-tag="true"
            :readonly="readonly"
            :required="true"
            :error="errors?.categories"
            @update:model-value="(v: (string | number)[]) => update('categoryIris', v.map(String))"
        />

        <MalioSelect
            :model-value="model.country"
            :options="countryOptions"
            :label="t('commercial.clients.form.address.country')"
            :readonly="readonly"
            :required="true"
            @update:model-value="(v: string | number | null) => update('country', String(v ?? 'France'))"
        />

        <MalioInputText
            :model-value="model.postalCode"
            :label="t('commercial.clients.form.address.postalCode')"
            :mask="POSTAL_CODE_MASK"
            :readonly="readonly"
            :required="true"
            :error="errors?.postalCode"
            @update:model-value="onPostalCodeChange"
        />

        <!-- Ville : MalioSelect alimente par le code postal (BAN). Si la BAN est
             indisponible, bascule en saisie libre — recuperable : re-saisir le
             code postal relance la recherche et repasse en select au succes. -->
        <MalioSelect
            v-if="!degraded"
            :model-value="model.city"
            :options="cityOptions"
            :label="t('commercial.clients.form.address.city')"
            :readonly="readonly"
            empty-option-label=""
            :required="true"
            :error="errors?.city"
            @update:model-value="(v: string | number | null) => update('city', v === null ? null : String(v))"
        />
        <MalioInputText
            v-else
            :model-value="model.city"
            :label="t('commercial.clients.form.address.city')"
            :readonly="readonly"
            :required="true"
            :error="errors?.city"
            @update:model-value="(v: string) => update('city', v)"
        />

        <!-- Adresse + Adresse complementaire sur 2 colonnes : on wrappe car
             MalioInputText/Autocomplete (inheritAttrs:false) renvoient `class`
             sur l'input interne, pas sur la cellule de grille. Le wrapper porte
             le col-span-2, le champ le remplit (w-full). -->
        <div class="col-span-2">
            <!-- Adresse : saisie assistee (BAN) en edition ; champ texte simple
                 seulement en lecture seule (MalioInputAutocomplete ne reaffiche pas
                 sa valeur liee, il n'afficherait rien en readonly). allow-create :
                 si la BAN ne propose rien (ou erreur), le texte saisi est CONSERVE au
                 blur/Entree (saisie manuelle) — sinon il serait efface. La ville reste
                 pilotee par le code postal ; choisir une suggestion remplit rue+ville+CP. -->
            <MalioInputAutocomplete
                v-if="!readonly"
                :model-value="model.street"
                :options="addressOptions"
                :loading="addressLoading"
                :min-search-length="3"
                :label="t('commercial.clients.form.address.street')"
                :readonly="readonly"
                :required="true"
                :error="errors?.street"
                :allow-create="true"
                :no-results-text="t('commercial.clients.form.address.streetNotFound')"
                @update:model-value="(v: string | number | null) => update('street', v === null ? null : String(v))"
                @search="onAddressSearch"
                @select="onAddressSelect"
            />
            <MalioInputText
                v-else
                :model-value="model.street"
                :label="t('commercial.clients.form.address.street')"
                :readonly="readonly"
                :required="true"
                :error="errors?.street"
                @update:model-value="(v: string) => update('street', v)"
            />
        </div>

        <div class="col-span-1">
            <MalioInputText
                :model-value="model.streetComplement"
                :label="t('commercial.clients.form.address.streetComplement')"
                :readonly="readonly"
                :error="errors?.streetComplement"
                @update:model-value="(v: string) => update('streetComplement', v)"
            />
        </div>

    </div>
</template>

<script setup lang="ts">
import {
    addressFlagsFromType,
    addressTypeFromFlags,
    isBillingEmailRequired,
    type AddressType,
} from '~/modules/commercial/utils/forms/clientFormRules'
import { useAddressAutocomplete, type AddressSuggestion } from '~/shared/composables/useAddressAutocomplete'
import type { CategoryOption, RefOption } from '~/modules/commercial/composables/useClientReferentials'
import type { AddressFormDraft } from '~/modules/commercial/types/clientForm'

// Masque code postal FR : 5 chiffres.
const POSTAL_CODE_MASK = '#####'

const props = defineProps<{
    /** Brouillon de l'adresse (v-model). */
    modelValue: AddressFormDraft
    title: string
    /** Categories autorisees sur une adresse (DISTRIBUTEUR/COURTIER exclus, RG-1.29). */
    categoryOptions: CategoryOption[]
    /** Sites Starseed disponibles. */
    siteOptions: RefOption[]
    /** Contacts deja saisis, rattachables a l'adresse. */
    contactOptions: RefOption[]
    /** Pays disponibles (France par defaut). */
    countryOptions: RefOption[]
    removable?: boolean
    readonly?: boolean
    /** Erreurs serveur 422 de cette ligne, indexees par champ (ERP-101). */
    errors?: Record<string, string>
}>()

const emit = defineEmits<{
    'update:modelValue': [value: AddressFormDraft]
    'remove': []
    /** Emis une fois quand le service d'autocompletion bascule en indisponible. */
    'degraded': []
}>()

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

const model = computed(() => props.modelValue)

// Type d'adresse (Select unique) derive des drapeaux back. null tant qu'aucun
// drapeau n'est pose -> champ vide + bouton « Valider » bloque (cf. parent).
const addressType = computed<AddressType | null>(() => addressTypeFromFlags(model.value))

const addressTypeOptions = computed<RefOption[]>(() => [
    { value: 'prospect', label: t('commercial.clients.form.address.addressTypeProspect') },
    { value: 'delivery', label: t('commercial.clients.form.address.addressTypeDelivery') },
    { value: 'billing', label: t('commercial.clients.form.address.addressTypeBilling') },
    { value: 'delivery_billing', label: t('commercial.clients.form.address.addressTypeDeliveryBilling') },
    { value: 'broker', label: t('commercial.clients.form.address.addressTypeBroker') },
    { value: 'distributor', label: t('commercial.clients.form.address.addressTypeDistributor') },
])

/** Applique le type choisi en repercutant les 3 drapeaux back (immutabilite). */
function onAddressTypeChange(value: string | number | null): void {
    if (value === null) return
    emit('update:modelValue', { ...props.modelValue, ...addressFlagsFromType(value as AddressType) })
}

// Repli saisie libre de la VILLE quand la BAN est indisponible (recuperable :
// remis a false des qu'une recherche de ville aboutit). N'affecte plus le champ
// Adresse, qui reste en autocompletion et reessaie a chaque frappe.
const degraded = ref(false)
// Avertissement « service indisponible » envoye au parent une seule fois.
let unavailableNotified = false
// Villes proposees par la BAN (alimentees a la saisie du code postal).
const banCityOptions = ref<RefOption[]>([])
// Adresses proposees par la BAN (alimentees a la saisie d'adresse).
const banAddressOptions = ref<RefOption[]>([])

// Options ville effectives : on garantit que la ville courante figure toujours
// dans la liste, sinon MalioSelect (qui resout le libelle depuis ses options)
// afficherait un champ vide en lecture seule (consultation 1.11) ou en edition
// d'une adresse existante (1.12), ou la BAN n'a pas (re)peuple les suggestions.
const cityOptions = computed<RefOption[]>(() => {
    const current = props.modelValue.city
    if (current && !banCityOptions.value.some(o => o.value === current)) {
        return [{ value: current, label: current }, ...banCityOptions.value]
    }
    return banCityOptions.value
})

// Meme garantie que cityOptions pour le champ Adresse : la rue courante doit
// toujours figurer dans les options, sinon MalioInputAutocomplete (qui resout
// l'affichage depuis ses options) laisse le champ VIDE des que la liste de
// suggestions BAN est vide — typiquement juste apres validation (remontage) ou
// a l'edition d'une adresse existante (1.12), alors que la valeur est bien
// persistee. On reinjecte donc la rue liee si la BAN ne l'a pas (re)proposee.
const addressOptions = computed<RefOption[]>(() => {
    const current = props.modelValue.street
    if (current && !banAddressOptions.value.some(o => o.value === current)) {
        return [{ value: current, label: current }, ...banAddressOptions.value]
    }
    return banAddressOptions.value
})
const addressLoading = ref(false)
// Conserve les suggestions d'adresse pour retrouver ville/CP au moment du select.
let lastAddressSuggestions: AddressSuggestion[] = []

/** Emet un nouveau brouillon avec le champ modifie (immutabilite). */
function update<K extends keyof AddressFormDraft>(field: K, value: AddressFormDraft[K]): void {
    emit('update:modelValue', { ...props.modelValue, [field]: value })
}

/** Revele le 2e champ email de facturation (clic sur le « + »). */
function revealSecondaryBillingEmail(): void {
    emit('update:modelValue', { ...props.modelValue, hasSecondaryBillingEmail: true })
}

/** Previent le parent (toast unique) que l'autocompletion est indisponible. */
function notifyUnavailable(): void {
    if (!unavailableNotified) {
        unavailableNotified = true
        emit('degraded')
    }
}

/** Saisie du code postal → met a jour le champ + interroge la BAN pour la ville. */
async function onPostalCodeChange(value: string): Promise<void> {
    update('postalCode', value)

    const digits = (value ?? '').replace(/\D/g, '')
    if (digits.length < 5) {
        return
    }
    try {
        const suggestions = await autocomplete.searchCity(digits)
        banCityOptions.value = suggestions.map(s => ({ value: s.city, label: s.city }))
        // Service repondu : on (re)passe la Ville en select assiste.
        degraded.value = false
    }
    catch {
        // BAN indispo : Ville en saisie libre (recuperable au prochain essai).
        degraded.value = true
        notifyUnavailable()
    }
}

/** Recherche d'adresse assistee (event de MalioInputAutocomplete). */
async function onAddressSearch(query: string): Promise<void> {
    // La BAN exige au moins 3 caracteres : on n'envoie rien en deca (evite un 400)
    // et on vide les suggestions devenues obsoletes.
    if (query.trim().length < 3) {
        banAddressOptions.value = []
        return
    }
    addressLoading.value = true
    try {
        const postalCode = (model.value.postalCode ?? '').replace(/\D/g, '') || undefined
        const suggestions = await autocomplete.searchAddress(query, postalCode)
        lastAddressSuggestions = suggestions
        banAddressOptions.value = suggestions.map(s => ({ value: s.street, label: s.label }))
    }
    catch {
        // Erreur transitoire : on vide les suggestions, la prochaine frappe reessaie
        // (pas de bascule definitive — c'etait le bug). Avertissement une seule fois.
        banAddressOptions.value = []
        notifyUnavailable()
    }
    finally {
        addressLoading.value = false
    }
}

/**
 * Selection d'une suggestion d'adresse → remplit rue + ville + CP.
 * Le type d'option suit le contrat MalioInputAutocomplete ({ label, value }).
 */
function onAddressSelect(option: { label: string, value: string | number } | null): void {
    if (option === null) {
        return
    }
    const suggestion = lastAddressSuggestions.find(s => s.street === option.value)
    if (!suggestion) {
        update('street', String(option.value))
        return
    }
    emit('update:modelValue', {
        ...props.modelValue,
        street: suggestion.street,
        city: suggestion.city,
        postalCode: suggestion.postalCode,
    })
}
</script>
