'use strict';
// ═══════════════════════════════════════════════
// js/export.js — JSON backup export / import
// ═══════════════════════════════════════════════

async function blobToB64(blob) {
  return new Promise((res, rej) => {
    const r = new FileReader();
    r.onload = () => res(r.result.split(',')[1]);
    r.onerror = rej;
    r.readAsDataURL(blob);
  });
}

function b64ToBlob(b64, mime) {
  const bin = atob(b64);
  const arr = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
  return new Blob([arr], { type: mime || 'image/jpeg' });
}

function _downloadBlob(blob, filename) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url; a.download = filename;
  document.body.appendChild(a); a.click();
  setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 1000);
}

// Build the full backup payload (shared between encrypted + plaintext exports)
async function _buildBackupPayload() {
  const entries = getEntries();

  // Own images only — keys start with 'img_', never buddy 'c_*' keys
  const images = {};
  for (const e of entries) {
    // Entry photos
    for (const f of ['imgFront']) {
      const k = e[f];
      if (k && k.startsWith('img_') && !images[k]) {
        const blob = await idbGet(k);
        if (blob) images[k] = { b64: await blobToB64(blob), mime: blob.type || 'image/jpeg' };
      }
    }
    // Wrinkle scan photos (wsScan object may have photoKey, displayKey, edgeKey, overlayKey)
    if (e.wsScan && typeof e.wsScan === 'object') {
      for (const f of ['photoKey', 'displayKey', 'edgeKey', 'overlayKey']) {
        const k = e.wsScan[f];
        if (k && k.startsWith('img_') && !images[k]) {
          const blob = await idbGet(k);
          if (blob) images[k] = { b64: await blobToB64(blob), mime: blob.type || 'image/jpeg' };
        }
      }
    }
  }

  // Own gallery photos
  const galleries = typeof getGalleries === 'function' ? getGalleries() : [];
  for (const g of galleries) {
    for (const item of (g.items || [])) {
      const k = item.imgKey;
      if (k && k.startsWith('img_') && !images[k]) {
        const blob = await idbGet(k);
        if (blob) images[k] = { b64: await blobToB64(blob), mime: blob.type || 'image/jpeg' };
      }
    }
  }

  // Own check-in photos
  const checkins = typeof getCheckIns === 'function' ? getCheckIns() : [];
  for (const ci of checkins) {
    const k = ci.imgKey;
    if (k && !images[k]) {
      const blob = await idbGet(k);
      if (blob) images[k] = { b64: await blobToB64(blob), mime: blob.type || 'image/jpeg' };
    }
  }

  // ECDH keypair
  const pubRaw  = await idbGet('frj_keypair_pub');
  const privRaw = await idbGet('frj_keypair_priv');
  const keypair = (pubRaw && privRaw) ? {
    pub:  b64FromBytes(pubRaw),
    priv: b64FromBytes(privRaw)
  } : null;

  // Contacts metadata (no journals or cached images — those re-sync)
  const contacts = getContacts().map(c => ({
    pubKeyB64: c.pubKeyB64, nickname: c.nickname, color: c.color,
    addedAt: c.addedAt, trusted: c.trusted || false
  }));

  return {
    version:     2,
    exportedAt:  new Date().toISOString(),
    keypair,
    displayName: lsGet('frj_displayname') || null,
    profile:     getProfile(),
    entries,
    weeklyStats: getWeeklyStats(),
    timerSessions: getTimerSessions(),
    contacts,
    wsZones:     getWsZones(),
    wsSettings:  getWsSettings(),
    buddyPermissions:   lsGet('frj_buddy_permissions') || null,
    vanishSettings:     lsGet('frj_vanish_settings') || null,
    watermarkSettings:  lsGet('frj_watermark_settings') || null,
    publicProfile:      lsGet('frj_public_profile') || null,
    blockedHashes:      lsGet('frj_blocked_hashes') || null,
    galleries:          typeof getGalleries === 'function' ? getGalleries() : [],
    galleryNotes:       lsGet('frj_gallery_notes') || {},
    checkins:           typeof getCheckIns === 'function' ? getCheckIns() : [],
    bookmarks:          lsGet('frj_bookmarks') || {},
    comparisons:        typeof getSavedComparisons === 'function' ? getSavedComparisons() : [],
    images
  };
}

