/**
 * Composable d'erreurs de formulaire — convention de mapping erreur→champ pour
 * tous les forms du projet (ERP-101).
 *
 * Le back renvoie TOUTES les violations d'une 422 d'un coup (un `propertyPath`
 * + `message` par champ fautif). Ce composable centralise leur affichage
 * inline : il tient un `Record<propertyPath, message>` reactif que le template
 * branche directement sur la prop `:error` des composants `Malio*` (le nom du
 * champ cote front = le `propertyPath` cote back, donc aucun mapping manuel).
 *
 * Chaque appel cree son propre etat (refs internes a la fonction) — un form =
 * une instance, pas de singleton partage.
 *
 * Convention d'usage : les appels API qui veulent un retour inline doivent
 * passer `{ toast: false }` a `useApi` (sinon le toast natif masque le mapping
 * fin), puis router l'erreur via `handleApiError`. Pour les collections (1
 * appel par ligne), utiliser directement `mapViolationsToRecord` par ligne.
 */
import { computed, reactive } from 'vue'
import { extractApiErrorMessage, extractApiViolations, resolveViolationMessage } from '~/shared/utils/api'

/**
 * Erreur HTTP capturee par ofetch. On n'expose que les champs lus ici (status
 * + payload) pour eviter de typer toute la lib.
 */
interface ApiFetchError {
    response?: {
        status?: number
        _data?: unknown
    }
}

/** Options de `handleApiError`. */
interface HandleApiErrorOptions {
    /** Message de toast si l'erreur n'est pas une 422 exploitable. */
    fallbackMessage?: string
}

export function useFormErrors() {
    const toast = useToast()
    const { t } = useI18n()

    // Etat d'erreurs indexe par propertyPath. Reactif : muter une cle suffit a
    // rafraichir la prop `:error` du champ correspondant.
    const errors = reactive<Record<string, string>>({})

    const hasErrors = computed(() => Object.keys(errors).length > 0)

    /** Pose une erreur sur un champ. */
    function setError(field: string, message: string): void {
        errors[field] = message
    }

    /** Retire l'erreur d'un champ (no-op si absente). */
    function clearError(field: string): void {
        delete errors[field]
    }

    /** Vide toutes les erreurs (a appeler en debut de submit). */
    function clearErrors(): void {
        for (const key of Object.keys(errors)) {
            delete errors[key]
        }
    }

    /**
     * Mappe les violations 422 d'un payload sur les champs. Retourne true des
     * qu'au moins une violation a ete posee, false sinon (payload sans
     * violation exploitable).
     */
    function setServerErrors(data: unknown): boolean {
        const violations = extractApiViolations(data)
        let mapped = false
        for (const v of violations) {
            if (!v.propertyPath) continue
            // Message back tel quel, sauf code surcharge par une cle i18n (ex.
            // erreur de type sur une date non parsable -> « Date invalide »).
            errors[v.propertyPath] = resolveViolationMessage(v, t)
            mapped = true
        }
        return mapped
    }

    /**
     * Route une erreur API : 422 avec violations exploitables → mapping inline
     * (pas de toast, l'erreur s'affiche sous le champ) ; sinon → toast de
     * fallback (message serveur extrait, ou `fallbackMessage`).
     *
     * Retourne true si l'erreur a ete mappee inline, false si fallback toast.
     */
    function handleApiError(e: unknown, opts: HandleApiErrorOptions = {}): boolean {
        const status = (e as ApiFetchError)?.response?.status
        const data = (e as ApiFetchError)?.response?._data

        if (status === 422 && setServerErrors(data)) {
            return true
        }

        const message
            = extractApiErrorMessage(data)
            || opts.fallbackMessage
            || t('errors.generic')
        toast.error({ title: t('errors.title'), message })
        return false
    }

    return {
        errors,
        hasErrors,
        setError,
        clearError,
        clearErrors,
        setServerErrors,
        handleApiError,
    }
}
