// 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';
// Couvertures mises en cache (A5, IDEES.md) : magasin séparé, clé = l'URL OpenLibrary,
// valeur = le Blob de l'image. Sert uniquement à la consultation hors-ligne — en ligne,
// l'<img> normale reste la voie rapide, ce cache se remplit en tâche de fond à côté.
const MAGASIN_COUVERTURES = 'couvertures';
const VERSION = 2;

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);
            if (!base.objectStoreNames.contains(MAGASIN_COUVERTURES)) base.createObjectStore(MAGASIN_COUVERTURES);
        };
        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;
}

// --- Couvertures (A5, IDEES.md) ---
//
// En ligne, l'<img> normale reste la voie d'affichage : rien ici ne doit la ralentir. Cette
// fonction tourne en tâche de fond, appelée sans attendre son résultat (fire-and-forget) après
// qu'une couverture s'est affichée avec succès. Le second fetch profite en général du cache HTTP
// du navigateur (même URL que l'<img>) : pas de second téléchargement réel dans le cas courant.
//
// OpenLibrary envoie `Access-Control-Allow-Origin: *` (vérifié) : un fetch cross-origin normal
// suffit, pas besoin du contournement `no-cors`/réponse opaque.
export async function couvertureMettreEnCache(url) {
    try {
        const base = await ouvrir();
        const tx = base.transaction(MAGASIN_COUVERTURES, 'readonly');
        const existe = await attendre(tx.objectStore(MAGASIN_COUVERTURES).getKey(url), tx);
        if (existe !== undefined) return; // Déjà en cache : pas de re-téléchargement.

        const reponse = await fetch(url, { cache: 'force-cache' });
        if (!reponse.ok) return; // 404/502 intermittent (CLAUDE.md) : rien à ranger.

        const blob = await reponse.blob();
        const ecriture = base.transaction(MAGASIN_COUVERTURES, 'readwrite');
        await attendre(ecriture.objectStore(MAGASIN_COUVERTURES).put(blob, url), ecriture);
    } catch (e) {
        // Quota dépassé, stockage refusé : une couverture non mise en cache reste un
        // agrément perdu, jamais une raison de faire échouer l'affichage en ligne.
    }
}

// Renvoie une URL d'objet (`blob:`) utilisable comme `src`, ou `null` si cette couverture n'a
// jamais été mise en cache — c'est alors à l'appelant de retomber sur l'URL réseau (qui échouera
// simplement si hors-ligne, exactement comme aujourd'hui).
export async function couvertureLire(url) {
    try {
        const base = await ouvrir();
        const tx = base.transaction(MAGASIN_COUVERTURES, 'readonly');
        const blob = await attendre(tx.objectStore(MAGASIN_COUVERTURES).get(url), tx);
        return blob ? URL.createObjectURL(blob) : null;
    } catch (e) {
        return null;
    }
}