// ── Primary export — requires encryption ──────────────────────────────────────
async function exportBackup() {
  const sessionKey = (typeof getSessionKey === 'function') ? getSessionKey() : null;
  if (sessionKey) {
    await _exportEncrypted(sessionKey);
    return;
  }
  // No active session — check why
  const encEnabled = (typeof idbMetaGet === 'function') ? await idbMetaGet('encryptionEnabled') : false;
  if (encEnabled) {
    // Encrypted but curtain is up — shouldn't normally reach here
    toast('Unlock your journal first, then export.', true);
  } else {
    confirm2(
      'Encryption required to export',
      'Backups are only available when your data is encrypted. Set up a password to protect your journal and enable exports.',
      () => { if (typeof closeSidebar === 'function') closeSidebar(); if (typeof showMigrationModal === 'function') showMigrationModal(); }
    );
  }
}

// Explicit plaintext export — warns user first
// NOTE: keypair is intentionally excluded from plaintext exports (security: private keys must not appear in unencrypted files)
function exportBackupPlaintext() {
  confirm2(
    'Export as plaintext?',
    'This backup will NOT be encrypted. Anyone with access to the file can read your journal. Use this for archival or to import on a device without encryption set up.',
    () => _exportPlaintext(/* warned= */ true)
  );
}

// Builds the encrypted backup Blob without downloading it. Used by the manual
// export below and by the automatic backup file writer (js/storagehealth.js).
async function _buildEncryptedBackupBlob(sessionKey) {
  const payload   = await _buildBackupPayload();
  const json      = JSON.stringify(payload);
  const saltArr   = (typeof idbMetaGet === 'function') ? await idbMetaGet('encryptionSalt') : null;
  if (!saltArr) throw new Error('Encryption salt not found. Cannot create encrypted backup.');
  const iv        = crypto.getRandomValues(new Uint8Array(12));
  const ct        = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv }, sessionKey, new TextEncoder().encode(json)
  );
  // Base64-encode ciphertext to avoid 160MB heap explosion from Array.from on large backups
  const ctBytes = new Uint8Array(ct);
  let ctB64 = '';
  const BLOCK = 32768;
  for (let i = 0; i < ctBytes.length; i += BLOCK) {
    ctB64 += String.fromCharCode.apply(null, ctBytes.subarray(i, Math.min(i + BLOCK, ctBytes.length)));
  }
  ctB64 = btoa(ctB64);
  const encPayload = {
    version:   'forestorers-backup-v2',
    created:   new Date().toISOString(),
    salt:      saltArr,               // stored so key can be re-derived from password on any device
    iv:        Array.from(iv),
    encrypted: ctB64,
    encFormat: 'base64'               // marker so import knows the format
  };
  return new Blob([JSON.stringify(encPayload)], { type: 'application/octet-stream' });
}

async function _exportEncrypted(sessionKey) {
  toast('Preparing encrypted backup…');
  try {
    const blob = await _buildEncryptedBackupBlob(sessionKey);
    _downloadBlob(blob, `forestorers-backup-${new Date().toISOString().slice(0, 10)}.forestorers`);
    _markExported();
    toast('Encrypted backup downloaded ✓');
  } catch(err) {
    console.error('Export error:', err);
    toast('Export failed: ' + err.message, true);
  }
}

async function _exportPlaintext(warned) {
  toast('Preparing backup…');
  try {
    const payload = await _buildBackupPayload();
    // Security: never include the ECDH keypair (private key) in plaintext JSON files.
    // The keypair is only included in encrypted (.forestorers) backups.
    payload.keypair = undefined;
    const blob    = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
    _downloadBlob(blob, `forestorers-backup-${new Date().toISOString().slice(0, 10)}.json`);
    _markExported();
    toast('Backup downloaded ✓');
  } catch(err) {
    console.error('Export error:', err);
    toast('Export failed: ' + err.message, true);
  }
}

