/**
 * Composable de formulaire categorie (M0 — Gestion des categories).
 *
 * Centralise la logique de validation client + appels API (POST / PATCH /
 * DELETE) du drawer de creation/edition. Contrairement a
 * `useCategoriesAdmin` qui porte un state singleton partage entre composants,
 * ce composable est instancie par formulaire (les refs vivent dans la
 * fonction `useCategoryForm()`) — chaque drawer ouvert a son propre state
 * isole.
 *
 * Validations client en miroir des regles back (RG-1.02 / RG-1.04 / RG-1.05) :
 * elles servent juste a eviter l'aller-retour reseau evitable. Le serveur
 * revalide toujours (defense en profondeur).
 *
 * Erreurs par champ : delegue a `useFormErrors` (convention ERP-101). Les
 * violations 422 sont mappees par `propertyPath` (`name`, `categoryTypes`) ;
 * l'erreur globale (status != 422 exploitable) part en toast. Le 409 (doublon
 * de nom GLOBAL, RG-1.07) reste un cas metier specifique : erreur inline sur
 * `name` + toast.
 */
import { computed, ref } from 'vue'
import type { Category } from '~/modules/catalog/types/category'

/**
 * Erreur HTTP capturee par ofetch. On expose juste les champs utilises ici
 * (status et payload data) pour eviter de typer toute la lib.
 */
interface ApiFetchError {
    response?: {
        status?: number
        _data?: unknown
    }
}

