'use strict';
// ═══════════════════════════════════════════════
// js/storagehealth.js — storage eviction defenses
// Browsers can evict site storage (iOS Safari is the
// worst offender). This module: warns when persistence
// is not granted or private browsing is detected, spots
// partial storage wipes and says so instead of showing
// a silently empty journal, nudges iOS users to install
// the PWA (installed apps are exempt from Safari's
// 7-day wipe), and maintains an automatic encrypted
// backup file on browsers with the File System Access
// API (desktop Chrome/Edge).
// ═══════════════════════════════════════════════

const _SHB_DISMISS_PREFIX = 'frj_shb_';
const _AUTOBACKUP_MIN_INTERVAL = 3600000;      // at most one background write per hour
const _AUTOBACKUP_INIT_AGE = 24 * 3600000;     // refresh on boot if older than a day

// ── Canary: remembers that data existed on this device ────────────────────────
// Survives partial wipes (e.g. IDB evicted while localStorage remains) so we can
// tell the user what happened. A full wipe takes the canary with it; nothing
// inside the origin can detect that, which is exactly why backups and the
// native apps matter.
function _updateCanary() {
  try {
    const n = (typeof getEntries === 'function') ? getEntries().length : 0;
    const prev = JSON.parse(localStorage.getItem('frj_canary') || 'null');
    // Only lower the recorded count after a week, so a deliberate journal
    // deletion eventually clears the warning but a sudden wipe does not.
    if (!prev || n >= prev.entries || Date.now() - (prev.at || 0) > 7 * 86400000) {
      localStorage.setItem('frj_canary', JSON.stringify({ entries: n, at: Date.now() }));
    }
  } catch (e) { /* non-fatal */ }
}

async function _storageIntegrityIssue() {
  try {
    const canary = JSON.parse(localStorage.getItem('frj_canary') || 'null');
    if (!canary || !(canary.entries > 0)) return null;
    const entryCount = (typeof getEntries === 'function') ? getEntries().length : 0;
    if (entryCount > 0) return null;
    const keypair = (typeof idbGet === 'function') ? await idbGet('frj_keypair_pub').catch(() => null) : null;
    if (!keypair) return canary.entries;   // journal AND identity gone, but canary survived
  } catch (e) { /* fall through */ }
  return null;
}

// ── Environment checks ────────────────────────────────────────────────────────
async function _persistenceGranted() {
  try {
    if (!navigator.storage?.persisted) return null;
    return await navigator.storage.persisted();
  } catch (e) { return null; }
}

async function _looksLikePrivateMode() {
  try {
    if (!navigator.storage?.estimate) return false;
    const est = await navigator.storage.estimate();
    // Normal quotas are tens of GB; private/restricted modes are far smaller
    return !!est.quota && est.quota < 150 * 1024 * 1024;
  } catch (e) { return false; }
}

function _isIosBrowserTab() {
  const ua = navigator.userAgent;
  const isIos = /iPad|iPhone|iPod/.test(ua) || (ua.includes('Mac') && navigator.maxTouchPoints > 1);
  const standalone = window.matchMedia?.('(display-mode: standalone)').matches || window.navigator.standalone === true;
  return isIos && !standalone;
}

// ── Banner ────────────────────────────────────────────────────────────────────
function _shbDismissed(key) {
  try { return localStorage.getItem(_SHB_DISMISS_PREFIX + key) === '1'; } catch (e) { return false; }
}

function shbDismiss(key) {
  try { localStorage.setItem(_SHB_DISMISS_PREFIX + key, '1'); } catch (e) { /* ignore */ }
  const el = document.getElementById('storage-health-banner');
  if (el) { el.style.display = 'none'; el.innerHTML = ''; }
}

function _shbShow(key, cls, html, dismissible) {
  const el = document.getElementById('storage-health-banner');
  if (!el) return;
  el.innerHTML = `<div class="shb ${cls}">
    <div class="shb-text">${html}</div>
    ${dismissible ? `<button class="shb-close" data-action="shbDismiss" data-a0="${key}" aria-label="Dismiss">&times;</button>` : ''}
  </div>`;
  el.style.display = '';
}

// ── Automatic backup file (File System Access API, desktop Chrome/Edge) ──────
async function _autoBackupWriteNow() {
  try {
    if (typeof idbMetaGet !== 'function' || typeof _buildEncryptedBackupBlob !== 'function') return false;
    const handle = await idbMetaGet('autoBackupHandle');
    if (!handle) return false;
    const key = (typeof getSessionKey === 'function') ? getSessionKey() : null;
    if (!key) return false;
    if ((await handle.queryPermission({ mode: 'readwrite' })) !== 'granted') return false;
    const blob = await _buildEncryptedBackupBlob(key);
    const w = await handle.createWritable();
    await w.write(blob);
    await w.close();
    localStorage.setItem('frj_autobackup_last', String(Date.now()));
    const st = document.getElementById('autobackup-status');
    if (st) st.textContent = 'Last written: just now';
    return true;
  } catch (e) {
    console.warn('[storagehealth] auto-backup write failed:', e);
    return false;
  }
}