function _markExported() {
  lsSet('frj_last_export', new Date().toISOString().slice(0, 10));
  updateBackupWarning();
}

function _sanitizeImportedEntry(e) {
  if (!e || typeof e !== 'object') return null;
  if (typeof e.date !== 'string') return null;
  // Allow entries without an id (old backups) — generate one so they survive the sprint0 migration
  const id = (typeof e.id === 'string' && e.id) ? String(e.id).slice(0, 64) : (typeof uuid === 'function' ? uuid() : crypto.randomUUID());
  // Numeric fields: coerce or strip
  const numFields = ['ci','bmi','pctTugged','pctRetained','avgHrsTugged','avgHrsRetained','goalCI'];
  const sanitized = { id, date: String(e.date).slice(0, 10) };
  numFields.forEach(k => { if (e[k] != null) { const n = Number(e[k]); if (!isNaN(n)) sanitized[k] = n; } });
  // String fields: limit length
  ['notes','supplements'].forEach(k => { if (typeof e[k] === 'string') sanitized[k] = e[k].slice(0, 4000); });
  // Array fields — handle both old names (tugDevices/retDevices/devices) and current names
  const tugArr = Array.isArray(e.tuggingDevices) ? e.tuggingDevices
    : Array.isArray(e.tugDevices) ? e.tugDevices
    : (typeof e.devices === 'string' && e.devices) ? [e.devices]
    : [];
  const retArr = Array.isArray(e.retainingDevices) ? e.retainingDevices
    : Array.isArray(e.retDevices) ? e.retDevices
    : (typeof e.retainingDevices === 'string' && e.retainingDevices) ? [e.retainingDevices]
    : [];
  sanitized.tuggingDevices  = tugArr.filter(x => typeof x === 'string').map(x => x.slice(0, 100));
  sanitized.retainingDevices = retArr.filter(x => typeof x === 'string').map(x => x.slice(0, 100));
  // Boolean flags
  if (e.imgPrivate) sanitized.imgPrivate = true;
  // Copy photo reference field
  if (typeof e.imgFront === 'string') sanitized.imgFront = e.imgFront.slice(0, 128);
  // Preserve wsScan object (wrinkle scan data with photo keys + zone results)
  if (e.wsScan && typeof e.wsScan === 'object') sanitized.wsScan = e.wsScan;
  // Preserve updatedAt, version, deviceId for sync
  if (typeof e.updatedAt === 'number') sanitized.updatedAt = e.updatedAt;
  if (typeof e.version   === 'number') sanitized.version   = e.version;
  if (typeof e.deviceId  === 'string') sanitized.deviceId  = e.deviceId.slice(0, 64);
  // Schema upgrade: backfill missing fields from older backups
  if (typeof upgradeEntrySchema === 'function') upgradeEntrySchema(sanitized);
  return sanitized;
}

// ── Import — handles v1/v2 plaintext + forestorers-backup-v2 encrypted ────────
function importBackupPrompt() {
  const input = document.createElement('input');
  input.type   = 'file';
  input.accept = '.json,.forestorers,application/json,application/octet-stream';
  input.onchange = async e => {
    const file = e.target.files[0];
    if (!file) return;
    try {
      const text    = await file.text();
      const parsed  = JSON.parse(text);

      if (parsed.version === 'forestorers-backup-v2') {
        // Encrypted backup — ask for password
        let result;
        try {
          result = await _showImportPasswordPrompt(parsed);
        } catch(e) {
          // User cancelled
          return;
        }
        await doImport(result.payload, result.password);
      } else {
        // Plaintext v1 or v2
        await doImport(parsed);
      }
    } catch(err) {
      console.error('Import parse error:', err);
      toast('Import failed: ' + err.message, true);
    }
  };
  input.click();
}

