/**
 * Projet                    : MageStock
 * Logiciel                  : MageStock
 * Rôle du fichier           : Service Worker PWA, cache statique et notifications
 * Version générale          : 2.6.124
 * Lot                       : MAGESTOCK UPDATE CENTER INSTALLER PROXY RESPONSIVE RELEASE FINAL1
 * Base cumulative requise   : MageStock 2.6.123
 * Date de création          : 2026-02-21
 * Date de modification      : 2026-07-30
 * Dépendances               : API Service Worker des navigateurs modernes
 * Données persistantes      : cache navigateur uniquement, jamais les API métier
 * Permissions requises      : contexte sécurisé HTTPS
 * Contraintes de sécurité   : aucun cache des réponses métier, portée portable
 * Compatibilité             : navigateurs PWA modernes
 * Résumé des modifications  : invalidation du cache pour le footer MageKorp, le centre UPDATE et l’installateur 2.6.124.
 *

 * Fichier  : sw.js
 * Auteur   : KREMER Régis
 * Version  : 2.6.122
 * Date     : 2026-07-22
 * Description : Service Worker — Cache offline et gestion des push notifications
 *
 * Changelog :
 * - 2.6.124 — cache du footer MageKorp et de la refonte complète du centre de mise à jour.
 * - 2.6.123 — cache du correctif ouverture FT/FS et clavier produit mobile.
 *   v2.6.111 (2026-07-22) — Cache invalidé pour la surcharge locale des autorisations LDAP.
 *   v2.6.112 (2026-07-22) — Cache invalidé et ressources du lecteur documentaire ajoutées.
 *   v2.6.113 (2026-07-22) — Cache invalidé pour le rôle Magasinier RBAC.
 *   v2.6.114 (2026-07-22) — Cache invalidé après correction du routage Documentation.
 *   v2.6.115 (2026-07-22) — Cache invalidé après enrichissement complet de la documentation.
 *   v2.6.116 (2026-07-25) — Cache invalidé après consolidation de la gestion des magasins.
 *   v2.6.117 (2026-07-25) — Cache invalidé pour le moteur UPDATE cumulatif et son interface.
 *   v2.6.118 (2026-07-25) — Cache invalidé pour la découverte distante signée.
 *   v2.6.119 (2026-07-25) — Cache invalidé pour la passerelle d’authentification ; pages de secrets exclues du cache.
 *   v2.6.120 (2026-07-25) — Cache de consolidation finale et distribution publique alignée.
 *   v2.6.121 (2026-07-26) — Cache invalidé pour l’identité MageStock des passerelles publiques.
 *   v2.6.122 (2026-07-28) — Cache invalidé pour les actions FT/FDS et le parcours Produit mobile.
 *   v2.6.102 (2026-07-21) — Cache invalidé pour authentification LDAP/Active Directory sécurisée.
 *   v2.6.101 (2026-07-20) — Cache invalidé pour correctif Produits, Commandes, Pilotage, Magasins et Aide Plans.
 *   v2.1.0 (2026-02-21) — Version initiale auditée et sécurisée
 *   v2.3.25 (2026-06-09) — Cache invalidé pour le correctif fiabilité produits.
 *   v2.3.26 (2026-06-09) — Cache invalidé pour la refonte UX produit.
 *   v2.3.27 (2026-06-09) — Cache invalidé pour les actions accessibles des cartes produits.
 *   v2.6.6 (2026-07-08) — Cache PWA MageStock, manifest et icônes installables.
 *   v2.6.7 (2026-07-08) — Recette finale et invalidation cache de clôture.
 *   v2.6.8 (2026-07-08) — Cache invalidé pour PWA installable, print-template et correctif menu.
 *   v2.6.9 (2026-07-08) — Cache invalidé pour correctif cartes produits et filtres produits.
 *   v2.6.10 (2026-07-08) — Portabilité APP_BASE_PATH : scope dynamique, assets relatifs, pas de cache API.
 *   v2.6.13 (2026-07-08) — Cache PWA invalidé et ajout favicons/icônes installables.
 *   v2.6.15 (2026-07-08) — Cache invalidé pour la trame d’impression premium commune.
 *   v2.6.16 (2026-07-08) — Cache invalidé pour l’impression inventaire avec images produit.
 *   v2.6.17 (2026-07-08) — Cache invalidé pour correctif impressions, images filtrées et logo actif.
 *   v2.6.18 (2026-07-08) — Cache invalidé pour proxy images et correctif PDF/preview logo.
 *   v2.6.19 (2026-07-08) — Cache invalidé pour impression logo/marges et paramètres logo.
 *   v2.6.21 (2026-07-08) — Cache invalidé pour UX sélection mouvements.
 *   v2.6.22 (2026-07-08) — Cache invalidé pour UX inventaires et marges impressions métier.
 *   v2.6.23 (2026-07-08) — Cache invalidé pour impression mouvements et BL.
 *   v2.6.24 (2026-07-08) — Cache invalidé pour cohérence finale.
 *   v2.6.25 (2026-07-09) — Cache invalidé pour le chantier plans d’emplacements PLAN 1.
 *   v2.6.26 (2026-07-09) — Cache invalidé pour l’éditeur visuel de base des plans PLAN 2.
 *   v2.6.27 (2026-07-09) — Cache invalidé pour la bibliothèque de composants des plans PLAN 3.
 *   v2.6.29 (2026-07-09) — Cache invalidé pour la localisation produit vers les plans PLAN 5.
 *   v2.6.31 (2026-07-09) — Cache invalidé pour correctif route /plans et bouton produit compact.
 *   v2.6.32 (2026-07-09) — Cache invalidé pour refonte UX et accessibilité de la page Plans.
 *   v2.6.33 (2026-07-09) — Cache invalidé pour page édition plans dédiée et drag & drop composants.
 *   v2.6.34 (2026-07-09) — Cache invalidé pour création de plan dédiée et corrections UX plans.
 *   v2.6.20 (2026-07-08) — Cache invalidé pour sidebar branding logo entreprise.
 *   v2.6.40 (2026-07-09) — Cache invalidé pour PLANS DESIGN1, objets premium et localisation plan fini.
 *   v2.6.41 (2026-07-09) — Cache invalidé pour PLANS EDITOR2, précision d’édition et pan/zoom.
 *   v2.6.42 (2026-07-09) — Cache invalidé pour PLANS LOCALISATION2, impression et plan fini produit.
 *   v2.6.43 (2026-07-09) — Cache invalidé pour PLANS PRINT3, export PDF premium et impression localisation.
 *   v2.6.44 (2026-07-09) — Cache invalidé pour PLANS ARCHITECTE1, éditeur plan 2D murs/ouvertures.
 *   v2.6.45 (2026-07-09) — Cache invalidé pour PLANS WALLS2, murs par points et ouvertures accrochées.
 *   v2.6.70 (2026-07-11) — I18N CORE LOCALE CURRENCY1 : cache invalidé pour le runtime locale/devise.
 *   v2.6.72 (2026-07-11) — I18N MODULES COMPLETE ROLLBACK1 : purge du cache contenant le moteur v2.6.71 bloquant.
 *   v2.6.73 (2026-07-11) — I18N MODULES SAFE ARCHI1 : cache du socle explicite connexion/shell/tableau de bord.
 *   v2.6.74 (2026-07-17) — INVENTORY MOBILE FLOW1 : invalidation du cache après refonte Inventaires.
 *   v2.6.75 (2026-07-17) — INVENTORY RESUME REVIEW2 : cache invalidé pour brouillons persistants et revue des écarts.
 *   v2.6.76 (2026-07-17) — INVENTORY FIELD RECETTE3 : cache invalidé après recette terrain, historique par magasin et autosauvegarde PWA renforcée.
 *   v2.6.77 (2026-07-17) — INVENTORY FIELD DEVICE VALIDATION4 : cache invalidé pour clavier mobile, rotation et cycle caméra PWA.
 *   v2.6.78 (2026-07-17) — MOBILE FLOW FOUNDATION2 : cache invalidé et socle mobile commun ajouté.
 *   v2.6.79 (2026-07-17) — MOVEMENTS TERRAIN FLOW3 : cache invalidé pour les parcours et cartes Mouvements.
 *   v2.6.80 (2026-07-17) — ORDERS MOBILE FLOW4 : cache invalidé pour assistant, brouillons et réception terrain Commandes.
 *   v2.6.81 (2026-07-17) — PRODUCTS MOBILE FLOW5 : cache invalidé pour assistant, cartes et fiche Produits.
 *   v2.6.82 (2026-07-17) — PLANS MOBILE POLICY6 : cache invalidé pour création guidée et politique Plans par appareil.
 *   v2.6.83 (2026-07-20) — BARCODES WIZARD7 : cache invalidé pour assistant tactile et aperçu Codes-barres.
 *   v2.6.84 (2026-07-20) — ADMIN COMPLEX FORMS8 : cache invalidé pour les interfaces administration adaptatives.
 *   v2.6.85 (2026-07-20) — Cache invalidé pour REPORTS MOBILE CARDS9.
 *   v2.6.86 (2026-07-20) — Cache invalidé pour CRUD MOBILE CARDS10.
 *   v2.6.87 (2026-07-20) — MOBILE FULL FIELD RECETTE11 : cache invalidé après recette cumulative et finitions accessibles.
 *   v2.6.88 (2026-07-20) — SCANNER CORE UNIFICATION1 : cache invalidé pour le noyau scanner unique.
 *   v2.6.89 (2026-07-20) — SCANNER MOBILE EXPERIENCE2 : cache invalidé pour la nouvelle page scanner tactile.
 *   v2.6.90 (2026-07-20) — SCANNER CONSUMERS MIGRATION3 : cache invalidé après migration des consommateurs métier.
 *   v2.6.91 (2026-07-20) — SCANNER FULL FIELD RECETTE4 : cache invalidé après recette finale et ajustements UX.
 *   v2.6.92 (2026-07-20) — SCANNER UX POLISH OPTIONAL1 : cache invalidé après harmonisation scanner premium.
 *   v2.6.93 (2026-07-20) — SCANNER UX POLISH FIX2 : cache invalidé après restauration d’Attente.
 *   v2.6.96 (2026-07-20) — SCANNER RESULT FLOW FIX4 : cache invalidé pour le cycle résultat indépendant.
 *   v2.6.97 (2026-07-20) — Cache invalidé pour SCANNER OVERLAY CONSUMER FIX5.
 *   v2.6.98 (2026-07-20) — Cache invalidé pour SCANNER TERRAIN CONFIRMATION1.
 *   v2.6.99 (2026-07-20) — Distribution vierge : nouveau cache et retrait temporaire de références PWA vers des fichiers Plans mobiles absents du ZIP source.
 *   v2.6.100 (2026-07-20) — MAGESTOCK DISTRIBUTION PHYSICAL RELEASE RECETTE1 : restauration des assets Plans mobiles et cache de release.
 */
