/**
 * LoadingCore — machine à états anti-clignotement pour l'indicateur d'activité réseau.
 *
 * Logique pure (aucune dépendance React/DOM) pour être testable en environnement
 * `node` via Vitest. Garantit :
 *  - délai avant affichage : les appels rapides ne font jamais flasher la barre ;
 *  - durée minimale visible : une fois affichée, la barre ne clignote pas ;
 *  - comptage de tâches concurrentes : la barre ne se masque qu'au retour à 0.
 *
 * Non bloquant par nature : au pire la barre reste visible un peu trop longtemps
 * (cosmétique), jamais de gel de l'UI.
 */
export interface LoadingCoreOptions {
    /** Délai avant d'afficher la barre, en ms (défaut 200). */
    showDelay?: number;
    /** Durée minimale d'affichage une fois visible, en ms (défaut 400). */
    minVisible?: number;
    /** Horloge injectable (tests). */
    now?: () => number;
}

export class LoadingCore {
    private count = 0;
    private visible = false;
    private shownAt = 0;
    private showTimer: ReturnType<typeof setTimeout> | null = null;
    private hideTimer: ReturnType<typeof setTimeout> | null = null;

    constructor(
        private readonly emit: (visible: boolean) => void,
        private readonly options: LoadingCoreOptions = {},
    ) {}

    private get showDelay(): number {
        return this.options.showDelay ?? 200;
    }

    private get minVisible(): number {
        return this.options.minVisible ?? 400;
    }

    private now(): number {
        return (this.options.now ?? Date.now)();
    }

    /** Nombre de tâches actuellement actives. */
    get activeCount(): number {
        return this.count;
    }

    /** La barre est-elle actuellement visible. */
    get isVisible(): boolean {
        return this.visible;
    }

    /** Une tâche démarre. */
    start(): void {
        this.count += 1;

        // Un nouveau travail annule tout masquage programmé.
        if (this.hideTimer) {
            clearTimeout(this.hideTimer);
            this.hideTimer = null;
        }

        // Première tâche : on programme l'affichage après le délai.
        if (this.count === 1 && !this.visible && this.showTimer === null) {
            this.showTimer = setTimeout(() => {
                this.showTimer = null;
                if (this.count > 0) {
                    this.visible = true;
                    this.shownAt = this.now();
                    this.emit(true);
                }
            }, this.showDelay);
        }
    }

    /** Une tâche se termine (succès, erreur ou annulation). */
    done(): void {
        this.count = Math.max(0, this.count - 1);
        if (this.count > 0) return;

        // Jamais affichée → on annule l'affichage en attente, pas de flash.
        if (this.showTimer !== null) {
            clearTimeout(this.showTimer);
            this.showTimer = null;
            return;
        }

        if (!this.visible) return;

        // Affichée : respecter la durée minimale visible.
        const remaining = Math.max(0, this.minVisible - (this.now() - this.shownAt));
        if (this.hideTimer) clearTimeout(this.hideTimer);
        this.hideTimer = setTimeout(() => {
            this.hideTimer = null;
            if (this.count === 0) {
                this.visible = false;
                this.emit(false);
            }
        }, remaining);
    }

    /** Vide tout immédiatement (erreur globale, annulation). */
    reset(): void {
        this.count = 0;
        if (this.showTimer) {
            clearTimeout(this.showTimer);
            this.showTimer = null;
        }
        if (this.hideTimer) {
            clearTimeout(this.hideTimer);
            this.hideTimer = null;
        }
        if (this.visible) {
            this.visible = false;
            this.emit(false);
        }
    }
}