// Shows a password overlay, decrypts and returns the inner payload
function _showImportPasswordPrompt(encPayload) {
  return new Promise((resolve, reject) => {
    const old = document.getElementById('_enc-import-overlay');
    if (old) old.remove();

    const overlay = document.createElement('div');
    overlay.id        = '_enc-import-overlay';
    overlay.className = 'enc-setup-overlay active';
    overlay.innerHTML = `
      <div class="enc-setup-box">
        <div class="enc-setup-icon">🔓</div>
        <h2 class="enc-setup-title">Encrypted backup</h2>
        <p class="enc-setup-sub">Enter the password that was used when this backup was created.</p>
        <div class="enc-setup-field">
          <input type="password" id="_enc-import-pw" placeholder="Backup password" autocomplete="current-password">
        </div>
        <p class="enc-setup-err" id="_enc-import-err"></p>
        <button class="btn btn-primary enc-setup-submit" id="_enc-import-btn">Decrypt &amp; Import</button>
        <button class="enc-setup-skip" id="_enc-import-cancel">Cancel</button>
      </div>`;
    document.body.appendChild(overlay);

    const pw     = overlay.querySelector('#_enc-import-pw');
    const btn    = overlay.querySelector('#_enc-import-btn');
    const err    = overlay.querySelector('#_enc-import-err');
    const cancel = overlay.querySelector('#_enc-import-cancel');

    setTimeout(() => pw.focus(), 60);

    const doDecrypt = async () => {
      if (!pw.value) { err.textContent = 'Enter the backup password.'; return; }
      btn.disabled = true; btn.textContent = 'Decrypting…';
      const password = pw.value;
      try {
        const key = await deriveKey(password, new Uint8Array(encPayload.salt));
        // Support both base64 (v2+) and Array-of-numbers (legacy) ciphertext format
        let ctArr;
        if (encPayload.encFormat === 'base64' && typeof encPayload.encrypted === 'string') {
          const bin = atob(encPayload.encrypted);
          ctArr = new Uint8Array(bin.length);
          for (let i = 0; i < bin.length; i++) ctArr[i] = bin.charCodeAt(i);
        } else {
          ctArr = new Uint8Array(encPayload.encrypted);
        }
        const pt  = await crypto.subtle.decrypt(
          { name: 'AES-GCM', iv: new Uint8Array(encPayload.iv) },
          key, ctArr
        );
        const payload = JSON.parse(new TextDecoder().decode(pt));
        overlay.remove();
        resolve({ payload, password });
      } catch(e) {
        console.error('[import] decrypt/parse failed:', e);
        err.textContent = 'Incorrect password or corrupted backup.';
        btn.disabled    = false; btn.textContent = 'Decrypt & Import';
        pw.value = ''; pw.focus();
      }
    };

    btn.onclick    = doDecrypt;
    pw.onkeydown   = e => { if (e.key === 'Enter') doDecrypt(); };
    cancel.onclick = () => { overlay.remove(); reject(new Error('Cancelled')); };
  });
}