// ================================================================
// sw.js — Service Worker pour MageStock
// Le scope est calculé depuis self.registration.scope : compatible
// /sm/, /ms/, /magestock/ et racine de sous-domaine.
// ================================================================

const CACHE_NAME = 'magestock-v2.6.124-update-center-installer-proxy-responsive-release-final1';
const SCOPE_URL = new URL(self.registration.scope || './', self.location.href);
const SCOPE_PATH = SCOPE_URL.pathname.replace(/\/$/, '/');

function scopedUrl(path) {
    return new URL(String(path || '').replace(/^\/+/, ''), SCOPE_URL).toString();
}

function scopedPath(path) {
    return new URL(String(path || '').replace(/^\/+/, ''), SCOPE_URL).pathname;
}

const STATIC_ASSETS = [
    './',
    'index.php',
    'assets/css/app.css',
    'assets/css/auth-gateway.css',
    'assets/css/refonte.css',
    'assets/css/mobile-flow.css',
    'assets/css/scanner-consumer.css',
    'assets/css/movements-terrain.css',
    'assets/css/orders-mobile.css',
    'assets/css/products-mobile.css',
    'assets/css/plans.css',
    'assets/css/plans-mobile.css',
    'assets/css/barcodes-mobile.css',
    'assets/css/admin-mobile.css',
    'assets/css/ldap.css',
    'assets/css/update.css',
    'assets/css/reports-mobile.css',
    'assets/css/crud-mobile.css',
    'assets/css/mobile-recette.css',
    'assets/js/app.js',
    'assets/js/auth-gateway.js',
    'assets/js/password-recovery.js',
    'assets/js/api.js',
    'assets/js/pages/settings.js',
    'assets/js/pages/ldap-settings.js',
    'assets/js/pages/update.js',
    'assets/js/pages/users.js',
    'assets/js/mobile-flow.js',
    'assets/js/scanner/ScannerConsumer.js',
    'assets/js/mobile-recette.js',
    'assets/js/scanner/ScannerService.js',
    'assets/js/pages/inventory.js',
    'assets/js/pages/movements.js',
    'assets/js/pages/movements-terrain.js',
    'assets/js/pages/orders-mobile.js',
    'assets/js/pages/products-mobile.js',
    'assets/js/pages/plans.js',
    'assets/js/pages/plans-mobile.js',
    'assets/js/pages/barcodes-mobile.js',
    'assets/js/pages/admin-mobile.js',
    'assets/js/pages/reports-mobile.js',
    'assets/js/pages/crud-mobile.js',
    'assets/js/pages/warehouse-manager.js',
    'assets/js/pwa-install.js',
    'assets/js/print-template.js',
    'assets/css/print-template.css',
    'app.webmanifest',
    'favicon.ico',
    'assets/img/favicon.svg',
    'assets/img/magekorp-logo-horizontal.png',
    'assets/img/magekorp-mark.png',
    'assets/img/favicon-16.png',
    'assets/img/favicon-32.png',
    'assets/img/apple-touch-icon.png',
    'assets/img/icon-180.png',
    'assets/img/icon-192.png',
    'assets/img/icon-512.png',
    'assets/img/badge-72.png'
].map(scopedUrl);

