/**
 * Cyber-Assistant - Gestionnaire de synchronisation unifié
 *
 * Fusionne les fonctionnalités de save-manager.js et server-sync.js :
 * - Sauvegarde locale avec debounce
 * - Synchronisation serveur automatique
 * - Détection du mode PHP/Node.js
 * - Gestion des retry en cas d'erreur
 * - Indicateur visuel de synchronisation
 *
 * Dépend de: modules/storage.js (doit être chargé avant)
 */
(function() {
  'use strict';

  // Vérifier que storage.js est chargé
  if (!window.CSI_Storage) {
    void 0;
    return;
  }

  var Storage = window.CSI_Storage;

  // i18n helper — delegates to the global i18n module when available
  function _t(key, fallback) {
    return window.CSI_i18n && window.CSI_i18n.t ? window.CSI_i18n.t(key) : (fallback || key);
  }

  // ============================================================
  // Sync mutex & retry guards
  // ============================================================

  var _syncInProgress = false;
  var _retryCount = 0;
  var MAX_AUTH_RETRIES = 3;

  // ============================================================
  // Configuration
  // ============================================================

  var CONFIG = {
    DEBOUNCE_LOCAL_MS: 1000,      // Délai debounce sauvegarde locale
    DEBOUNCE_SYNC_MS: 2000,       // Délai avant sync serveur
    RETRY_DELAY_MS: 5000,         // Délai entre retries
    MAX_RETRIES: 2,               // Nombre max de retries
    SYNC_PHP_URL: './data/sync.php',
    EXPORT_PHP_URL: './data/sync.php'
  };

  // ============================================================
  // État interne
  // ============================================================

  var state = {
    // Timers
    localSaveTimeout: null,
    syncTimeout: null,

    // Flags
    localPending: false,
    syncPending: false,
    syncEnabled: true,

    // FIX-FULLSYNC-STALE: clés en attente pour la prochaine sync débouncée.
    // undefined = rien en attente ; null = sync complète demandée (remplacement
    // intentionnel, ex. import/restore) ; tableau = union des clés déclarées.
    pendingSyncKeys: undefined,

    // Détection mode
    isServerMode: false, // true si http://, false si file://

    // Stats
    saveCount: 0,
    syncCount: 0,
    lastSaveTime: 0,
    lastSyncTime: 0
  };

  // ============================================================
  // Détection du mode
  // ============================================================

  /**
   * Détecte si on est en mode serveur (http://)
   */
  function isServerProtocol() {
    return window.location.protocol !== 'file:';
  }

  /**
   * === CSI-ME === Ajoute ?entity= à une URL si un périmètre de sous-entité est
   * ciblé (compte principal). No-op hors mode multi-entités → URL inchangée.
   */
  function withEntityScope(url) {
    if (window.CSI_EntityManager && window.CSI_EntityManager.isMultiEntity()) {
      return window.CSI_EntityManager.appendEntityParam(url);
    }
    return url;
  }

  /**
   * Retourne l'URL d'import (toujours PHP)
   */
  function getImportUrl() {
    var config = Storage.getServerConfig();
    var base = config.baseUrl || window.location.href;
    try {
      return withEntityScope(new URL(CONFIG.SYNC_PHP_URL, base).toString());
    } catch (e) {
      return withEntityScope(CONFIG.SYNC_PHP_URL);
    }
  }

  /**
   * Retourne l'URL d'export (toujours PHP)
   */
  function getExportUrl() {
    var config = Storage.getServerConfig();
    var base = config.baseUrl || window.location.href;
    try {
      return withEntityScope(new URL(CONFIG.EXPORT_PHP_URL, base).toString());
    } catch (e) {
      return withEntityScope(CONFIG.EXPORT_PHP_URL);
    }
  }

  // ============================================================
  // Sauvegarde locale
  // ============================================================

  /**
   * Demande une sauvegarde locale (debounced)
   */
  function requestSave() {
    state.localPending = true;

    if (state.localSaveTimeout) {
      clearTimeout(state.localSaveTimeout);
    }

    state.localSaveTimeout = setTimeout(function() {
      executeSave();
    }, CONFIG.DEBOUNCE_LOCAL_MS);
  }

  /**
   * Force une sauvegarde immédiate
   * @returns {boolean} Succès
   */
  function forceSave() {
    if (state.localSaveTimeout) {
      clearTimeout(state.localSaveTimeout);
      state.localSaveTimeout = null;
    }
    return executeSave();
  }

  /**
   * Exécute la sauvegarde locale
   * @private
   */
  function executeSave() {
    state.localPending = false;
    state.localSaveTimeout = null;

    // Utiliser saveData() de bundle.js
    if (typeof window.saveData === 'function') {
      var result = window.saveData();
      if (result) {
        state.lastSaveTime = Date.now();
        state.saveCount++;

        // En mode serveur, le tab-push granulaire est fait directement par saveData()
        // → PAS de full-sync automatique (FIX-INTEGRITY-1)
        // Le full-sync reste disponible manuellement via forceServerSync() pour l'import initial
      }
      return result;
    }

    void 0;
    return false;
  }

  // ============================================================
  // Licence expiration handler (HTTP 402)
  // ============================================================

  /**
   * Appelé quand le serveur retourne 402 (licence/trial expiré).
   * Désactive l'auto-sync et notifie l'utilisateur.
   */
  function handleLicenseExpired() {
    state.syncEnabled = false;

    // Rafraîchir l'état trial si le module est chargé
    if (window.CSI_TrialManager) {
      window.CSI_TrialManager.detect().catch(function() {});
    }

    // Notification utilisateur
    var t = window.t || function(k, d) { return d || k; };
    if (typeof window.showToast2 === 'function') {
      window.showToast2(t('license.readOnlyNotice', 'Mode lecture seule — activez une licence pour modifier les données'), 'warning', 8000);
    }

    // Ouvrir la modale d'activation si disponible
    if (window.CSI_LicenseManager && typeof window.CSI_LicenseManager.showActivationModal === 'function') {
      window.CSI_LicenseManager.showActivationModal();
    }
  }

  // ============================================================
  // Synchronisation serveur
  // ============================================================

  /**
   * Fusionne des clés déclarées dans l'accumulateur pendingSyncKeys.
   * Sans clés (appel legacy), on bascule en sync complète (null) — remplacement intentionnel.
   * @private
   */
  function _accumulateSyncKeys(keys) {
    if (!keys || !keys.length) {
      state.pendingSyncKeys = null;
      return;
    }
    if (state.pendingSyncKeys === null) return; // une sync complète est déjà demandée
    var acc = state.pendingSyncKeys || [];
    for (var i = 0; i < keys.length; i++) {
      if (acc.indexOf(keys[i]) === -1) acc.push(keys[i]);
    }
    state.pendingSyncKeys = acc;
  }

  /**
   * Demande une synchronisation serveur (debounced)
   * @param {string[]} [keys] - Clés appData modifiées (FIX-FULLSYNC-STALE).
   *   Sans argument : sync complète (réservé à l'import/restauration).
   */
  function requestServerSync(keys) {
    state.syncPending = true;
    _accumulateSyncKeys(keys);

    if (state.syncTimeout) {
      clearTimeout(state.syncTimeout);
    }

    state.syncTimeout = setTimeout(function() {
      var pendingKeys = state.pendingSyncKeys;
      state.pendingSyncKeys = undefined;
      executeServerSync(CONFIG.MAX_RETRIES, pendingKeys === null ? undefined : pendingKeys);
    }, CONFIG.DEBOUNCE_SYNC_MS);
  }

  /**
   * Force une synchronisation serveur immédiate
   * @param {string[]} [keys] - Clés appData modifiées (FIX-FULLSYNC-STALE).
   *   Sans argument : sync complète (réservé à l'import/restauration — écrase tout l'état serveur).
   */
  function forceServerSync(keys, opts) {
    if (state.syncTimeout) {
      clearTimeout(state.syncTimeout);
      state.syncTimeout = null;
    }
    _accumulateSyncKeys(keys);
    var pendingKeys = state.pendingSyncKeys;
    state.pendingSyncKeys = undefined;
    return executeServerSync(CONFIG.MAX_RETRIES, pendingKeys === null ? undefined : pendingKeys, opts);
  }

  /**
   * Exécute la synchronisation vers le serveur
   * @param {number} retriesLeft - Nombre de tentatives restantes
   * @param {string[]} [keys] - Clés appData à synchroniser. Si fourni, l'état envoyé
   *   est composé de l'état serveur FRAIS surchargé par ces seules clés locales
   *   (FIX-FULLSYNC-STALE). Sans clés : remplacement complet par appData (legacy,
   *   réservé aux flux d'import/restauration intentionnels).
   * @returns {Promise<{success: boolean, message?: string}>}
   * @private
   */
  async function executeServerSync(retriesLeft, keys, opts) {
    // H19: reset retry count if last sync was more than 5 minutes ago
    if (Date.now() - (state.lastSyncTime || 0) > 300000) {
      _retryCount = 0;
    }

    // Guard: require AuthManager to be available and logged in
    if (!window.CSI_AuthManager || !window.CSI_AuthManager.isLoggedIn()) {
      void 0;
      return { success: false, message: 'Not authenticated' };
    }

    // FIX-SYNC-DISABLED (audit 2026-06-10, sync-manager.js:177) : respecter syncEnabled
    // (mis à false par handleLicenseExpired sur 402, ou via setAutoSync) — il n'était
    // jamais consulté, la « désactivation » de la sync était sans effet. L'état se
    // réinitialise au rechargement de page (après réactivation de licence).
    if (!state.syncEnabled) {
      void 0;
      return { success: false, message: 'Sync désactivée' };
    }

    // Guard: prevent concurrent sync operations
    if (_syncInProgress) {
      void 0;
      // FIX-FULLSYNC-STALE: ne pas perdre les clés déclarées — re-programmer
      // une sync débouncée qui les reprendra une fois la sync en cours terminée.
      if (keys && keys.length) {
        requestServerSync(keys);
      }
      return { success: false, message: 'Sync in progress' };
    }
    _syncInProgress = true;

    // M27 (audit 2026-07-10) : sur un échec NON FATAL, ré-accumuler les clés déclarées
    // (sans planifier de retry immédiat → pas de boucle serrée) pour qu'elles rejoignent la
    // prochaine sync naturelle (autosave, retour du verrou). Sans cela, les réglages
    // structurants poussés par ce canal (référentiel/niveau NIS2, langue CyFun, niveau
    // ReCyF, titre/maturité cible) — appliqués immédiatement à l'écran — disparaissaient au
    // rechargement dès qu'un 423/401/409/429/erreur réseau survenait. No-op pour les full
    // syncs (keys absent : ne jamais re-forcer un remplacement complet).
    var _reaccumKeys = function() { if (keys && keys.length) _accumulateSyncKeys(keys); };

    try {
      state.syncPending = false;
      state.syncTimeout = null;

      var data;
      // En mode serveur, lire depuis appData en mémoire (pas localStorage)
      if (Storage.isServerMode()) {
        if (keys && keys.length && window.appData) {
          // FIX-FULLSYNC-STALE (audit 2026-06-10, critique C1) : ne jamais pousser
          // l'état mémoire complet — appData n'est jamais rafraîchi globalement après
          // le pull du login, et l'endpoint import écrase server-data.json PUIS purge
          // tous les tab_*.json (push granulaires des AUTRES utilisateurs → perdus).
          // On repart donc de l'état serveur frais (GET export = server-data.json +
          // tab_*.json fusionnés) et on ne remplace que les clés que l'appelant
          // vient réellement de modifier.
          var fresh = await pullFromServer();
          if (!fresh || typeof fresh !== 'object') {
            void 0;
            updateSyncIndicator('error');
            return { success: false, message: 'Pull pré-sync échoué — synchronisation annulée' };
          }
          for (var ki = 0; ki < keys.length; ki++) {
            var syncKey = keys[ki];
            if (Object.prototype.hasOwnProperty.call(window.appData, syncKey)) {
              fresh[syncKey] = window.appData[syncKey];
            } else {
              delete fresh[syncKey]; // propager une suppression locale de la clé
            }
          }
          data = JSON.stringify(fresh);
        } else {
          data = window.appData ? JSON.stringify(window.appData) : null;
        }
      } else {
        try {
          data = localStorage.getItem(Storage.KEYS.DATA);
        } catch(e) {
          void 0;
          return { success: false, message: 'Storage unavailable' };
        }
      }
      if (!data) {
        return { success: false, message: 'No data' };
      }

      var url = getImportUrl();

      // === CSI-ME === écho d'identité de partition sur l'import scopé (V16) —
      // porte forceServerSync(keys) pour les clés hors-onglet. No-op hors mode.
      var _emImportBody = { data: data };
      if (window.CSI_EntityManager && window.CSI_EntityManager.isMultiEntity()) {
        var _emImp = window.CSI_EntityManager.pushBodyExtra();
        for (var _ik in _emImp) { if (Object.prototype.hasOwnProperty.call(_emImp, _ik)) _emImportBody[_ik] = _emImp[_ik]; }
      }
      // === CSI-ME === (audit C1) confirmation explicite d'un import inter-périmètre.
      if (opts && opts.allowCrossImport) _emImportBody.allowCrossImport = true;
      // === CSI-ME === (audit import-export) origine du FICHIER importé pour la garde
      // anti-croisé : le data poussé porte le _csiEntity du périmètre courant, pas celle
      // du fichier — seul l'appelant (applyImportedData) connaît l'origine réelle.
      if (opts && opts.importOrigin) _emImportBody.importOrigin = String(opts.importOrigin);

      // Afficher indicateur "en cours"
      updateSyncIndicator('syncing');

      // Retry loop (no recursion — avoids mutex double-release)
      var attempts = retriesLeft + 1;
      for (var attempt = 0; attempt < attempts; attempt++) {
        try {
          var response = await (window.fetchWithTimeout || fetch)(url, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'Authorization': window.CSI_AuthManager ? window.CSI_AuthManager.getAuthHeader() : ''
            },
            body: JSON.stringify(_emImportBody)
          });

          if (response.ok) {
            _retryCount = 0;
            state.lastSyncTime = Date.now();
            state.syncCount++;
            Storage.updateLastPush();
            updateSyncIndicator('synced');
            // FIX-OPTIMISTIC-REV (2026-06-11) : l'import full purge les tab_*.json serveur
            // → les révisions repartent de 0. Oublier les révisions mémorisées côté client.
            if (typeof window._csiResetTabRevisions === 'function') window._csiResetTabRevisions();
            void 0;

            if (typeof window.refreshSyncMeta === 'function') {
              window.refreshSyncMeta();
            }
            // === CSI-ME === (audit 2026-07-03, K5) remonter les sections écartées par les
            // droits du périmètre (import scopé) pour le feedback utilisateur.
            var _okImportBody = null;
            try { _okImportBody = await response.json(); } catch (e) { /* corps non-JSON */ }
            // FIX-EPOCH-ECHO (audit co-édition 2026-07-17) : un import réussi incrémente
            // l'époque serveur (_dataEpoch, FIX-DATA-EPOCH) et le serveur renvoie la nouvelle
            // valeur — la mémoriser tout de suite, sinon le PROPRE client qui vient
            // d'importer (ex. écriture de configuration sous verrou) écho-e l'ancienne
            // époque au push suivant → 409 epochConflict → parachute et téléchargement
            // d'un fichier smsi_conflit_import_*.json sans raison (observé au banc).
            if (_okImportBody && typeof _okImportBody.dataEpoch === 'number' && window.appData) {
              window.appData._dataEpoch = _okImportBody.dataEpoch;
            }
            return { success: true, ignoredSections: (_okImportBody && _okImportBody.ignoredSections) || [] };
          } else if (response.status === 402) {
            _retryCount = 0;
            updateSyncIndicator('license-expired');
            void 0;
            handleLicenseExpired();
            return { success: false, message: 'License expired', code: 402 };
          } else if (response.status === 423) {
            // FIX-OPTIMISTIC-REV (2026-06-11) : import refusé car verrou d'édition non détenu
            // (EDIT_LOCK_ENFORCED). Pas de retry : un full-sync hors mode édition (ex.
            // persistance d'une migration automatique) est légitimement refusé — il sera
            // rejoué quand l'utilisateur entrera en édition. Données conservées en mémoire.
            // Revue 2026-06-11 : remettre l'indicateur (sinon « Sync... » tourne sans fin).
            _retryCount = 0;
            updateSyncIndicator('edit-locked');
            void 0;
            _reaccumKeys(); // M27 : rejouer les réglages au retour du verrou / prochaine sync
            return { success: false, message: 'Verrou d\'édition non détenu', code: 423 };
          } else if (response.status === 401) {
            _retryCount++;
            if (_retryCount >= MAX_AUTH_RETRIES) {
              void 0;
              _retryCount = 0;
              updateSyncIndicator('auth-error');
              // Déclencher re-authentification
              if (window.CSI_AuthManager && typeof window.CSI_AuthManager.logout === 'function') {
                window.CSI_AuthManager.logout();
              }
              if (window.CSI_AuthManager && typeof window.CSI_AuthManager.showLoginModal === 'function') {
                window.CSI_AuthManager.showLoginModal(function(success) {
                  // FIX-FULLSYNC-STALE: conserver les clés déclarées pour le retry post-login
                  if (success) setTimeout(function() { requestServerSync(keys); }, 500);
                });
              }
              return { success: false, message: 'Session expirée' };
            }
            updateSyncIndicator('auth-error');
            _reaccumKeys(); // M27 : conserver les réglages pour la prochaine sync
            return { success: false, message: 'Auth failed, attempt ' + _retryCount };
          } else if (response.status === 429) {
            _retryCount = 0;
            updateSyncIndicator('retrying');
            void 0;
            var backoffMs = Math.min(CONFIG.RETRY_DELAY_MS * Math.pow(2, attempt), 30000);
            await new Promise(function(r) { setTimeout(r, backoffMs); });
            continue; // réessayer dans la boucle
          } else if (response.status === 409) {
            // === CSI-ME === (audit C1) 409 sur import : garde anti-import-croisé ou
            // conflit d'époque. Pas de retry aveugle ; on remonte le détail pour que
            // l'UI propose la confirmation (crossImport) ou informe l'utilisateur.
            _retryCount = 0;
            var _b409 = null;
            try { _b409 = await response.json(); } catch (e) {}
            updateSyncIndicator('error');
            if (_b409 && _b409.crossImport) {
              return { success: false, crossImport: true, origin: _b409.origin, target: _b409.target,
                       message: _b409.error || 'Import d\'un autre périmètre', code: 409 };
            }
            // === CSI-ME === (audit co-édition 2026-07-03) rattachement changé pendant la
            // session (réassignation admin) : signal dédié — l'appelant recharge l'app.
            if (_b409 && _b409.code === 'ENTITY_CONTEXT_CHANGED') {
              return { success: false, entityContextChanged: true,
                       message: _b409.error || 'Contexte de partition changé', code: 409 };
            }
            _reaccumKeys(); // M27 : conserver les réglages (rejoués à la prochaine sync)
            return { success: false, conflict: true, message: (_b409 && _b409.error) || 'Conflit (409)', code: 409 };
          } else {
            _retryCount = 0;
            throw new Error('HTTP ' + response.status);
          }
        } catch (err) {
          void 0;
          var remaining = attempts - attempt - 1;
          if (remaining > 0) {
            void 0;
            updateSyncIndicator('retrying');
            await new Promise(function(resolve) { setTimeout(resolve, CONFIG.RETRY_DELAY_MS); });
            // Continue loop for retry
          } else {
            updateSyncIndicator('error');
            _reaccumKeys(); // M27 : erreur réseau/5xx épuisée — conserver les réglages
            return { success: false, message: err.message };
          }
        }
      }

      // H18: all retries exhausted (e.g. 429 loop)
      _reaccumKeys(); // M27 : 429 épuisé — conserver les réglages
      return { success: false, message: 'Trop de requêtes, réessayez plus tard' };
    } finally {
      _syncInProgress = false;
    }
  }

  /**
   * AUDIT-CO-C3: Pull les données d'un seul tab depuis le serveur (granulaire).
   * Utilise l'endpoint data-pull?tabs=<tabId> pour récupérer les données fraîches.
   * @param {string} tabId - L'identifiant du tab à pull
   * @returns {Promise<Object|null>} Les données du tab ou null si échec
   */
  async function pullTabFromServer(tabId) {
    if (!tabId) return null;
    if (!window.CSI_AuthManager || !window.CSI_AuthManager.isLoggedIn()) return null;
    var baseUrl = CONFIG.SYNC_PHP_URL;
    var url = withEntityScope(baseUrl + '?action=data-pull&tabs=' + encodeURIComponent(tabId)); // === CSI-ME ===
    try {
      var response = await (window.fetchWithTimeout || fetch)(url, {
        method: 'GET',
        headers: {
          'Authorization': window.CSI_AuthManager.getAuthHeader()
        }
      });
      if (!response.ok) return null;
      var result = await response.json();
      if (result && result.ok && result.tabs && result.tabs[tabId]) {
        // FIX-SYNCMETA-DISPLAY (audit 2026-06-10) : compter aussi les pulls granulaires
        // dans « Dernière réception » (Réglages > Stockage serveur).
        Storage.updateLastPull();
        return result.tabs[tabId];
      }
      return null;
    } catch (e) {
      void 0;
      return null;
    }
  }

  /**
   * Récupère les données depuis le serveur
   * @returns {Promise<Object|null>}
   */
  async function pullFromServer() {
    // Guard: require AuthManager to be available and logged in
    if (!window.CSI_AuthManager || !window.CSI_AuthManager.isLoggedIn()) {
      void 0;
      return null;
    }

    var url = getExportUrl();

    try {
      var response = await (window.fetchWithTimeout || fetch)(url, {
        method: 'GET',
        headers: {
          'Authorization': window.CSI_AuthManager ? window.CSI_AuthManager.getAuthHeader() : ''
        }
      });

      if (response.status === 402) {
        void 0;
        handleLicenseExpired();
        return null;
      }
      if (!response.ok) {
        void 0;
        return null;
      }

      var data = await response.json();
      // FIX-EXPORT: PHP json_encode([]) produit [] — normaliser en {} côté client
      if (Array.isArray(data) && data.length === 0) data = {};
      // === CSI-ME === (Lot 3b) sidecar _csiGroupView : données du groupe visibles en
      // lecture (V8). Capturé pour l'UI puis RETIRÉ du payload — ne doit jamais entrer
      // dans appData (jamais persisté, jamais ré-exporté localement).
      if (data && data._csiGroupView) {
        if (window.CSI_EntityManager && window.CSI_EntityManager.ingestGroupView) {
          window.CSI_EntityManager.ingestGroupView(data._csiGroupView);
        }
        delete data._csiGroupView;
      }
      Storage.updateLastPull();
      return data;
    } catch (e) {
      void 0;
      return null;
    }
  }

  // ============================================================
  // Indicateur visuel
  // ============================================================

  /**
   * Met à jour l'indicateur de synchronisation dans l'UI
   * @param {string} status - 'syncing', 'synced', 'retrying', 'auth-error', 'error'
   */
  function updateSyncIndicator(status) {
    var indicator = document.getElementById('serverSyncIndicator');

    if (!indicator) {
      indicator = document.createElement('span');
      indicator.id = 'serverSyncIndicator';
      indicator.style.cssText = 'display:inline-flex;align-items:center;gap:4px;margin-left:12px;font-size:11px;padding:4px 8px;border-radius:4px;transition:all 0.3s;';

      var navbar = document.querySelector('.navbar__left') || document.querySelector('.header__title');
      if (navbar) {
        navbar.appendChild(indicator);
      }
    }

    if (!indicator) return;

    indicator.style.opacity = '1';

    switch (status) {
      case 'syncing':
        indicator.innerHTML = '<i class="fas fa-sync fa-spin" style="color:var(--color-primary,#3b82f6)"></i> <span style="color:var(--color-text-secondary)">Sync...</span>';
        indicator.style.background = 'var(--color-surface-hover,#f0f0f0)';
        indicator.title = _t('sync.syncing', 'Synchronizing');
        break;

      case 'synced':
        indicator.innerHTML = '<i class="fas fa-cloud-upload-alt" style="color:var(--color-success,#22c55e)"></i>';
        indicator.style.background = 'transparent';
        indicator.title = _t('sync.synced', 'Synchronized with server');
        break;

      case 'retrying':
        indicator.innerHTML = '<i class="fas fa-sync fa-spin" style="color:var(--color-warning,#f59e0b)"></i> <span style="color:var(--color-text-secondary)">Retry...</span>';
        indicator.style.background = 'var(--color-surface-hover,#f0f0f0)';
        indicator.title = _t('sync.retrying', 'Retrying synchronization');
        break;

      case 'auth-error':
        indicator.innerHTML = '<i class="fas fa-exclamation-triangle" style="color:var(--color-warning,#f59e0b)"></i>';
        indicator.style.background = 'transparent';
        indicator.title = _t('sync.authError', 'Authentication error - check password');
        break;

      case 'edit-locked': {
        // FIX-OPTIMISTIC-REV (2026-06-11) : full-sync différé faute de verrou d'édition —
        // état neutre (pas une erreur : la sync se rejouera en mode édition).
        indicator.textContent = '';
        var eli = document.createElement('i');
        eli.className = 'fas fa-user-pen';
        eli.style.color = 'var(--color-warning,#f59e0b)';
        indicator.appendChild(eli);
        indicator.style.background = 'transparent';
        indicator.title = _t('sync.editLocked', 'Synchronisation différée — verrou d\'édition non détenu');
        break;
      }

      case 'license-expired': {
        indicator.textContent = '';
        var li = document.createElement('i');
        li.className = 'fas fa-lock';
        li.style.color = 'var(--color-danger,#dc3545)';
        indicator.appendChild(li);
        indicator.appendChild(document.createTextNode(' '));
        var ls = document.createElement('span');
        ls.style.cssText = 'color:var(--color-danger,#dc3545);font-size:10px';
        ls.textContent = _t('sync.licenseLabel', 'License');
        indicator.appendChild(ls);
        indicator.style.background = 'transparent';
        indicator.title = _t('sync.licenseExpired', 'License or trial expired \u2014 read-only mode');
        break;
      }

      case 'error':
        indicator.innerHTML = '<i class="fas fa-cloud" style="color:var(--color-danger,#dc3545)"></i>';
        indicator.style.background = 'transparent';
        indicator.title = _t('sync.syncError', 'Synchronization error');
        break;
    }

    // Fade out après 5 secondes (sauf pour syncing/retrying)
    if (status !== 'syncing' && status !== 'retrying') {
      setTimeout(function() {
        if (indicator) indicator.style.opacity = '0.5';
      }, 5000);
    }
  }

  // ============================================================
  // API publique
  // ============================================================

  var SyncManager = {
    // Sauvegarde
    requestSave: requestSave,
    forceSave: forceSave,

    // Synchronisation serveur
    requestServerSync: requestServerSync,
    forceServerSync: forceServerSync,
    pullFromServer: pullFromServer,
    pullTabFromServer: pullTabFromServer, // AUDIT-CO-C3

    // Contrôle
    setAutoSync: function(enabled) {
      state.syncEnabled = !!enabled;
      void 0;
    },

    // FIX-SYNC-DISABLED : consulté par _serverDataPushWithRetry (bundle.js) avant chaque push
    isSyncEnabled: function() {
      return state.syncEnabled;
    },

    cancel: function() {
      if (state.localSaveTimeout) {
        clearTimeout(state.localSaveTimeout);
        state.localSaveTimeout = null;
      }
      if (state.syncTimeout) {
        clearTimeout(state.syncTimeout);
        state.syncTimeout = null;
      }
      state.localPending = false;
      state.syncPending = false;
      state.pendingSyncKeys = undefined;
    },

    // État
    isPending: function() {
      return state.localPending || state.syncPending;
    },

    isServerMode: function() {
      return state.isServerMode;
    },

    getStats: function() {
      return {
        saveCount: state.saveCount,
        syncCount: state.syncCount,
        lastSaveTime: state.lastSaveTime,
        lastSyncTime: state.lastSyncTime,
        localPending: state.localPending,
        syncPending: state.syncPending,
        isServerMode: state.isServerMode,
        syncEnabled: state.syncEnabled
      };
    },

    // URLs
    getImportUrl: getImportUrl,
    getExportUrl: getExportUrl
  };

  // ============================================================
  // Initialisation
  // ============================================================

  async function init() {
    state.isServerMode = isServerProtocol();

    if (state.isServerMode) {
      void 0;
    } else {
      void 0;
    }
  }

  // Lancer l'initialisation
  init();

  // Enregistrer dans le registre CSI
  if (window.CSI && window.CSI.register) {
    window.CSI.register('syncManager', {
      exports: SyncManager
    });
  }

  // Exposer globalement
  window.CSI_SyncManager = SyncManager;

  // Compatibilité avec ancien code (save-manager.js)
  window.requestSave = requestSave;
  window.forceSave = forceSave;
  window.forceServerSync = forceServerSync;
  window.setAutoSync = SyncManager.setAutoSync;

  void 0;

})();
