/**
 * 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`, `categoryType`) ;
 * l'erreur globale (status != 422 exploitable) part en toast. Le 409 (doublon
 * 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 categoryTypeId = ref<number | null>(null)

    // 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 initialCategoryTypeId = ref<number | null>(null)

    const submitting = ref(false)

    const isDirty = computed(
        () =>
            name.value !== initialName.value
            || categoryTypeId.value !== initialCategoryTypeId.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) {
            name.value = category.name
            categoryTypeId.value = category.categoryType.id
            initialName.value = category.name
            initialCategoryTypeId.value = category.categoryType.id
        } else {
            name.value = ''
            categoryTypeId.value = null
            initialName.value = ''
            initialCategoryTypeId.value = null
        }
    }

    /**
     * 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 — categoryType obligatoire.
        if (categoryTypeId.value === null) {
            formErrors.setError('categoryType', t('admin.categories.validation.typeRequired'))
        }

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

    /**
     * Construit le payload POST a partir du state. Le `categoryType` est
     * envoye en IRI Hydra (`/api/category_types/{id}`) — convention API
     * Platform pour referencer une ressource liee.
     */
    function buildCreatePayload(): Record<string, unknown> {
        return {
            name: name.value.trim(),
            categoryType: `/api/category_types/${categoryTypeId.value}`,
        }
    }

    /**
     * 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 uniquement les champs modifies pour
     * coller a la semantique merge-patch (Content-Type pose par useApi).
     * 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> = {}
        if (name.value !== initialName.value) {
            payload.name = name.value.trim()
        }
        if (categoryTypeId.value !== initialCategoryTypeId.value) {
            payload.categoryType = `/api/category_types/${categoryTypeId.value}`
        }
        // Garde-fou : un PATCH sans changement ne sert a rien. Theoriquement
        // empeche par le drawer (bouton Enregistrer masque si !isDirty) mais
        // on protege le composable contre un appel direct mal utilise.
        if (Object.keys(payload).length === 0) {
            submitting.value = false
            return null
        }
        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 = ''
        categoryTypeId.value = null
        initialName.value = ''
        initialCategoryTypeId.value = null
        formErrors.clearErrors()
        submitting.value = false
    }

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