/**
 * Helpers purs de l'ecran « Modification client » (M1 Commercial, 1.12).
 *
 * Deux responsabilites, toutes deux testables unitairement (cf. clientEdit.spec.ts) :
 *  1. Pre-remplissage : mapper le payload `GET /api/clients/{id}` (embed
 *     contacts/adresses/ribs + scalaires) vers les brouillons « plats » edites
 *     par la page et les blocs reutilisables (mappers contacts/adresses/ribs/
 *     comptabilite reutilises depuis clientConsultation).
 *  2. Scoping STRICT des payloads PATCH (mode strict RG-1.28 / ERP-74) : chaque
 *     onglet n'envoie QUE les champs de SON groupe de serialisation, jamais un
 *     payload mixte — un champ hors-permission = 403 sur l'integralite cote back.
 *
 * Ces helpers ne touchent ni a l'API ni a l'etat reactif.
 *
 * NOTE : l'onglet Information est facultatif pour tous les roles (RG-1.04
 * « Information obligatoire pour la Commerciale » retiree cote back).
 */

import {
    iriOf,
    relationOf,
    type ClientDetail,
} from '~/modules/commercial/utils/forms/clientConsultation'
import {
    ADDRESS_REQUIRED_NON_NULLABLE_KEYS,
    blankEmptyRequired,
    MAIN_REQUIRED_NON_NULLABLE_KEYS,
    omitEmptyRequired,
    RIB_REQUIRED_NON_NULLABLE_KEYS,
} from '~/modules/commercial/utils/forms/clientFormRules'
import type { AddressFormDraft, ContactFormDraft, RibFormDraft } from '~/modules/commercial/types/clientForm'

/**
 * Etat « plat » du bloc principal (groupe client:write:main). Distinct des
 * brouillons Contact : ces champs vivent sur le Client lui-meme (companyName,
 * categories, relation, triage), pas sur une sous-ressource ClientContact. Les
 * coordonnees de contact (nom, prenom, telephones, email) ne sont plus portees
 * par le Client : elles vivent exclusivement dans l'onglet Contacts.
 */
export interface MainFormDraft {
    companyName: string | null
    /** IRI des categories rattachees (M2M). */
    categoryIris: string[]
    relationType: 'distributeur' | 'courtier' | null
    distributorIri: string | null
    brokerIri: string | null
    triageService: boolean
}

/** Etat « plat » de l'onglet Information (groupe client:write:information). */
export interface InformationFormDraft {
    description: string | null
    competitors: string | null
    /** Date de creation de l'entreprise au format YYYY-MM-DD (MalioDate). */
    foundedAt: string | null
    /**
     * Saisie brute invalide remontee par MalioDate (`@update:rawValue`) : '' tant
     * que la saisie est valide/vide, sinon le texte tel que tape. On l'envoie au
     * back en priorite sur `foundedAt` pour que la 422 (validation autoritaire du
     * format, ERP-101) porte sur le champ et s'affiche inline. Cf. MUI-44.
     */
    foundedAtRaw: string
    /** Nombre de salaries en chaine (saisie masquee), converti en number au PATCH. */
    employeesCount: string | null
    revenueAmount: string | null
    profitAmount: string | null
    directorName: string | null
}

/** Etat « plat » de l'onglet Comptabilite (groupe client:write:accounting). */
export interface AccountingFormDraft {
    siren: string | null
    accountNumber: string | null
    nTva: string | null
    tvaModeIri: string | null
    paymentDelayIri: string | null
    paymentTypeIri: string | null
    bankIri: string | null
}

/** Permissions de l'utilisateur courant pertinentes pour l'edition d'un client. */
export interface ClientEditAbilities {
    /** `commercial.clients.manage` : bloc principal + onglets metier. */
    canManage: boolean
    /** `commercial.clients.accounting.view` : visibilite de l'onglet Comptabilite. */
    canAccountingView: boolean
    /** `commercial.clients.accounting.manage` : edition de l'onglet Comptabilite. */
    canAccountingManage: boolean
}

/** Editabilite resolue par zone d'onglet (deduite des permissions). */
export interface TabEditability {
    /** Bloc principal + onglets Information / Contact / Adresse editables. */
    businessEditable: boolean
    /** Onglet Comptabilite present (affiche). */
    accountingVisible: boolean
    /** Onglet Comptabilite editable. */
    accountingEditable: boolean
}

// ── Pre-remplissage (GET detail -> brouillons) ──────────────────────────────