self.addEventListener('install', (event) => {
    console.log('[SW] Installation...', SCOPE_PATH);
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => cache.addAll(STATIC_ASSETS).catch(err => {
                console.warn('[SW] Certains assets non mis en cache:', err);
            }))
            .then(() => self.skipWaiting())
    );
});

self.addEventListener('activate', (event) => {
    console.log('[SW] Activation...', SCOPE_PATH);
    event.waitUntil(
        caches.keys()
            .then(keys => Promise.all(
                keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))
            ))
            .then(() => self.clients.claim())
    );
});

self.addEventListener('fetch', (event) => {
    const reqUrl = new URL(event.request.url);

    // Ignorer les requêtes non GET, hors origine, hors scope ou API stock.
    if (event.request.method !== 'GET') return;
    if (reqUrl.origin !== self.location.origin) return;
    if (!reqUrl.pathname.startsWith(SCOPE_PATH)) return;
    if (reqUrl.pathname.includes('/api/')) return;
    // Les pages de connexion, d’installation et de récupération peuvent contenir des états
    // de session ou des messages sensibles : elles restent toujours réseau et ne sont jamais mises en cache.
    if (/\/(?:login|login\.php|install|install\.php|password-recovery|password-recovery\.php)\/?$/.test(reqUrl.pathname)) return;

    event.respondWith(
        fetch(event.request)
            .then(response => {
                if (response && response.status === 200 && response.type === 'basic') {
                    const clone = response.clone();
                    caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
                }
                return response;
            })
            .catch(() => caches.match(event.request))
    );
});