async function doImport(payload, encPassword) {
  if (!payload || (payload.version !== 1 && payload.version !== 2)) {
    toast('Unrecognised backup format', true);
    return;
  }

  const entryCount = (payload.entries || []).length;
  const hasKeypair = !!payload.keypair;
  confirm2(
    'Restore Backup?',
    `This will REPLACE your current data with ${entryCount} entries from the backup${hasKeypair ? ', including your identity keypair' : ''}. Your existing entries will be removed. Buddy journals are not included and will re-sync on reconnect.`,
    async () => {
      toast('Restoring…');
      try {
        // Snapshot current state for rollback if import fails
        let _kpRollback = null;
        let _deviceRollback = null;
        if (payload.keypair && typeof idbGet === 'function') {
          try {
            const oldPriv = await idbGet('frj_keypair_priv');
            const oldPub  = await idbGet('frj_keypair_pub');
            if (oldPriv && oldPub) _kpRollback = { priv: oldPriv, pub: oldPub };
            if (typeof idbMetaGet === 'function') {
              _deviceRollback = {
                paired: await idbMetaGet('paired_devices').catch(() => []),
                deviceId: await idbMetaGet('frj_device_id').catch(() => null),
                pending: await idbMetaGet('pending_pair').catch(() => null),
              };
            }
          } catch(_) { /* best-effort */ }
        }

        // ── Step 1: Disconnect from signal server with OLD identity ────────
        // Must happen BEFORE keypair replacement so the server correctly
        // removes the old registration and notifies old buddies/devices.
        if (payload.keypair && typeof p2pReinit === 'function') {
          await p2pReinit();
        }

        // ── Step 2: Clear stale state from previous profile ─────────────
        // Buddy cached images (c_* IDB keys) belong to the old identity's contacts
        if (typeof idbGetAllKeys === 'function') {
          try {
            const allKeys = await idbGetAllKeys();
            const staleKeys = allKeys.filter(k => k.startsWith('c_'));
            if (staleKeys.length) await Promise.all(staleKeys.map(k => idbDel(k)));
          } catch(e) { console.warn('[import] buddy image cleanup:', e); }
        }
        // Revoke in-memory image URLs for buddy photos
        if (typeof imgCache !== 'undefined') {
          for (const k of Object.keys(imgCache)) {
            if (k.startsWith('c_')) { URL.revokeObjectURL(imgCache[k]); delete imgCache[k]; }
          }
        }
        // Clear device pairing state — new identity can't reuse old pairings
        if (payload.keypair && typeof idbMetaPut === 'function') {
          await idbMetaPut('paired_devices', []).catch(() => {});
          await idbMetaPut('pending_pair', null).catch(() => {});
          await idbMetaPut('used_pair_nonces', []).catch(() => {});
          await idbMetaPut('peerEncSalt', null).catch(() => {});
          // Regenerate device ID so old paired devices can't route to us
          await idbMetaPut('frj_device_id', crypto.randomUUID
            ? crypto.randomUUID()
            : 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
                const r = Math.random() * 16 | 0;
                return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
              })).catch(() => {});
        }
        // Clear per-buddy ephemeral data (notes, chat, notifications)
        lsSet('frj_buddy_notes', {});
        lsSet('frj_notes_unread', []);
        lsSet('frj_notes_sent', {});
        lsSet('frj_notes_queue', {});
        try { localStorage.removeItem('frj_notifs'); localStorage.removeItem('frj_notifications'); } catch(e) {}
        // Clear per-peer chat history (frj_chat_* keys)
        try {
          for (let i = localStorage.length - 1; i >= 0; i--) {
            const k = localStorage.key(i);
            if (k && k.startsWith('frj_chat_')) localStorage.removeItem(k);
          }
        } catch(e) {}

        // ── Step 3: Restore backup data ─────────────────────────────────
        // Restore own images (img_* and checkin_* keys — no buddy c_* or gal_* keys)
        const images = payload.images || {};
        for (const [key, val] of Object.entries(images)) {
          if (!key.startsWith('img_') && !key.startsWith('checkin_')) continue;
          const blob = b64ToBlob(val.b64, val.mime);
          await idbPut(key, blob);
          if (typeof imgCache !== 'undefined') imgCache[key] = URL.createObjectURL(blob);
        }

        // Restore ECDH keypair
        if (payload.keypair) {
          const privBytes = Uint8Array.from(atob(payload.keypair.priv), c => c.charCodeAt(0));
          const pubBytes  = Uint8Array.from(atob(payload.keypair.pub),  c => c.charCodeAt(0));
          await idbPut('frj_keypair_priv', privBytes.buffer);
          await idbPut('frj_keypair_pub',  pubBytes.buffer);
          if (typeof _identityCache !== 'undefined') _identityCache = null;
        }

        // Restore display name (always replace — clear if absent so old profile name doesn't linger)
        lsSet('frj_displayname', payload.displayName || null);
        if (typeof _identityCache !== 'undefined') _identityCache = null;

        // Restore profile (always replace — use empty profile if absent)
        const p = payload.profile || {};
        p.onboarding = { profile: true, journal: true, encrypt: true, backup: true };
        saveProfile(p);

        // Replace entries — sanitize each one to prevent XSS or corrupt data
        const sanitizedEntries = (payload.entries || []).map(_sanitizeImportedEntry).filter(Boolean);
        saveEntries(sanitizedEntries);

        // Replace weekly stats entirely
        saveWeeklyStats(payload.weeklyStats || []);

        // Replace timer sessions
        if (payload.timerSessions) saveTimerSessions(payload.timerSessions);

        // Contacts: always replace (buddy journal data will re-sync)
        saveContacts(payload.contacts || []);

        // Restore workspace zones/settings (always replace — clear if absent)
        saveWsZones(payload.wsZones || []);
        saveWsSettings(payload.wsSettings || {});

        // Restore user-configured settings (always replace — clear if absent)
        lsSet('frj_buddy_permissions', payload.buddyPermissions || null);
        lsSet('frj_vanish_settings', payload.vanishSettings || null);
        lsSet('frj_watermark_settings', payload.watermarkSettings || null);
        lsSet('frj_public_profile', payload.publicProfile || null);
        lsSet('frj_blocked_hashes', payload.blockedHashes || null);

        // Restore galleries, check-ins, bookmarks, comparisons
        if (payload.galleries && typeof saveGalleries === 'function') saveGalleries(payload.galleries);
        if (payload.galleryNotes) lsSet('frj_gallery_notes', payload.galleryNotes);
        if (payload.checkins && typeof saveCheckIns === 'function') saveCheckIns(payload.checkins);
        if (payload.bookmarks) lsSet('frj_bookmarks', payload.bookmarks);
        if (payload.comparisons && typeof saveSavedComparisons === 'function') saveSavedComparisons(payload.comparisons);

        // Re-render
        if (typeof renderDash === 'function') renderDash();
        if (typeof renderJournalPage === 'function') renderJournalPage();
        // Re-render profile modal if open, and update curtain identity
        const _pfModal = document.getElementById('profileModal');
        if (_pfModal?.classList.contains('open') && typeof openProfile === 'function') openProfile();
        if (typeof _setCurtainIdentity === 'function') _setCurtainIdentity();
        if (typeof renderTimeline === 'function' && document.getElementById('page-timeline')?.classList.contains('active')) renderTimeline();
        if (typeof updateStorageSize === 'function') updateStorageSize();

        // If plaintext backup imported on encrypted device: data was written through the
        // encrypted cache above — no need to clear encryption. Just reload if keypair changed.
        const currentlyEncrypted = await idbMetaGet('encryptionEnabled');

        if (encPassword && !currentlyEncrypted && typeof setupEncryptionFromPairing === 'function') {
          // Encrypted backup on unencrypted device — auto-encrypt with the same password
          await setupEncryptionFromPairing(encPassword);
          toast(`Imported ${entryCount} entries. Encrypting…`);
          try {
            await runMigration(({ current, complete }) => {
              if (!complete) toast(current);
            });
            setTimeout(() => location.reload(), 1200);
          } catch(e) {
            console.error('Post-import migration error:', e);
            toast('Import succeeded but encryption failed. You can retry from Settings.', true);
          }
        } else if (payload.keypair) {
          toast(`Imported ${entryCount} entries. Reloading…`);
          setTimeout(() => location.reload(), 1200);
        } else if (!currentlyEncrypted) {
          // Imported plaintext backup onto unencrypted device — prompt to encrypt now
          // Re-register with signal server so buddy list is up-to-date
          if (typeof p2pSigRegister === 'function') p2pSigRegister();
          toast(`Imported ${entryCount} entries ✓`);
          setTimeout(() => {
            if (typeof showMigrationModal === 'function') {
              confirm2(
                'Protect your data',
                `Your backup has been restored (${entryCount} entries). Your data is currently unprotected. Set up encryption now to secure it.`,
                () => showMigrationModal()
              );
            }
          }, 600);
        } else {
          // Data-only import on encrypted device — re-register with updated buddy list
          if (typeof p2pSigRegister === 'function') p2pSigRegister();
          toast(`Imported ${entryCount} entries ✓`);
        }
      } catch(err) {
        console.error('Import error:', err);
        // Rollback keypair + device state if we cleared them before the failure
        if (_kpRollback) {
          try {
            await idbPut('frj_keypair_priv', _kpRollback.priv);
            await idbPut('frj_keypair_pub',  _kpRollback.pub);
            if (typeof _identityCache !== 'undefined') _identityCache = null;
            console.log('[import] keypair rolled back after import failure');
          } catch(e2) { console.error('[import] keypair rollback failed:', e2); }
        }
        if (_deviceRollback && typeof idbMetaPut === 'function') {
          try {
            if (_deviceRollback.paired) await idbMetaPut('paired_devices', _deviceRollback.paired);
            if (_deviceRollback.deviceId) await idbMetaPut('frj_device_id', _deviceRollback.deviceId);
            if (_deviceRollback.pending) await idbMetaPut('pending_pair', _deviceRollback.pending);
            console.log('[import] device state rolled back after import failure');
          } catch(e2) { console.error('[import] device rollback failed:', e2); }
        }
        // Re-init signaling with the restored identity
        if (typeof p2pReinit === 'function') p2pReinit().catch(() => {});
        toast('Import failed: ' + err.message, true);
      }
    }
  );
}