/**
 * Mappe le detail client vers le brouillon du bloc principal. La relation
 * Distributeur/Courtier est resolue par exclusivite (RG-1.03) et son IRI extrait
 * de l'embed.
 */
export function mapMainDraft(client: ClientDetail): MainFormDraft {
    const relation = relationOf(client)

    return {
        companyName: client.companyName ?? null,
        categoryIris: (client.categories ?? []).map(c => c['@id']),
        relationType: relation.type,
        distributorIri: iriOf(client.distributor),
        brokerIri: iriOf(client.broker),
        triageService: client.triageService === true,
    }
}

/** Mappe le detail client vers le brouillon de l'onglet Information. */
export function mapInformationDraft(client: ClientDetail): InformationFormDraft {
    return {
        description: client.description ?? null,
        competitors: client.competitors ?? null,
        // MalioDate attend strictement YYYY-MM-DD : on tronque l'ISO datetime.
        foundedAt: client.foundedAt ? client.foundedAt.slice(0, 10) : null,
        // Aucune saisie brute invalide au chargement (la valeur stockee est valide).
        foundedAtRaw: '',
        employeesCount: client.employeesCount != null ? String(client.employeesCount) : null,
        revenueAmount: client.revenueAmount ?? null,
        profitAmount: client.profitAmount ?? null,
        directorName: client.directorName ?? null,
    }
}

/** Mappe les champs comptables du detail vers le brouillon de l'onglet (scalaires + IRI). */
export function mapAccountingFormDraft(client: ClientDetail): AccountingFormDraft {
    return {
        siren: client.siren ?? null,
        accountNumber: client.accountNumber ?? null,
        nTva: client.nTva ?? null,
        tvaModeIri: iriOf(client.tvaMode),
        paymentDelayIri: iriOf(client.paymentDelay),
        paymentTypeIri: iriOf(client.paymentType),
        bankIri: iriOf(client.bank),
    }
}

// ── Scoping strict des payloads PATCH ────────────────────────────────────────

/**
 * Options de construction d'un payload d'ecriture.
 *  - `forUpdate: false` (defaut, CREATION/POST) : champs requis vides OMIS -> 422
 *    NotBlank (le back ne reçoit pas la cle, la propriete garde son defaut).
 *  - `forUpdate: true` (EDITION/PATCH d'une ligne existante) : champs requis vides
 *    envoyes en `''` -> 422 NotBlank (sinon une cle omise laisse la valeur serveur
 *    inchangee, faux 200 — cf. blankEmptyRequired).
 */
export interface BuildPayloadOptions {
    forUpdate?: boolean
}

/** Selectionne le finaliseur des champs requis selon création (omit) vs édition (blank). */
function finalizeRequired<T extends Record<string, unknown>>(
    payload: T,
    requiredKeys: readonly string[],
    options: BuildPayloadOptions,
): T {
    return options.forUpdate
        ? blankEmptyRequired(payload, requiredKeys)
        : omitEmptyRequired(payload, requiredKeys)
}

/**
 * Payload du bloc principal — groupe client:write:main UNIQUEMENT. La relation
 * Distributeur/Courtier est mutuellement exclusive (RG-1.03) : on ne renseigne
 * que la FK correspondant au type choisi, l'autre est forcee a null.
 */
export function buildMainPayload(main: MainFormDraft, options: BuildPayloadOptions = {}): Record<string, unknown> {
    // companyName omis si vide -> 422 NotBlank au lieu d'un 400 de type (ERP-119).
    // relationType : champ transitoire (non persiste cote back) qui porte
    // l'intention UI « ce client depend d'un distributeur / courtier ». Il sert
    // a la validation croisee serveur (RG-1.03 bis) : si une relation est choisie,
    // la FK correspondante devient obligatoire -> 422 sur distributor / broker.
    // Sans equivalent derivable cote back (FK nullable), c'est la seule facon de
    // rester sur « on soumet, le back tranche » plutot qu'une garde front-only.
    return finalizeRequired({
        companyName: main.companyName,
        categories: main.categoryIris,
        relationType: main.relationType,
        distributor: main.relationType === 'distributeur' ? main.distributorIri : null,
        broker: main.relationType === 'courtier' ? main.brokerIri : null,
        triageService: main.triageService,
    }, MAIN_REQUIRED_NON_NULLABLE_KEYS, options)
}

