/**
 * Les SEULS appels `fetch` du Yield Manager.
 *
 * Trois appelants (l'aperçu du slide-over, son amorçage, l'hydratation de la
 * grille) recopiaient chacun leur propre en-tête HTTP — dont la lecture de la
 * balise `<meta name="csrf-token">`, figée après le premier chargement et cause
 * documentée de 419 en production (errors.md, 2026-08-26). Deux d'entre eux
 * consommaient en plus le corps sans tester `response.ok`.
 *
 * Tout passe maintenant par ici : jeton CSRF centralisé (via `apiFetch`, qui
 * lit le cookie vivant) et `response.ok` vérifié AVANT de décoder le corps, de
 * sorte qu'un 422 revienne à l'appelant comme un échec décrit — jamais comme un
 * objet à moitié vide qui plante au rendu.
 */
import { apiFetch } from '@/lib/loading/api-fetch';
import { csrfHeaders } from '@/lib/csrf';
import { buildFailure, type ApiResult } from './api-errors';
import { coHostFromSearch, withCoHost } from './yield-scope';

export interface YieldRequestOptions {
    /**
     * `true` = l'appel N'alimente PAS la barre de chargement globale.
     *
     * Réservé à l'hydratation paresseuse de la grille (5 requêtes en rafale au
     * chargement de la page) : la convention maison réserve la barre aux appels
     * « foreground » et l'interdit au polling / re-fetch de fond, sous peine de
     * la faire clignoter en permanence.
     */
    background?: boolean;
}

/**
 * Relaie le périmètre de la page (`?coHost=`, compte principal consultant une
 * franchise) sur l'appel JSON : sans lui, le serveur répondrait pour le parc
 * du siège — la grille resterait vide et le slide-over prendrait un 403. Hors
 * navigateur (tests), l'URL est rendue telle quelle.
 */
function scoped(url: string): string {
    if (typeof window === 'undefined') return url;
    return withCoHost(url, coHostFromSearch(window.location.search));
}

/** Décode la réponse APRÈS avoir vérifié le statut. */
async function readResult<T>(response: Response): Promise<ApiResult<T>> {
    if (!response.ok) {
        // Un corps non-JSON (page d'erreur HTML, réponse vide) ne doit pas
        // masquer le vrai code HTTP : on retombe sur `null` et `buildFailure`
        // produit quand même un message français exploitable.
        const payload = await response.json().catch(() => null);
        return { ok: false, failure: buildFailure(response.status, payload) };
    }

    try {
        return { ok: true, data: (await response.json()) as T };
    } catch {
        return { ok: false, failure: buildFailure(response.status, null) };
    }
}

/** Échec réseau : la requête n'a jamais atteint le serveur. */
function networkFailure<T>(): ApiResult<T> {
    return { ok: false, failure: buildFailure(0, null) };
}

/** POST JSON → JSON. Ne lève jamais : l'échec revient dans le résultat. */
export async function postYieldJson<T>(url: string, body: unknown, options: YieldRequestOptions = {}): Promise<ApiResult<T>> {
    const init: RequestInit = {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            Accept: 'application/json',
            'X-Requested-With': 'XMLHttpRequest',
        },
        body: JSON.stringify(body),
    };

    try {
        const target = scoped(url);
        const response = options.background
            ? await fetch(target, { ...init, headers: { ...init.headers, ...csrfHeaders() } })
            : await apiFetch(target, init);
        return await readResult<T>(response);
    } catch {
        return networkFailure<T>();
    }
}

/** GET JSON. Ne lève jamais : l'échec revient dans le résultat. */
export async function getYieldJson<T>(url: string, options: YieldRequestOptions = {}): Promise<ApiResult<T>> {
    const init: RequestInit = {
        method: 'GET',
        headers: {
            Accept: 'application/json',
            'X-Requested-With': 'XMLHttpRequest',
        },
    };

    try {
        const target = scoped(url);
        const response = options.background ? await fetch(target, init) : await apiFetch(target, init);
        return await readResult<T>(response);
    } catch {
        return networkFailure<T>();
    }
}
