/**
 * Regles metier pures de l'ecran « Ajouter un fournisseur » (M2 Commercial).
 *
 * Miroir de `clientFormRules.ts` (M1), centralisees ici (hors composant) pour
 * rester testables unitairement et partagees entre la creation et les ecrans
 * d'edition/consultation (95/96). Ces helpers ne touchent ni a l'API ni a l'etat
 * reactif : ils prennent des brouillons « plats » et retournent des booleens.
 *
 * Le back reste la source de verite (les RG sont re-validees serveur, mode
 * strict) ; ces regles ne servent qu'au feedback UI immediat (gating de boutons).
 *
 * Differences M2 vs M1 :
 *  - Adresse via enum `addressType` (PROSPECT/DEPART/RENDU, RG-2.09) — pas de
 *    drapeaux ni d'exclusivite a gerer cote front (le radio est exclusif par nature).
 *  - Pas d'email de facturation, pas de relation Distributeur/Courtier.
 */

import type { SupplierAddressType } from '~/modules/commercial/types/supplierForm'

/**
 * Onglets « coquille » (non encore implementes) : frame vide, passage
 * automatique a l'onglet suivant (aligne M1).
 */
export const SUPPLIER_FORM_PLACEHOLDER_TABS = ['transport', 'statistics', 'reports', 'exchanges'] as const

/**
 * Onglets affiches uniquement en MODIFICATION/CONSULTATION (jamais a la
 * creation) : Statistiques / Rapports / Echanges. A rebrancher dans les ecrans
 * 95/96 via l'option `includeEditOnlyTabs`.
 */
export const SUPPLIER_FORM_EDIT_ONLY_TABS = ['statistics', 'reports', 'exchanges'] as const

/**
 * Construit l'ordre des onglets du formulaire fournisseur.
 *  - L'onglet Comptabilite n'est present que si l'utilisateur a `accounting.view`
 *    (Bureau / Commerciale ne le voient pas).
 *  - Les onglets edit-only sont exclus par defaut (creation) ; passer
 *    `includeEditOnlyTabs: true` pour les afficher en modification/consultation.
 * Ordre aligne sur la spec M2 § Ecran « Ajouter un fournisseur » (barre 5 onglets).
 */
export function buildSupplierFormTabKeys(
    canAccountingView: boolean,
    options: { includeEditOnlyTabs?: boolean } = {},
): string[] {
    const keys = ['information', 'contacts', 'addresses', 'transport']
    if (canAccountingView) {
        keys.push('accounting')
    }
    if (options.includeEditOnlyTabs) {
        keys.push(...SUPPLIER_FORM_EDIT_ONLY_TABS)
    }
    return keys
}

/**
 * Dernier onglet REMPLISSABLE d'un jeu d'onglets : le dernier qui n'est pas un
 * placeholder. Role-aware sans regle ad hoc — il suffit de lui passer les
 * `tabKeys` deja filtres par permission. Sa validation marque la fin de l'ajout.
 */
export function lastFillableTabKey(tabKeys: string[]): string | undefined {
    return [...tabKeys].reverse().find(
        key => !(SUPPLIER_FORM_PLACEHOLDER_TABS as readonly string[]).includes(key),
    )
}

/** Sous-ensemble d'un contact necessaire aux regles de nommage (RG-2.04/2.13). */
export interface ContactDraft {
    firstName: string | null
    lastName: string | null
}

/** Vrai si une chaine porte au moins un caractere non-espace. */
function isFilled(value: string | null | undefined): boolean {
    return value !== null && value !== undefined && value.trim() !== ''
}

/** RG-2.04 : un contact est valide des qu'il porte un nom OU un prenom. */
export function isContactNamed(contact: ContactDraft): boolean {
    return isFilled(contact.firstName) || isFilled(contact.lastName)
}

/**
 * RG-2.13 : l'onglet Contacts ne peut etre finalise que s'il reste au moins un
 * contact nomme (nom ou prenom).
 */
export function hasAtLeastOneValidContact(contacts: ContactDraft[]): boolean {
    return contacts.some(isContactNamed)
}

/**
 * Primitive reutilisable : vrai si TOUTES les valeurs fournies sont vides. Sert a
 * detecter un bloc de collection totalement vide (amorce non remplie). Un bloc qui
 * porte la moindre donnee n'est PAS « blank » : il doit etre soumis pour declencher
 * sa 422 inline plutot que d'etre saute silencieusement.
 */
export function isBlankRow(values: (string | null | undefined)[]): boolean {
    return values.every(value => !isFilled(value))
}

/** Champs saisissables d'un bloc contact (pour detecter un bloc totalement vide). */
export interface ContactFillableDraft extends ContactDraft {
    jobTitle: string | null
    phonePrimary: string | null
    phoneSecondary: string | null
    email: string | null
}

/**
 * Vrai si AUCUN champ saisissable du bloc contact n'est rempli. Distingue un bloc
 * d'amorce vide (a ignorer au submit) d'un bloc partiellement rempli sans nom
 * (email/telephone/fonction seul) : ce dernier doit etre soumis pour declencher la
 * 422 RG-2.04 affichee inline.
 */
export function isContactBlank(contact: ContactFillableDraft): boolean {
    return isBlankRow([
        contact.firstName,
        contact.lastName,
        contact.jobTitle,
        contact.phonePrimary,
        contact.phoneSecondary,
        contact.email,
    ])
}