function updateBackupWarning() {
  const el = document.getElementById('backup-warning');
  if (!el) return;
  // Show persistent prompt when data is not encrypted
  if (typeof idbMetaGet !== 'function') { el.style.display = 'none'; return; }
  idbMetaGet('encryptionEnabled').then(enabled => {
    // Hide when encryption is set up at all — encryptDataBtn covers the "enabled but not migrated" state
    el.style.display = enabled ? 'none' : '';
  }).catch(() => { el.style.display = 'none'; });
}

async function encryptAndBackup() {
  if (typeof closeSidebar === 'function') closeSidebar();
  // If already encrypted, just export
  const sessionKey = (typeof getSessionKey === 'function') ? getSessionKey() : null;
  if (sessionKey) {
    await exportBackup();
    return;
  }
  // Not encrypted — run encryption setup, then migration, then auto-export
  if (typeof openEncryptionSettings === 'function') await openEncryptionSettings();
  // After setup, check if encryption was enabled (user didn't skip)
  const enabled = (typeof idbMetaGet === 'function') ? await idbMetaGet('encryptionEnabled') : false;
  if (!enabled) return;
  // If schema not yet v2, run migration
  const v = (typeof getSchemaVersion === 'function') ? await getSchemaVersion() : null;
  if (v !== 2) {
    if (typeof showMigrationModal === 'function') showMigrationModal();
    // Export will happen after migration completes — user clicks export from there
    return;
  }
  // Already migrated — export now
  const key = (typeof getSessionKey === 'function') ? getSessionKey() : null;
  if (key) {
    await _exportEncrypted(key);
    updateBackupWarning();
  }
}

function fmtBytes(bytes) {
  if (bytes < 1024) return bytes + ' B';
  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
  return (bytes / 1024 / 1024).toFixed(2) + ' MB';
}

async function updateStorageSize() {
  const el = document.getElementById('storage-size');
  if (!el) return;
  try {
    if (navigator.storage && navigator.storage.estimate) {
      const est = await navigator.storage.estimate();
      el.textContent = fmtBytes(est.usage || 0) + ' / ' + fmtBytes(est.quota || 0);
    } else {
      let total = 0;
      for (const k in localStorage) {
        if (Object.prototype.hasOwnProperty.call(localStorage, k)) {
          total += (localStorage[k].length + k.length) * 2;
        }
      }
      el.textContent = '~' + fmtBytes(total) + ' (localStorage only)';
    }
  } catch(e) {
    el.textContent = 'unknown';
  }
}
