// Cache de consultation hors-ligne (CLAUDE.md — « Stratégie hors-ligne »).
//
// Rôle unique de ce fichier : ranger et relire des instantanés JSON dans IndexedDB, et dire à
// C# quand le navigateur bascule en ligne / hors ligne. AUCUNE logique métier ici.
//
// Pourquoi IndexedDB et pas le cache du service worker : c'est la seule option qui permette de
// rechercher et trier hors-ligne sur TOUTE la bibliothèque. Un cache de réponses HTTP ne
// restituerait que les URL déjà visitées — une recherche jamais tapée ne rendrait rien.
//
// Les valeurs sont stockées telles quelles, en chaîne JSON : c'est System.Text.Json côté C# qui
// sérialise et désérialise, donc une seule forme fait autorité et le clone structuré d'IndexedDB
// n'a rien à interpréter.

const NOM_BASE = 'mabibli';
const MAGASIN = 'instantanes';
const VERSION = 1;

let promesseBase = null;

function ouvrir() {
    if (promesseBase) return promesseBase;

    promesseBase = new Promise((resoudre, rejeter) => {
        // IndexedDB peut être absent ou refusé (navigation privée stricte, stockage bloqué).
        // On rejette proprement : l'appelant C# retombe alors sur « pas de cache ».
        if (!self.indexedDB) { rejeter(new Error('IndexedDB indisponible')); return; }

        const requete = indexedDB.open(NOM_BASE, VERSION);
        requete.onupgradeneeded = () => {
            const base = requete.result;
            if (!base.objectStoreNames.contains(MAGASIN)) base.createObjectStore(MAGASIN);
        };
        requete.onsuccess = () => resoudre(requete.result);
        requete.onerror = () => rejeter(requete.error);
        requete.onblocked = () => rejeter(new Error('IndexedDB bloqué'));
    }).catch(e => { promesseBase = null; throw e; });

    return promesseBase;
}

function attendre(requete, transaction) {
    return new Promise((resoudre, rejeter) => {
        requete.onsuccess = () => resoudre(requete.result);
        requete.onerror = () => rejeter(requete.error);
        if (transaction) transaction.onabort = () => rejeter(transaction.error);
    });
}

// Écrit un instantané. `dateIso` est l'instant de la synchronisation, pas celui de l'écriture :
// c'est cette date que l'interface affiche pour dire de quand datent les données montrées.
export async function ecrire(cle, json, dateIso) {
    const base = await ouvrir();
    const tx = base.transaction(MAGASIN, 'readwrite');
    const requete = tx.objectStore(MAGASIN).put({ json, date: dateIso }, cle);
    await attendre(requete, tx);
    return true;
}

// Renvoie { json, date } ou null si rien n'a jamais été rangé sous cette clé.
export async function lire(cle) {
    const base = await ouvrir();
    const tx = base.transaction(MAGASIN, 'readonly');
    const valeur = await attendre(tx.objectStore(MAGASIN).get(cle), tx);
    return valeur ?? null;
}

export async function vider() {
    const base = await ouvrir();
    const tx = base.transaction(MAGASIN, 'readwrite');
    await attendre(tx.objectStore(MAGASIN).clear(), tx);
    return true;
}

// navigator.onLine ne vaut que par sa négation : « false » est fiable (aucune interface réseau),
// « true » ne prouve rien (portail captif, serveur éteint). C# complète donc cet état avec le
// résultat réel de ses appels HTTP — voir EtatReseau.SignalerEchecReseau.
export function enLigne() {
    return navigator.onLine !== false;
}

export function surveiller(reference) {
    const prevenir = () => reference.invokeMethodAsync('SurChangementReseau', navigator.onLine !== false);
    self.addEventListener('online', prevenir);
    self.addEventListener('offline', prevenir);
    return navigator.onLine !== false;
}