/** Champs saisissables d'un bloc RIB (pour detecter un bloc totalement vide). */
export interface RibFillableDraft {
    label: string | null
    bic: string | null
    iban: string | null
}

/**
 * Vrai si AUCUN champ du bloc RIB n'est rempli. Un RIB partiellement rempli (ex.
 * IBAN seul) n'est PAS « blank » : il doit etre soumis pour declencher les 422
 * NotBlank inline plutot que d'etre saute silencieusement.
 */
export function isRibBlank(rib: RibFillableDraft): boolean {
    return isBlankRow([rib.label, rib.bic, rib.iban])
}

/**
 * RG-2.08 : un RIB est complet quand ses trois champs sont remplis (label, BIC,
 * IBAN). Predicat partage entre le gating du bouton « + RIB » et la validation de
 * l'onglet (au moins un RIB complet si reglement LCR).
 */
export function isRibComplete(rib: RibFillableDraft): boolean {
    return isFilled(rib.label) && isFilled(rib.bic) && isFilled(rib.iban)
}

/**
 * Sous-ensemble d'une adresse necessaire a sa validite par-bloc : type (enum),
 * sites et categories rattaches.
 */
export interface AddressValidityDraft {
    addressType: SupplierAddressType | null
    categoryIris: string[]
    siteIris: string[]
}

/**
 * Validite par-bloc d'une adresse : type renseigne (RG-2.09), >= 1 site (RG-2.06)
 * et >= 1 categorie (RG-2.10). Predicat partage entre le gating du bouton
 * « + Adresse » (le dernier bloc doit etre valide avant d'en ajouter un autre) et
 * la validation de l'onglet (toutes les adresses valides). Pas d'email de
 * facturation cote fournisseur (difference M1).
 */
export function isAddressValid(address: AddressValidityDraft): boolean {
    return address.addressType !== null
        && address.siteIris.length >= 1
        && address.categoryIris.length >= 1
}

/** Code stable du type de reglement « virement » (RG-2.07). */
const PAYMENT_TYPE_TRANSFER = 'VIREMENT'

/** Code stable du type de reglement « lettre de change » (RG-2.08). */
const PAYMENT_TYPE_LCR = 'LCR'

/** RG-2.07 : la banque est obligatoire lorsque le type de reglement est un virement. */
export function isBankRequiredForPaymentType(code: string | null | undefined): boolean {
    return code === PAYMENT_TYPE_TRANSFER
}

/** RG-2.08 : au moins un RIB complet est obligatoire lorsque le type de reglement est une LCR. */
export function isRibRequiredForPaymentType(code: string | null | undefined): boolean {
    return code === PAYMENT_TYPE_LCR
}

// ── Champs requis adosses a une colonne NON-nullable (ERP-119) ───────────────
// Memes contraintes qu'au M1 : un champ requis (NotBlank) porte par une colonne
// Doctrine NON nullable rejette `null` en 400 de TYPE avant le Validator. Parade :
// OMETTRE la cle du payload quand elle est vide -> le back produit une 422 NotBlank
// avec propertyPath, mappee en rouge sous le champ.
export const MAIN_REQUIRED_NON_NULLABLE_KEYS = ['companyName'] as const
// addressType : colonne non-nullable + NotBlank cote back. Envoyer `null` (radio
// non choisi) provoque un 400 de TYPE a la deserialisation AVANT le Validator
// (« must be string, NULL given ») -> pas de violation, pas d'erreur inline. On
// omet donc la cle quand elle est vide pour obtenir une 422 NotBlank propertyPath.
export const ADDRESS_REQUIRED_NON_NULLABLE_KEYS = ['addressType', 'postalCode', 'city', 'street'] as const
export const RIB_REQUIRED_NON_NULLABLE_KEYS = ['label', 'bic', 'iban'] as const

/**
 * Retire d'un payload d'ecriture les cles requises laissees vides (null / '' /
 * undefined), pour laisser le back produire une 422 NotBlank par champ plutot
 * qu'un 400 de type sur une colonne non-nullable. Mute et retourne le payload.
 */
export function omitEmptyRequired<T extends Record<string, unknown>>(
    payload: T,
    requiredKeys: readonly string[],
): T {
    for (const key of requiredKeys) {
        const value = payload[key]
        if (value === null || value === undefined || value === '') {
            delete payload[key]
        }
    }

    return payload
}

/**
 * Variante PATCH (edition d'une ligne EXISTANTE) : remplace les cles requises
 * laissees vides par une chaine vide `''` au lieu de les OMETTRE.
 *
 * Pourquoi pas `omitEmptyRequired` en edition : un PATCH a semantique merge — une
 * cle absente laisse la valeur serveur INCHANGEE. Vider un champ requis puis valider
 * renverrait alors un 200 trompeur (l'ancienne valeur est conservee). En envoyant
 * `''`, la propriete `?string` est bien deserialisee (pas de 400 de type, contrairement
 * a `null` sur une colonne non-nullable), puis le Validator `NotBlank(trim)` la rejette
 * -> 422 avec propertyPath, mappee inline sous le champ. Mute et retourne le payload.
 */
export function blankEmptyRequired<T extends Record<string, unknown>>(
    payload: T,
    requiredKeys: readonly string[],
): T {
    for (const key of requiredKeys) {
        const value = payload[key]
        if (value === null || value === undefined || value === '') {
            (payload as Record<string, unknown>)[key] = ''
        }
    }

    return payload
}
