import { computed, reactive, ref, type Ref } from 'vue'
import { useFormErrors } from '~/shared/composables/useFormErrors'
import { extractApiErrorMessage, mapViolationsToRecord } from '~/shared/utils/api'
import { removeCollectionRow } from '~/shared/utils/collectionRow'
import {
    emptyProviderAccounting,
    emptyProviderAddress,
    emptyProviderContact,
    emptyProviderMain,
    emptyProviderRib,
    type ProviderAccountingDraft,
    type ProviderAddressFormDraft,
    type ProviderAddressResponse,
    type ProviderContactFormDraft,
    type ProviderContactResponse,
    type ProviderMainDraft,
    type ProviderMainResponse,
    type ProviderRibFormDraft,
    type ProviderRibResponse,
} from '~/modules/technique/types/providerForm'
import {
    buildProviderContactPayload,
    isProviderContactBlank,
    isProviderContactNamed,
} from '~/modules/technique/utils/forms/providerContact'
import {
    buildProviderAddressPayload,
    isProviderAddressValid,
} from '~/modules/technique/utils/forms/providerAddress'
import {
    buildProviderAccountingPayload,
    buildProviderRibPayload,
    isRibBlank,
    isRibComplete,
} from '~/modules/technique/utils/forms/providerAccounting'

/**
 * Workflow de l'ecran « Ajouter un prestataire » (M3 Technique, ERP-141) —
 * miroir conceptuel de la logique de creation fournisseur (M2), extraite ici en
 * composable.
 *
 * Particularites M3 (cf. spec-front § « Ecran Ajouter ») :
 *  - PAS d'onglet « Information » : le formulaire principal est minimal (Nom +
 *    Categorie + Site).
 *  - Selecteur de site SUR le formulaire principal (RG-3.03, relation directe
 *    `provider.sites`).
 *  - Creation incrementale par onglets (Contact · Adresse · Comptabilite) :
 *    POST principal puis PATCH partiels par groupe de serialisation
 *    (`provider:write:*`, mode strict — spec-back § 2.10). Le contenu des onglets
 *    arrive aux tickets ERP-142 → 144 ; ce composable pose le POST principal et
 *    l'orchestration des onglets.
 *
 * Etat 100 % local a l'instance (refs/reactive) — aucune persistance URL.
 */

/**
 * Cles des onglets du FLUX DE CREATION. Pas d'onglet « Information » au M3 ;
 * « Rapports » / « Echanges » n'apparaissent qu'en consultation/modification.
 * L'onglet « Comptabilite » n'est present que pour les roles qui peuvent le voir
 * (`technique.providers.accounting.view` — Admin, Compta).
 */
export function buildProviderCreateTabKeys(canAccountingView: boolean): string[] {
    return canAccountingView
        ? ['contact', 'address', 'accounting']
        : ['contact', 'address']
}

