import { apiFetch } from '@/lib/loading/api-fetch';

/**
 * Appels JSON de l'import du fichier des paiements.
 *
 * Tous passent par `apiFetch` : la barre de chargement du haut s'allume, et le
 * jeton CSRF part sur chaque POST (un 419 silencieux a déjà coûté une journée,
 * voir l'en-tête de `api-fetch.ts`). Aucun voile ni flou sur l'écran.
 *
 * Un appel ne LÈVE jamais : il rend `{ ok: false, statut, message }`, et
 * l'écran dit ce qui a échoué avec `raisonEchec()` — jamais un message
 * générique qui accuserait le fichier quand c'est le réseau qui manque.
 */

export type ReponseApi<T> = { ok: true; donnees: T } | EchecApi;

export interface EchecApi {
    ok: false;
    /** `null` : le serveur n'a pas répondu du tout. */
    statut: number | null;
    /** Le `message` du corps JSON, s'il y en a un. */
    message: string | null;
    corps: unknown;
}

/** Le `message` lisible d'un corps JSON — `null` s'il n'y en a pas. */
export function messageDuCorps(corps: unknown): string | null {
    if (corps !== null && typeof corps === 'object' && 'message' in corps) {
        const message = (corps as { message: unknown }).message;
        if (typeof message === 'string' && message.trim() !== '') return message;
    }

    return null;
}

/**
 * L'URL d'une route nommée — `null` si elle n'existe pas (encore) côté
 * serveur : Ziggy LÈVE sur un nom inconnu, et une exception au clic ne doit
 * pas casser l'écran.
 */
export function urlRoute(nom: string, params?: Record<string, string | number>): string | null {
    try {
        return route(nom, params);
    } catch {
        return null;
    }
}

export async function appelJson<T>(url: string | null, options: { methode?: 'GET' | 'POST'; corps?: unknown } = {}): Promise<ReponseApi<T>> {
    if (url === null) return { ok: false, statut: 404, message: null, corps: null };

    const methode = options.methode ?? 'GET';
    const entetes: Record<string, string> = { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' };
    if (options.corps !== undefined) entetes['Content-Type'] = 'application/json';

    let reponse: Response;
    try {
        reponse = await apiFetch(url, {
            method: methode,
            headers: entetes,
            credentials: 'same-origin',
            body: options.corps !== undefined ? JSON.stringify(options.corps) : undefined,
        });
    } catch {
        return { ok: false, statut: null, message: null, corps: null };
    }

    // Un corps non-JSON (page d'erreur, session expirée) ne doit jamais casser l'écran.
    const corps: unknown = await reponse.json().catch(() => null);

    if (!reponse.ok || corps === null) {
        return { ok: false, statut: reponse.status, message: messageDuCorps(corps), corps };
    }

    return { ok: true, donnees: corps as T };
}

type Traduire = (namespace: string, key: string, fallback?: string) => string;

/**
 * Ce qui a échoué, dans les mots de l'utilisateur.
 *
 * Le message du serveur est préféré quand il est rédigé pour l'écran (403,
 * 422 — le contrat les promet en français). Pour 401/419/429/5xx, c'est le
 * cadre qui répond, souvent en anglais technique : l'écran dit alors la chose
 * lui-même.
 */
export function raisonEchec(echec: Pick<EchecApi, 'statut' | 'message'>, t: Traduire): string {
    const { statut, message } = echec;

    if (statut === null) {
        return t('retenue', 'import_err_reseau', 'Le serveur n’a pas répondu. Vérifiez la connexion puis réessayez — rien n’a été modifié.');
    }
    if (statut === 401 || statut === 419) {
        return t('retenue', 'import_err_session', 'Votre session a expiré : rechargez la page puis recommencez.');
    }
    if (statut === 429) {
        return t('retenue', 'import_err_limite', 'Trop de lectures du fichier en peu de temps. Patientez une minute, puis réessayez.');
    }
    if ((statut === 403 || statut === 422) && message) return message;
    if (statut === 403) {
        return t('retenue', 'import_err_droit', 'Votre compte n’a pas le droit de faire cette opération.');
    }
    if (statut === 404) {
        return t('retenue', 'import_err_absent', 'Cette fonction n’est pas encore disponible sur ce serveur.');
    }
    if (message && statut < 500) return message;

    return t('retenue', 'import_err_serveur', 'Le serveur a refusé la demande (HTTP :statut).').replace(':statut', String(statut));
}

/** Remplace `:nom` par sa valeur — seulement les noms fournis. */
export function remplir(texte: string, params: Record<string, string | number>): string {
    return texte.replace(/:([a-z_]+)/g, (tout, nom: string) => (nom in params ? String(params[nom]) : tout));
}