self.addEventListener('push', (event) => {
    console.log('[SW] Push reçu:', event);

    let data = {
        title: 'MageStock',
        body: 'Nouvelle notification',
        icon: scopedPath('assets/img/icon-192.png'),
        badge: scopedPath('assets/img/badge-72.png'),
        tag: 'magestock-notification',
        requireInteraction: false,
        data: { url: scopedPath('') }
    };

    if (event.data) {
        try {
            const payload = event.data.json();
            data = { ...data, ...payload };
        } catch (e) {
            data.body = event.data.text();
        }
    }

    const iconMap = {
        'stock_out': scopedPath('assets/img/notif-out.png'),
        'stock_in': scopedPath('assets/img/notif-in.png'),
        'alert': scopedPath('assets/img/notif-alert.png'),
        'inventory': scopedPath('assets/img/notif-inventory.png')
    };

    if (data.type && iconMap[data.type]) data.icon = iconMap[data.type];

    const actions = [];
    if (data.type === 'alert') {
        actions.push({ action: 'view', title: 'Voir les alertes' }, { action: 'dismiss', title: 'Ignorer' });
    } else if (data.type === 'stock_out' || data.type === 'stock_in') {
        actions.push({ action: 'view', title: 'Voir le mouvement' });
    }

    const options = {
        body: data.body,
        icon: data.icon || scopedPath('assets/img/icon-192.png'),
        badge: data.badge || scopedPath('assets/img/badge-72.png'),
        tag: data.tag || 'magestock-' + Date.now(),
        requireInteraction: data.requireInteraction || false,
        vibrate: [200, 100, 200],
        data: data.data || { url: scopedPath('') },
        actions: actions,
        timestamp: Date.now()
    };

    event.waitUntil(self.registration.showNotification(data.title, options));
});

self.addEventListener('notificationclick', (event) => {
    console.log('[SW] Notification cliquée:', event);
    event.notification.close();

    let url = scopedPath('');
    if (event.action === 'view') {
        const data = event.notification.data || {};
        url = data.url || scopedPath('');
    } else if (event.action === 'dismiss') {
        return;
    } else if (event.notification.data && event.notification.data.url) {
        url = event.notification.data.url;
    }

    const targetUrl = new URL(url, SCOPE_URL).toString();

    event.waitUntil(
        clients.matchAll({ type: 'window', includeUncontrolled: true })
            .then(clientList => {
                for (const client of clientList) {
                    if (client.url.startsWith(SCOPE_URL.href) && 'focus' in client) {
                        client.navigate(targetUrl);
                        return client.focus();
                    }
                }
                if (clients.openWindow) return clients.openWindow(targetUrl);
            })
    );
});

self.addEventListener('notificationclose', (event) => {
    console.log('[SW] Notification fermée:', event.notification.tag);
});

self.addEventListener('message', (event) => {
    console.log('[SW] Message reçu:', event.data);
    if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting();
});