/** Payload de l'onglet Information — groupe client:write:information UNIQUEMENT. */
export function buildInformationPayload(information: InformationFormDraft): Record<string, unknown> {
    return {
        description: information.description || null,
        competitors: information.competitors || null,
        // Saisie invalide (foundedAtRaw) prioritaire : on l'envoie telle quelle
        // pour que le back renvoie une 422 sur foundedAt (cf. foundedAtRaw).
        foundedAt: information.foundedAtRaw || information.foundedAt || null,
        employeesCount: information.employeesCount ? Number(information.employeesCount) : null,
        revenueAmount: information.revenueAmount || null,
        profitAmount: information.profitAmount || null,
        directorName: information.directorName || null,
    }
}

/**
 * Payload des scalaires de l'onglet Comptabilite — groupe client:write:accounting
 * UNIQUEMENT (les RIB passent par la sous-ressource /clients/{id}/ribs). La banque
 * n'a de sens que pour un Virement (RG-1.12) : forcee a null sinon.
 */
export function buildAccountingPayload(
    accounting: AccountingFormDraft,
    isBankRequired: boolean,
): Record<string, unknown> {
    return {
        siren: accounting.siren || null,
        accountNumber: accounting.accountNumber || null,
        tvaMode: accounting.tvaModeIri,
        nTva: accounting.nTva || null,
        paymentDelay: accounting.paymentDelayIri,
        paymentType: accounting.paymentTypeIri,
        bank: isBankRequired ? accounting.bankIri : null,
    }
}

/** Payload d'un contact (sous-ressource client_contact). */
export function buildContactPayload(contact: ContactFormDraft): Record<string, unknown> {
    return {
        firstName: contact.firstName || null,
        lastName: contact.lastName || null,
        jobTitle: contact.jobTitle || null,
        phonePrimary: contact.phonePrimary || null,
        phoneSecondary: contact.hasSecondaryPhone ? (contact.phoneSecondary || null) : null,
        email: contact.email || null,
    }
}

/** Payload d'une adresse (sous-ressource client_address). */
export function buildAddressPayload(
    address: AddressFormDraft,
    isBillingEmailRequired: boolean,
    options: BuildPayloadOptions = {},
): Record<string, unknown> {
    // postalCode / city / street : omis a la creation, `''` en edition -> 422 NotBlank (ERP-119).
    return finalizeRequired({
        isProspect: address.isProspect,
        isDelivery: address.isDelivery,
        isBilling: address.isBilling,
        isBroker: address.isBroker,
        isDistributor: address.isDistributor,
        country: address.country,
        postalCode: address.postalCode || null,
        city: address.city || null,
        street: address.street || null,
        streetComplement: address.streetComplement || null,
        categories: address.categoryIris,
        sites: address.siteIris,
        contacts: address.contactIris,
        billingEmail: isBillingEmailRequired ? (address.billingEmail || null) : null,
        billingEmailSecondary: isBillingEmailRequired && address.hasSecondaryBillingEmail ? (address.billingEmailSecondary || null) : null,
    }, ADDRESS_REQUIRED_NON_NULLABLE_KEYS, options)
}

/** Payload d'un RIB (sous-ressource client_rib). */
export function buildRibPayload(rib: RibFormDraft, options: BuildPayloadOptions = {}): Record<string, unknown> {
    // label / bic / iban : omis a la creation, `''` en edition -> 422 NotBlank au lieu
    // d'un 400 de type (ou d'un faux 200 PATCH qui garderait l'ancienne valeur). ERP-119.
    return finalizeRequired({
        label: rib.label,
        bic: rib.bic,
        iban: rib.iban,
    }, RIB_REQUIRED_NON_NULLABLE_KEYS, options)
}

// ── Gating par permission ────────────────────────────────────────────────────

/**
 * Resout l'editabilite par zone a partir des permissions (option 1 ERP-74,
 * miroir UI du re-gating champ-par-champ du ClientProcessor) :
 *  - bloc principal + Information/Contact/Adresse : editables ssi `manage` ;
 *  - Comptabilite : visible ssi `accounting.view`, editable ssi `accounting.manage`.
 *
 * Produit le comportement attendu :
 *  - Admin : tout editable.
 *  - Bureau / Commerciale (manage, sans accounting) : metier editable, Compta masquee.
 *  - Compta (accounting seul, sans manage) : metier readonly, Compta editable.
 */
export function resolveTabEditability(abilities: ClientEditAbilities): TabEditability {
    return {
        businessEditable: abilities.canManage,
        accountingVisible: abilities.canAccountingView,
        accountingEditable: abilities.canAccountingManage,
    }
}