export function useCategoryForm() {
    const api = useApi()
    const { t } = useI18n()
    const toast = useToast()

    // Etat d'erreurs par champ (indexe par propertyPath) + dispatch API 422.
    const formErrors = useFormErrors()

    // State local du formulaire — pas singleton, chaque appel a useCategoryForm
    // cree son propre state (cohérent avec le pattern « un drawer = un form »).
    const name = ref('')
    const categoryTypeIds = ref<number[]>([])

    // Snapshot des valeurs initiales : sert a calculer `isDirty` pour le
    // pattern view → edit du drawer (le bouton Enregistrer reste masque tant
    // que rien n'a change en mode consultation).
    const initialName = ref('')
    const initialCategoryTypeIds = ref<number[]>([])

    const submitting = ref(false)

    // Compare deux listes d'ids sans tenir compte de l'ordre (la selection
    // multi-types n'est pas ordonnee).
    function sameIds(a: number[], b: number[]): boolean {
        if (a.length !== b.length) return false
        const sortedA = [...a].sort((x, y) => x - y)
        const sortedB = [...b].sort((x, y) => x - y)
        return sortedA.every((v, i) => v === sortedB[i])
    }

    const isDirty = computed(
        () =>
            name.value !== initialName.value
            || !sameIds(categoryTypeIds.value, initialCategoryTypeIds.value),
    )

    /**
     * Pre-remplit le formulaire a partir d'une categorie existante (mode
     * consultation/edition) ou vide (mode creation). Reinitialise les
     * erreurs et le snapshot initial pour repartir d'un etat propre.
     */
    function loadFrom(category: Category | null): void {
        formErrors.clearErrors()
        if (category) {
            const ids = category.categoryTypes.map(t => t.id)
            name.value = category.name
            categoryTypeIds.value = [...ids]
            initialName.value = category.name
            initialCategoryTypeIds.value = [...ids]
        } else {
            name.value = ''
            categoryTypeIds.value = []
            initialName.value = ''
            initialCategoryTypeIds.value = []
        }
    }

    /**
     * Validation client miroir des RG back. Renvoie true si tout passe et
     * peuple `errors` sinon. Le trim est applique cote client (miroir RG-1.03)
     * mais le serveur retrim de toute facon — pas de risque de divergence.
     */
    function validate(): boolean {
        formErrors.clearErrors()
        const trimmedName = name.value.trim()

        // RG-1.02 — name obligatoire (vide / whitespace-only).
        if (trimmedName === '') {
            formErrors.setError('name', t('admin.categories.validation.nameRequired'))
        } else if (trimmedName.length < 2 || trimmedName.length > 120) {
            // RG-1.04 — longueur 2-120 apres trim.
            formErrors.setError('name', t('admin.categories.validation.nameLength'))
        }

        // RG-1.05 — au moins un type obligatoire.
        if (categoryTypeIds.value.length === 0) {
            formErrors.setError('categoryTypes', t('admin.categories.validation.typesRequired'))
        }

        return !formErrors.errors.name && !formErrors.errors.categoryTypes
    }

    /**
     * Construit le payload POST a partir du state. Les `categoryTypes` sont
     * envoyes en tableau d'IRI Hydra (`/api/category_types/{id}`) — convention
     * API Platform pour referencer une collection de ressources liees.
     */
    function buildCreatePayload(): Record<string, unknown> {
        return {
            name: name.value.trim(),
            categoryTypes: categoryTypeIds.value.map(id => `/api/category_types/${id}`),
        }
    }

    /**
     * Traite une erreur API : 409 (doublon RG-1.07) → erreur inline sur `name`
     * + toast ; sinon delegue a `useFormErrors.handleApiError` (422 mappe inline
     * par champ sans toast, autre → toast de fallback). Retourne true si traitee
     * inline (409/422 mappe), false si fallback toast.
     */
    function handleApiError(e: unknown, attemptedName: string): boolean {
        const status = (e as ApiFetchError)?.response?.status

        if (status === 409) {
            const duplicateMessage = t('admin.categories.toast.duplicate', {
                name: attemptedName,
            })
            formErrors.setError('name', duplicateMessage)
            toast.error({ title: t('errors.title'), message: duplicateMessage })
            return true
        }

        return formErrors.handleApiError(e, { fallbackMessage: t('errors.generic') })
    }

    /**
     * POST /api/categories. Renvoie la categorie creee, ou `null` si la
     * validation client a echoue ou si le serveur a renvoye une erreur. Le
     * caller (drawer) decide quoi faire en fonction (fermer ou rester ouvert).
     */
    async function submitCreate(): Promise<Category | null> {
        if (!validate()) return null
        submitting.value = true
        const payload = buildCreatePayload()
        try {
            const created = await api.post<Category>('/categories', payload, {
                toast: false,
            })
            toast.success({
                title: t('success.title'),
                message: t('admin.categories.toast.created'),
            })
            return created
        } catch (e) {
            handleApiError(e, String(payload.name))
            return null
        } finally {
            submitting.value = false
        }
    }

    /**
     * PATCH /api/categories/{id}. Envoie le payload complet (name +
     * categoryTypes), comme les autres drawers du projet : le bouton
     * Enregistrer sauvegarde a tout moment, meme sans modification, et renvoie
     * toujours un retour (toast succes + refresh). Renvoie la categorie mise a
     * jour, ou `null` en cas d'echec.
     */
    async function submitUpdate(id: number): Promise<Category | null> {
        if (!validate()) return null
        submitting.value = true
        const payload: Record<string, unknown> = {
            name: name.value.trim(),
            categoryTypes: categoryTypeIds.value.map(id => `/api/category_types/${id}`),
        }
        try {
            const updated = await api.patch<Category>(`/categories/${id}`, payload, {
                toast: false,
            })
            toast.success({
                title: t('success.title'),
                message: t('admin.categories.toast.updated'),
            })
            return updated
        } catch (e) {
            const attemptedName = typeof payload.name === 'string'
                ? payload.name
                : name.value.trim()
            handleApiError(e, attemptedName)
            return null
        } finally {
            submitting.value = false
        }
    }

    /**
     * DELETE /api/categories/{id} → soft delete (RG-1.12). Le serveur pose
     * `deleted_at = now()` et retourne 204. Renvoie true en cas de succes,
     * false sinon (avec toast erreur deja affiche).
     */
    async function submitDelete(id: number): Promise<boolean> {
        submitting.value = true
        formErrors.clearErrors()
        try {
            await api.delete(`/categories/${id}`, {}, { toast: false })
            toast.success({
                title: t('success.title'),
                message: t('admin.categories.toast.deleted'),
            })
            return true
        } catch (e) {
            handleApiError(e, name.value)
            return false
        } finally {
            submitting.value = false
        }
    }

    /**
     * Reset complet du formulaire — utilise par le drawer apres save ou
     * fermeture pour ne pas garder de donnees stale entre deux ouvertures.
     */
    function reset(): void {
        name.value = ''
        categoryTypeIds.value = []
        initialName.value = ''
        initialCategoryTypeIds.value = []
        formErrors.clearErrors()
        submitting.value = false
    }

    return {
        // State
        name,
        categoryTypeIds,
        errors: formErrors.errors,
        submitting,
        isDirty,
        // Methods
        loadFrom,
        validate,
        submitCreate,
        submitUpdate,
        submitDelete,
        reset,
    }
}