export function useProviderForm() {
    const api = useApi()
    const { t } = useI18n()
    const toast = useToast()
    const { can } = usePermissions()

    // Erreurs de validation par champ (ERP-101) du formulaire principal.
    const mainErrors = useFormErrors()

    // ERP-172 : remontee d'erreur 409/422 lors d'une suppression immediate de
    // sous-ressource (message back affiche en toast dedie — pas de mapping inline,
    // le bloc est en cours de retrait). Ex. dernier RIB d'une LCR -> 409.
    function notifyRemovalError(error: unknown): void {
        toast.error({
            title: t('technique.providers.toast.error'),
            message: extractApiErrorMessage((error as { data?: unknown })?.data) || t('technique.providers.toast.error'),
        })
    }

    // ── Etat du prestataire cree ────────────────────────────────────────────
    const providerId = ref<number | null>(null)
    const mainLocked = ref(false)
    const mainSubmitting = ref(false)
    const tabSubmitting = ref(false)

    // ── Formulaire principal ──────────────────────────────────────────────────
    const main = reactive<ProviderMainDraft>(emptyProviderMain())

    // ── Onglets : ordre + gating progressif ───────────────────────────────────
    const canAccountingView = computed(() => can('technique.providers.accounting.view'))
    const canAccountingManage = computed(() => can('technique.providers.accounting.manage'))
    const tabKeys = computed(() => buildProviderCreateTabKeys(canAccountingView.value))

    // Index du dernier onglet deverrouille (-1 tant que le prestataire n'est pas cree).
    const unlockedIndex = ref(-1)
    const activeTab = ref<string>('contact')
    // Onglets valides (passent en lecture seule).
    const validated = reactive<Record<string, boolean>>({})
    // Mode MODIFICATION (ERP-145) : navigation libre, pas de verrouillage ni de
    // bascule automatique d'onglet a la validation (cf. completeTab).
    const editMode = ref(false)

    function isValidated(key: string): boolean {
        return validated[key] === true
    }

    function tabIndex(key: string): number {
        return tabKeys.value.indexOf(key)
    }

    /**
     * Validation FRONT du formulaire principal : RG-3.03 (>= 1 site) et RG-3.09
     * (>= 1 categorie). Pose les erreurs inline et retourne false si invalide.
     * Le back reste la couche autoritaire (ERP-101) ; ce pre-check evite un
     * aller-retour inutile et porte la garantie RG-3.03 cote front.
     */
    function validateMainFront(): boolean {
        let valid = true
        if (!main.companyName?.trim()) {
            mainErrors.setError('companyName', t('technique.providers.form.errors.nameRequired'))
            valid = false
        }
        if (main.siteIris.length === 0) {
            mainErrors.setError('sites', t('technique.providers.form.errors.siteRequired'))
            valid = false
        }
        if (main.categoryIris.length === 0) {
            mainErrors.setError('categories', t('technique.providers.form.errors.categoryRequired'))
            valid = false
        }
        return valid
    }

    /**
     * Payload du POST principal (groupe `provider:write:main`). `companyName` est
     * omis s'il est vide afin que la 422 porte la violation NotBlank (RG-3.11) sur
     * le champ plutot qu'une erreur de type. Les relations M2M partent en IRI.
     */
    function buildMainPayload(): Record<string, unknown> {
        const payload: Record<string, unknown> = {
            categories: [...main.categoryIris],
            sites: [...main.siteIris],
        }
        if (main.companyName?.trim()) {
            payload.companyName = main.companyName
        }
        return payload
    }

    /**
     * POST /providers (groupe `provider:write:main`). Pre-check front RG-3.03/3.09,
     * puis creation. Au succes : verrouille le bloc principal, deverrouille le 1er
     * onglet et bascule sur « Contact ». Retourne true si cree, false sinon.
     */
    async function submitMain(): Promise<boolean> {
        if (mainSubmitting.value) return false
        mainErrors.clearErrors()
        if (!validateMainFront()) return false

        mainSubmitting.value = true
        try {
            const created = await api.post<ProviderMainResponse>('/providers', buildMainPayload(), {
                headers: { Accept: 'application/ld+json' },
                toast: false,
            })

            providerId.value = created.id
            // Reaffiche la valeur normalisee renvoyee par le serveur (UPPERCASE, RG-3.11).
            main.companyName = created.companyName ?? main.companyName

            mainLocked.value = true
            unlockedIndex.value = 0
            activeTab.value = tabKeys.value[0] ?? 'contact'
            toast.success({ title: t('technique.providers.toast.createSuccess') })
            return true
        }
        catch (error) {
            // 409 = doublon de nom (RG-3.10) → erreur inline dediee + toast ;
            // 422 → mapping inline par champ ; autre → toast de fallback (ERP-101).
            const status = (error as { response?: { status?: number } })?.response?.status
            if (status === 409) {
                const message = t('technique.providers.form.duplicateCompany')
                mainErrors.setError('companyName', message)
                toast.error({ title: t('technique.providers.toast.error'), message })
            }
            else {
                mainErrors.handleApiError(error, { fallbackMessage: t('technique.providers.toast.error') })
            }
            return false
        }
        finally {
            mainSubmitting.value = false
        }
    }

    /**
     * PATCH partiel du prestataire (mode strict : un seul groupe de serialisation
     * par appel — spec-back § 2.10). Sert l'onglet Comptabilite a champs scalaires
     * (ERP-144) ; les onglets Contact/Adresse passent par leurs sous-ressources
     * (POST/PATCH par ligne, ERP-142/143). No-op tant que le prestataire n'existe pas.
     */
    async function patchProvider(payload: Record<string, unknown>): Promise<void> {
        if (providerId.value === null) return
        await api.patch(`/providers/${providerId.value}`, payload, { toast: false })
    }

    /**
     * MODIFICATION du bloc principal (ERP-145) : PATCH /providers/{id} sur le groupe
     * provider:write:main (nom + categories + sites). Pre-check front RG-3.03/3.09,
     * 409 doublon de nom (RG-3.10) et 422 mappes inline comme a la creation. A la
     * difference de `submitMain`, ne verrouille rien et ne bascule pas d'onglet (la
     * navigation est libre en modification). Retourne true si le PATCH a reussi.
     */
    async function updateMain(): Promise<boolean> {
        if (providerId.value === null || mainSubmitting.value) return false
        mainErrors.clearErrors()
        if (!validateMainFront()) return false

        mainSubmitting.value = true
        try {
            const updated = await api.patch<ProviderMainResponse>(
                `/providers/${providerId.value}`,
                buildMainPayload(),
                { toast: false },
            )
            main.companyName = updated.companyName ?? main.companyName
            return true
        }
        catch (error) {
            const status = (error as { response?: { status?: number } })?.response?.status
            if (status === 409) {
                const message = t('technique.providers.form.duplicateCompany')
                mainErrors.setError('companyName', message)
                toast.error({ title: t('technique.providers.toast.error'), message })
            }
            else {
                mainErrors.handleApiError(error, { fallbackMessage: t('technique.providers.toast.error') })
            }
            return false
        }
        finally {
            mainSubmitting.value = false
        }
    }

    /**
     * Marque un onglet valide (passe en lecture seule), deverrouille et avance a
     * l'onglet suivant. Retourne true si c'etait le dernier onglet du flux
     * (creation terminee), false sinon.
     */
    function completeTab(key: string): boolean {
        // En modification : navigation libre, l'onglet reste editable apres validation.
        if (editMode.value) {
            return false
        }
        validated[key] = true
        const index = tabIndex(key)
        const next = tabKeys.value[index + 1]
        if (next === undefined) {
            return true
        }
        unlockedIndex.value = Math.max(unlockedIndex.value, index + 1)
        activeTab.value = next
        return false
    }

    /**
     * Soumet TOUS les blocs d'une collection en collectant les erreurs PAR INDEX :
     * on n'arrete pas au premier bloc en echec (decision ERP-101). Reinitialise la
     * cible, tente chaque ligne via `saveRow`, mappe les 422 inline ou delegue le
     * fallback a `onUnmappedError`. `shouldSkip` ignore les amorces vides. Retourne
     * true si au moins un bloc a echoue. Miroir de `useSupplierFormErrors.submitRows`.
     */
    async function submitRows<T>(
        rows: T[],
        target: Ref<Record<string, string>[]>,
        saveRow: (row: T, index: number) => Promise<void>,
        onUnmappedError: (error: unknown, index: number) => void,
        shouldSkip?: (row: T, index: number) => boolean,
    ): Promise<boolean> {
        target.value = []
        let hasError = false
        for (let index = 0; index < rows.length; index++) {
            const row = rows[index] as T
            if (shouldSkip?.(row, index)) {
                continue
            }
            try {
                await saveRow(row, index)
            }
            catch (error) {
                const response = (error as { response?: { status?: number, _data?: unknown } })?.response
                const mapped = response?.status === 422 ? mapViolationsToRecord(response._data) : {}
                if (Object.keys(mapped).length > 0) {
                    target.value[index] = mapped
                }
                else {
                    onUnmappedError(error, index)
                }
                hasError = true
            }
        }
        return hasError
    }

    // ── Onglet Contact (ERP-142) ──────────────────────────────────────────────
    const contacts = ref<ProviderContactFormDraft[]>([emptyProviderContact()])
    // Erreurs 422 par ligne (alignees sur l'index du v-for), peuplees par submitRows.
    const contactErrors = ref<Record<string, string>[]>([])

    // « + Nouveau contact » desactive tant que le dernier bloc n'a pas de nom OU
    // prenom (RG-3.04, aligne M1/M2 — fonction/tel/email seuls ne suffisent pas).
    const canAddContact = computed(() => {
        const last = contacts.value[contacts.value.length - 1]
        return last !== undefined && isProviderContactNamed(last)
    })

    function addContact(): void {
        if (canAddContact.value) {
            contacts.value.push(emptyProviderContact())
        }
    }

    // ERP-172 : DELETE immediat du contact existant (sous-ressource) a la
    // confirmation de la modale. Bloc jamais persiste (id null) : retrait local.
    async function removeContact(index: number): Promise<void> {
        await removeCollectionRow({
            rows: contacts.value,
            errors: contactErrors.value,
            index,
            endpoint: '/provider_contacts',
            deleteRow: url => api.delete(url, {}, { toast: false }),
            makeEmpty: emptyProviderContact,
            onError: notifyRemovalError,
        })
    }

    /**
     * Valide l'onglet Contact : POST des nouveaux contacts sur
     * /providers/{id}/contacts, PATCH des existants sur /provider_contacts/{id}
     * (sous-ressource, groupe provider:write:contacts). RG-3.12 : au moins un bloc
     * valide. Si l'onglet ne contient QUE des amorces vides, on les soumet pour
     * declencher la 422 RG-3.04 inline (sur `firstName`) plutot que de finaliser un
     * onglet vide. Retourne true si l'onglet a ete valide (avance/termine).
     */
    async function submitContacts(onError: (error: unknown) => void): Promise<boolean> {
        if (providerId.value === null || tabSubmitting.value) {
            return false
        }
        tabSubmitting.value = true
        try {
            const hasSubmittable = contacts.value.some(c => c.id !== null || !isProviderContactBlank(c))
            const hasError = await submitRows(
                contacts.value,
                contactErrors,
                async (contact) => {
                    const body = buildProviderContactPayload(contact)
                    if (contact.id === null) {
                        const created = await api.post<ProviderContactResponse>(
                            `/providers/${providerId.value}/contacts`,
                            body,
                            { headers: { Accept: 'application/ld+json' }, toast: false },
                        )
                        contact.id = created.id
                        contact.iri = created['@id'] ?? null
                    }
                    else {
                        await api.patch(`/provider_contacts/${contact.id}`, body, { toast: false })
                    }
                },
                onError,
                contact => hasSubmittable && contact.id === null && isProviderContactBlank(contact),
            )
            if (hasError) {
                return false
            }
            completeTab('contact')
            return true
        }
        finally {
            tabSubmitting.value = false
        }
    }

    // ── Onglet Adresse (ERP-143) ──────────────────────────────────────────────
    const addresses = ref<ProviderAddressFormDraft[]>([emptyProviderAddress()])
    // Erreurs 422 par ligne (alignees sur l'index du v-for).
    const addressErrors = ref<Record<string, string>[]>([])

    // « + Nouvelle adresse » desactive tant que la derniere adresse n'a pas
    // au moins un site ET une categorie (RG-3.05 / RG-3.09).
    const canAddAddress = computed(() => {
        const last = addresses.value[addresses.value.length - 1]
        return last !== undefined && isProviderAddressValid(last)
    })

    function addAddress(): void {
        if (canAddAddress.value) {
            addresses.value.push(emptyProviderAddress())
        }
    }

    // ERP-172 : DELETE immediat de l'adresse existante (sous-ressource).
    async function removeAddress(index: number): Promise<void> {
        await removeCollectionRow({
            rows: addresses.value,
            errors: addressErrors.value,
            index,
            endpoint: '/provider_addresses',
            deleteRow: url => api.delete(url, {}, { toast: false }),
            makeEmpty: emptyProviderAddress,
            onError: notifyRemovalError,
        })
    }

    /**
     * Valide l'onglet Adresse : POST des nouvelles adresses sur
     * /providers/{id}/addresses, PATCH des existantes sur /provider_addresses/{id}
     * (sous-ressource, groupe provider:write:addresses). Erreurs 422 collectees par
     * ligne. Retourne true si l'onglet a ete valide (avance/termine).
     */
    async function submitAddresses(onError: (error: unknown) => void): Promise<boolean> {
        if (providerId.value === null || tabSubmitting.value) {
            return false
        }
        tabSubmitting.value = true
        try {
            const hasError = await submitRows(
                addresses.value,
                addressErrors,
                async (address) => {
                    const body = buildProviderAddressPayload(address)
                    if (address.id === null) {
                        const created = await api.post<ProviderAddressResponse>(
                            `/providers/${providerId.value}/addresses`,
                            body,
                            { headers: { Accept: 'application/ld+json' }, toast: false },
                        )
                        address.id = created.id
                    }
                    else {
                        await api.patch(`/provider_addresses/${address.id}`, body, { toast: false })
                    }
                },
                onError,
            )
            if (hasError) {
                return false
            }
            completeTab('address')
            return true
        }
        finally {
            tabSubmitting.value = false
        }
    }

    // ── Onglet Comptabilite (ERP-144) ─────────────────────────────────────────
    const accounting = reactive<ProviderAccountingDraft>(emptyProviderAccounting())
    const ribs = ref<ProviderRibFormDraft[]>([])
    const accountingErrors = useFormErrors()
    // Erreurs 422 par ligne de RIB (alignees sur l'index du v-for).
    const ribErrors = ref<Record<string, string>[]>([])

    // L'onglet est editable seulement avec accounting.manage (sinon lecture seule).
    const accountingReadonly = computed(() => isValidated('accounting') || !canAccountingManage.value)

    /**
     * Met a jour le type de reglement (IRI) en propageant ses RG inter-champs :
     *  - hors VIREMENT (RG-3.07) : on vide la banque (sans objet) ;
     *  - LCR (RG-3.08) : on garantit au moins un bloc RIB visible ; hors LCR, on
     *    purge les erreurs de RIB (les blocs sont conserves mais non persistes).
     * `isBankRequired` / `isRibRequired` sont calcules par l'appelant (page) a
     * partir du code resolu via les referentiels.
     */
    function setPaymentType(iri: string | null, isBankRequired: boolean, isRibRequired: boolean): void {
        accounting.paymentTypeIri = iri
        if (!isBankRequired) {
            accounting.bankIri = null
        }
        if (isRibRequired) {
            if (ribs.value.length === 0) {
                ribs.value.push(emptyProviderRib())
            }
        }
        else {
            ribErrors.value = []
        }
    }

    // « + RIB » desactive tant que le dernier bloc RIB n'est pas complet (RG-3.08).
    const canAddRib = computed(() => {
        const last = ribs.value[ribs.value.length - 1]
        return last !== undefined && isRibComplete(last)
    })

    function addRib(): void {
        if (canAddRib.value) {
            ribs.value.push(emptyProviderRib())
        }
    }

    // ERP-172 : DELETE immediat du RIB existant. Le back peut refuser la suppression
    // du dernier RIB d'une LCR -> 409 remonte via notifyRemovalError, bloc conserve.
    async function removeRib(index: number): Promise<void> {
        await removeCollectionRow({
            rows: ribs.value,
            errors: ribErrors.value,
            index,
            endpoint: '/provider_ribs',
            deleteRow: url => api.delete(url, {}, { toast: false }),
            makeEmpty: emptyProviderRib,
            onError: notifyRemovalError,
        })
    }

    /**
     * Valide l'onglet Comptabilite : (1) sous LCR, POST/PATCH des RIB d'abord
     * (le back valide RG-3.08 sur le PATCH scalaires, les RIB doivent donc exister
     * AVANT) ; (2) PATCH des scalaires comptables (groupe provider:write:accounting,
     * banque envoyee seulement si VIREMENT — RG-3.07). Erreurs RIB par ligne ;
     * erreurs scalaires inline (bank/paymentType). Retourne true si l'onglet a ete
     * valide.
     */
    async function submitAccounting(
        isBankRequired: boolean,
        isRibRequired: boolean,
        onRibError: (error: unknown) => void,
    ): Promise<boolean> {
        if (providerId.value === null || tabSubmitting.value) {
            return false
        }
        tabSubmitting.value = true
        accountingErrors.clearErrors()
        try {
            // 1) RIB d'abord, uniquement sous LCR. Une amorce vide neuve est sautee
            // s'il reste un autre RIB soumettable ; sinon (LCR sans aucun RIB rempli)
            // on la soumet pour declencher la 422 NotBlank inline.
            if (isRibRequired) {
                const hasSubmittableRib = ribs.value.some(r => r.id !== null || !isRibBlank(r))
                const ribHasError = await submitRows(
                    ribs.value,
                    ribErrors,
                    async (rib) => {
                        const body = buildProviderRibPayload(rib)
                        if (rib.id === null) {
                            const created = await api.post<ProviderRibResponse>(
                                `/providers/${providerId.value}/ribs`,
                                body,
                                { headers: { Accept: 'application/ld+json' }, toast: false },
                            )
                            rib.id = created.id
                        }
                        else {
                            await api.patch(`/provider_ribs/${rib.id}`, body, { toast: false })
                        }
                    },
                    onRibError,
                    rib => hasSubmittableRib && rib.id === null && isRibBlank(rib),
                )
                if (ribHasError) {
                    return false
                }
            }

            // 2) PATCH des scalaires comptables (erreurs inline sur leurs champs).
            try {
                await api.patch(
                    `/providers/${providerId.value}`,
                    buildProviderAccountingPayload(accounting, isBankRequired),
                    { toast: false },
                )
            }
            catch (error) {
                accountingErrors.handleApiError(error, { fallbackMessage: t('technique.providers.toast.error') })
                return false
            }

            completeTab('accounting')
            return true
        }
        finally {
            tabSubmitting.value = false
        }
    }

    return {
        // etat
        main,
        providerId,
        mainLocked,
        mainSubmitting,
        tabSubmitting,
        mainErrors,
        // onglets
        canAccountingView,
        canAccountingManage,
        tabKeys,
        activeTab,
        unlockedIndex,
        validated,
        editMode,
        isValidated,
        // contacts
        contacts,
        contactErrors,
        canAddContact,
        addContact,
        removeContact,
        submitContacts,
        // adresses
        addresses,
        addressErrors,
        canAddAddress,
        addAddress,
        removeAddress,
        submitAddresses,
        // comptabilite
        accounting,
        ribs,
        accountingErrors,
        ribErrors,
        accountingReadonly,
        setPaymentType,
        canAddRib,
        addRib,
        removeRib,
        submitAccounting,
        // actions
        validateMainFront,
        buildMainPayload,
        submitMain,
        updateMain,
        patchProvider,
        completeTab,
        submitRows,
    }
}