async function autoBackupSetup() {
  if (!window.showSaveFilePicker) {
    toast('Automatic backup files need desktop Chrome or Edge. Use Export Backup instead.', true);
    return;
  }
  const key = (typeof getSessionKey === 'function') ? getSessionKey() : null;
  if (!key) {
    toast('Set up encryption first. Automatic backups are always encrypted.', true);
    return;
  }
  try {
    const handle = await showSaveFilePicker({
      suggestedName: 'forestorers-autobackup.forestorers',
      types: [{ description: 'Forestorers backup', accept: { 'application/octet-stream': ['.forestorers'] } }]
    });
    if ((await handle.requestPermission({ mode: 'readwrite' })) !== 'granted') {
      toast('Permission to write the backup file was not granted.', true);
      return;
    }
    await idbMetaPut('autoBackupHandle', handle);
    await _autoBackupWriteNow();
    toast('Automatic backup file set. It refreshes in the background as you use the app.');
    if (typeof renderSettingsPage === 'function') renderSettingsPage();
  } catch (e) {
    if (e.name !== 'AbortError') toast('Could not set up the backup file: ' + e.message, true);
  }
}

async function autoBackupDisable() {
  try {
    if (typeof idbMetaPut === 'function') await idbMetaPut('autoBackupHandle', null);
    localStorage.removeItem('frj_autobackup_last');
    toast('Automatic backup file disabled. The file itself is untouched.');
    if (typeof renderSettingsPage === 'function') renderSettingsPage();
  } catch (e) { toast('Could not disable: ' + e.message, true); }
}

function _autoBackupMaybe(minAge) {
  const last = parseInt(localStorage.getItem('frj_autobackup_last') || '0', 10);
  if (Date.now() - last < minAge) return;
  _autoBackupWriteNow();
}

// ── Init (called late in app init, after storage + encryption are ready) ─────
async function storageHealthInit() {
  try {
    const entryCount = (typeof getEntries === 'function') ? getEntries().length : 0;

    // Detect a partial wipe BEFORE refreshing the canary
    const lostEntries = await _storageIntegrityIssue();

    if (lostEntries) {
      _shbShow('wiped', 'shb-danger',
        `This browser appears to have cleared Forestorers' stored data. A journal with ${lostEntries} update${lostEntries !== 1 ? 's' : ''} was here before. Restore it from a backup file, or re-pair with your other device.
        <button class="btn btn-primary btn-sm" data-action="importBackupPrompt" style="margin-left:8px">Import backup</button>`, true);
    } else if (
      // app.js's first-run notice already flags private mode until dismissed;
      // re-raise it here only once that notice is gone, because the data loss is real
      localStorage.getItem('frj_swDismissed') === '1' &&
      (typeof _detectPrivateMode === 'function' ? await _detectPrivateMode() : await _looksLikePrivateMode())
    ) {
      _shbShow('private', 'shb-danger',
        'You appear to be in private browsing. Everything saved here will be deleted when this window closes. Use a normal window for your journal.', true);
    } else if (_isIosBrowserTab() && !_shbDismissed('ios')) {
      _shbShow('ios', 'shb-warn',
        'On iPhone and iPad, Safari can delete this journal after 7 days of not visiting. Installing the app prevents that: tap the Share button, then "Add to Home Screen".', true);
    } else {
      const persisted = await _persistenceGranted();
      if (persisted === false && entryCount > 0 && !_shbDismissed('persist')) {
        _shbShow('persist', 'shb-warn',
          `Your browser has not promised to keep this data permanently and may clear it to free space. Export a backup and consider pairing a second device.
          <button class="btn btn-sm" data-action="exportBackup" style="margin-left:8px">Export backup</button>`, true);
      }
    }

    _updateCanary();

    // Background auto-backup: on boot if stale, then on tab-hide at most hourly
    _autoBackupMaybe(_AUTOBACKUP_INIT_AGE);
    document.addEventListener('visibilitychange', () => {
      if (document.visibilityState === 'hidden') _autoBackupMaybe(_AUTOBACKUP_MIN_INTERVAL);
    });
  } catch (e) {
    console.warn('[storagehealth] init failed:', e);
  }
}
