'use strict';
// ═══════════════════════════════════════════════
// js/devices.js — Sprint 1: Device pairing & paired-device management
// ═══════════════════════════════════════════════
//
// Pairing code format:
//   FRP1:<base64url(JSON({v,type,priv,pub,deviceId,deviceLabel,expiresAt,nonce}))>
//
// Key properties:
//   - 5-minute countdown shown in UI (expiry NOT checked on import — nonce is the replay guard)
//   - Single-use nonce (stored per-device to prevent replay)
//   - Shares the ECDH identity keypair (sync key derived deterministically via derivePairSyncKey)
//   - All key material is transmitted only via on-screen display (never over the network)
//   - Works fully offline (no QR library needed — text code is primary UX)

const PAIR_EXPIRY_MS = 300_000; // 5 minutes — nonce is the real replay guard; expiry is UI only

// ── Device label (human-readable, auto-detected) ─────────────────────────────
function getDeviceLabel() {
  const ua = navigator.userAgent;
  if (/iPhone/i.test(ua))  return 'iPhone';
  if (/iPad/i.test(ua))    return 'iPad';
  if (/Android/i.test(ua)) return /Mobile/i.test(ua) ? 'Android Phone' : 'Android Tablet';
  if (/Macintosh/i.test(ua)) return 'Mac';
  if (/Windows/i.test(ua))   return 'Windows PC';
  if (/Linux/i.test(ua))     return 'Linux';
  return 'Browser';
}

// ── Paired devices list (stored in IDB meta) ──────────────────────────────────
// Each entry: { deviceId, label, addedAt, pending? }
async function getPairedDevices() {
  return (await idbMetaGet('paired_devices')) || [];
}
async function savePairedDevices(arr) {
  await idbMetaPut('paired_devices', arr);
}

// ── Generate pairing code ─────────────────────────────────────────────────────
async function generatePairingCode(encPw) {
  const privRaw = await idbGet('frj_keypair_priv');
  const pubRaw  = await idbGet('frj_keypair_pub');
  if (!privRaw || !pubRaw) throw new Error('No identity keypair. Try refreshing the app.');

  const deviceId    = await getDeviceId();
  const deviceLabel = getDeviceLabel();

  const expiresAt = Date.now() + PAIR_EXPIRY_MS;
  const nonce     = uuid();

  const payload = {
    v:           1,
    type:        'pair',
    priv:        b64FromBytes(privRaw instanceof ArrayBuffer ? privRaw : privRaw),
    pub:         b64FromBytes(pubRaw  instanceof ArrayBuffer ? pubRaw  : pubRaw),
    deviceId,
    deviceLabel,
    expiresAt,
    nonce,
    hasEncryption: !!(encPw)
  };

  // Include encryption salt so Device B uses the same salt — required for password change sync
  if (encPw && typeof idbMetaGet === 'function') {
    const saltArr = await idbMetaGet('encryptionSalt').catch(() => null);
    if (saltArr) payload.encSalt = saltArr;
  }

  // Store pending pair so this device can recognize when pairing was completed
  await idbMetaPut('pending_pair', { nonce, expiresAt, peerDeviceId: null });

  const json = JSON.stringify(payload);
  const code = 'FRP1:' + toUrlSafeB64(b64FromBytes(new TextEncoder().encode(json)));

  return { code, expiresAt, deviceId };
}

// ── Import / accept a pairing code (device B) ─────────────────────────────────
async function importPairingCode(code) {
  // Normalize: strip all whitespace (iOS may insert spaces), fix prefix case (iOS autocorrect)
  const normalized = code.replace(/\s+/g, '').replace(/^frp1:/i, 'FRP1:');
  const m = normalized.match(/^FRP1:([A-Za-z0-9_\-]+)$/);
  if (!m) throw new Error('Invalid pairing code. Must start with FRP1:');
  code = normalized;

  let payload;
  try {
    payload = JSON.parse(new TextDecoder().decode(bytesFromB64(m[1])));
  } catch(e) {
    throw new Error('Pairing code is corrupted or truncated.');
  }

  if (payload.v !== 1 || payload.type !== 'pair') {
    throw new Error('Unrecognised pairing code format.');
  }
  // Expiry check — reject codes older than 10 minutes (double the UI timer for clock skew)
  if (payload.expiresAt && Date.now() > payload.expiresAt + 300000) {
    throw new Error('This pairing code has expired. Generate a new one.');
  }
  // Single-use nonce check
  const usedNonces = (await idbMetaGet('used_pair_nonces')) || [];
  if (usedNonces.includes(payload.nonce)) {
    throw new Error('This pairing code has already been used. Generate a new one.');
  }

  // Validate keypair bytes before storing
  const privBytes = Uint8Array.from(atob(payload.priv), c => c.charCodeAt(0));
  const pubBytes  = Uint8Array.from(atob(payload.pub),  c => c.charCodeAt(0));
  try {
    await crypto.subtle.importKey('pkcs8', privBytes, { name: 'ECDH', namedCurve: 'P-256' }, false, ['deriveKey']);
    await crypto.subtle.importKey('raw',   pubBytes,  { name: 'ECDH', namedCurve: 'P-256' }, true, []);
  } catch(e) {
    throw new Error('Pairing code contains invalid key data.');
  }

  // Import the shared identity keypair
  await idbPut('frj_keypair_priv', privBytes.buffer);
  await idbPut('frj_keypair_pub',  pubBytes.buffer);
  if (typeof _identityCache !== 'undefined') _identityCache = null;

  // Mark nonce used
  usedNonces.push(payload.nonce);
  if (usedNonces.length > 1000) usedNonces.shift();
  await idbMetaPut('used_pair_nonces', usedNonces);

  // Record the source device
  const paired = await getPairedDevices();
  const myDeviceId = await getDeviceId();
  if (payload.deviceId !== myDeviceId && !paired.find(p => p.deviceId === payload.deviceId)) {
    paired.push({
      deviceId:    payload.deviceId,
      label:       payload.deviceLabel || 'Other device',
      addedAt:     new Date().toISOString(),
      pending:     false
    });
    await savePairedDevices(paired);
  }

  // Stash peer's encryption salt so setupEncryptionFromPairing can use it
  if (payload.encSalt && Array.isArray(payload.encSalt) && payload.encSalt.length === 16 &&
      payload.encSalt.every(b => Number.isInteger(b) && b >= 0 && b <= 255) &&
      typeof idbMetaPut === 'function') {
    await idbMetaPut('peerEncSalt', payload.encSalt);
  }

  return { deviceId: payload.deviceId, label: payload.deviceLabel, nonce: payload.nonce, hasEncryption: !!payload.hasEncryption };
}

// ── Called on device A when device B completes pairing (via signal server) ────
// p2p.js calls this by name when it receives a 'peer_paired' message.
async function onPeerPaired(deviceId, label) {
  const pendingPair = await idbMetaGet('pending_pair');
  if (!pendingPair) return; // no pending pair on this device
  // Validate expiry (with 60s clock skew tolerance) to prevent stale pending records
  if (pendingPair.expiresAt && Date.now() > pendingPair.expiresAt + 60000) {
    await idbMetaPut('pending_pair', null);
    return;
  }

  const paired = await getPairedDevices();
  if (!paired.find(p => p.deviceId === deviceId)) {
    paired.push({
      deviceId,
      label:   label || 'Device',
      addedAt: new Date().toISOString(),
      pending: false
    });
    await savePairedDevices(paired);
  }

  // Consume the pending pair record
  await idbMetaPut('pending_pair', null);

  // Re-register with the signal server now that we have a paired device
  if (typeof _sigRegisterDevice === 'function') _sigRegisterDevice();

  // Start sync engine (was skipped at init since paired list was empty then)
  if (typeof syncInit === 'function') syncInit();

  renderDevicesList();
  if (typeof toast === 'function') toast(`"${label || 'Device'}" paired. Syncing…`);
}

// ── Remove a paired device ────────────────────────────────────────────────────
async function removePairedDevice(deviceId) {
  const paired = await getPairedDevices();
  const device = paired.find(p => p.deviceId === deviceId);
  if (!device) return;

  const online = typeof getOnlineDeviceIds === 'function' && getOnlineDeviceIds().has(deviceId);

  // Always allow force remove — device might be unreachable even if "online"
  {
    confirm2(
      `Remove "${device.label}"?`,
      online
        ? `Remove this device from your paired list? A notification will be sent to unpair it.`
        : `That device is offline. It will be removed from your list now.`,
      async () => {
        // Best-effort notify (may be received later)
        if (typeof sendDeviceRemovedMsg === 'function') sendDeviceRemovedMsg(deviceId).catch(() => {});
        const updated = paired.filter(p => p.deviceId !== deviceId);
        await savePairedDevices(updated);
        // Clear the pending password change safeguard if no more peers
        if (typeof _hasPairedDevices === 'function') {
          const stillHasPeers = await _hasPairedDevices();
          if (!stillHasPeers) {
            const pending = await idbMetaGet('pending_pw_change');
            if (pending) {
              await applyPendingPwChange();
              toast('Password change applied. No more paired devices.');
            }
          }
        }
        renderDevicesList();
        toast(`"${device.label}" removed`);
      }
    );
  }
}

// ── UI: render the "My Devices" section ──────────────────────────────────────
async function renderDevicesList() {
  const el = document.getElementById('devices-list');
  if (!el) return;

  const paired   = await getPairedDevices();
  const myId     = await getDeviceId();
  const myLabel  = getDeviceLabel();
  const pending  = await idbMetaGet('pending_pair');
  const isPendingValid = pending && pending.expiresAt > Date.now();

  let html = `
    <div style="display:flex;flex-direction:column;gap:6px">
      <!-- This device -->
      <div style="display:flex;align-items:center;justify-content:space-between;padding:7px 10px;background:var(--surface2);border:1px solid var(--border2);border-radius:6px">
        <div>
          <span style="font-family:var(--mono);font-size:12px;font-weight:600">${esc(myLabel)}</span>
          <span style="font-size:10px;color:var(--green,#22c55e);margin-left:6px;font-family:var(--mono)">this device</span>
        </div>
        <span style="font-size:10px;color:var(--muted2);font-family:var(--mono)">${myId.slice(0, 8)}…</span>
      </div>`;

  for (const d of paired) {
    html += `
      <div style="display:flex;align-items:center;justify-content:space-between;padding:7px 10px;background:var(--surface2);border:1px solid var(--border2);border-radius:6px">
        <div>
          <span style="font-family:var(--mono);font-size:12px">${esc(d.label)}</span>
          <span style="font-size:10px;color:var(--muted2);margin-left:6px;font-family:var(--mono)">added ${timeAgo(d.addedAt)}</span>
        </div>
        <button class="btn btn-ghost btn-sm" style="color:var(--red)" data-action="removePairedDevice" data-a0="${esc(d.deviceId)}">Remove</button>
      </div>`;
  }

  html += `
    </div>
    <div style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap">
      <button class="btn btn-ghost btn-sm" data-action="openPairModal">+ Add device</button>
      <button class="btn btn-ghost btn-sm" data-action="openJoinPairModal">I have a pairing code</button>
    </div>`;

  el.innerHTML = html;
}

// ── Modal: show pairing code (device A shows this) ───────────────────────────
let _pairTimer = null;

async function openPairModal() {
  const encEnabled = (typeof idbMetaGet === 'function') ? await idbMetaGet('encryptionEnabled') : false;
  if (!encEnabled) {
    if (typeof confirm2 === 'function') {
      confirm2(
        'Encryption required',
        'Cross-device sync requires encryption to be enabled. Your data must be encrypted before it can be synced to another device.',
        () => { if (typeof showMigrationModal === 'function') showMigrationModal(); }
      );
    }
    return;
  }
  const overlay = document.getElementById('pair-modal');
  if (!overlay) return;
  overlay.classList.add('active');
  await _startPairingDisplay();
}

async function _startPairingDisplay() {
  const statusEl   = document.getElementById('pair-status');
  const codeEl     = document.getElementById('pair-code-text');
  const countEl    = document.getElementById('pair-countdown');
  const copyBtn    = document.getElementById('pair-copy-btn');
  const refreshEl  = document.getElementById('pair-refresh');
  const encRow     = document.getElementById('pair-enc-row');
  const encPwEl    = document.getElementById('pair-enc-pw');
  const encPwErr   = document.getElementById('pair-enc-pw-err');
  const codeSec    = document.getElementById('pair-code-section');
  if (!codeEl) return;

  clearInterval(_pairTimer);
  if (statusEl)  statusEl.textContent  = '';
  if (refreshEl) refreshEl.style.display = 'none';

  const encEnabled = await idbMetaGet('encryptionEnabled').catch(() => false);

  if (encEnabled) {
    // Password gate: show password input, hide code until authenticated
    if (encRow)   encRow.style.display  = '';
    if (codeSec)  codeSec.style.display = 'none';
    if (codeEl)   codeEl.value          = ''; // clear any stale code from previous session
    if (encPwEl)  encPwEl.value         = '';
    if (encPwErr) encPwErr.textContent  = '';
    // Reset the generate button
    const genBtn = document.querySelector('#pair-enc-row button');
    if (genBtn) { genBtn.disabled = false; genBtn.textContent = 'Generate Code'; }
    setTimeout(() => encPwEl?.focus(), 60);
  } else {
    // No encryption — generate code immediately
    if (encRow)  encRow.style.display  = 'none';
    if (codeSec) codeSec.style.display = '';
    await _generateAndShowCode(null);
  }
}

// Shared helper: generate a pairing code (with optional password) and start the countdown UI
async function _generateAndShowCode(pw) {
  const codeEl    = document.getElementById('pair-code-text');
  const countEl   = document.getElementById('pair-countdown');
  const copyBtn   = document.getElementById('pair-copy-btn');
  const refreshEl = document.getElementById('pair-refresh');
  const codeSec   = document.getElementById('pair-code-section');
  if (!codeEl) return;

  if (codeSec) codeSec.style.display = '';

  // Device A must be online to receive the peer_paired notification from the signal server
  localStorage.setItem('frj_p2p_offline', '0');
  if (typeof p2pSetOffline === 'function') p2pSetOffline(false);

  let result;
  try {
    result = await generatePairingCode(pw || undefined);
    if (typeof _sigRegisterDevice === 'function') _sigRegisterDevice();
  } catch(e) {
    codeEl.value = 'Error: ' + e.message;
    return;
  }

  const { code, expiresAt } = result;
  codeEl.value = code;
  if (copyBtn) {
    copyBtn.disabled = false;
    copyBtn.onclick  = () => {
      navigator.clipboard?.writeText(code).catch(() => {});
      copyBtn.textContent = 'Copied ✓';
      setTimeout(() => { copyBtn.textContent = 'Copy'; }, 2000);
    };
  }

  clearInterval(_pairTimer);
  const tick = () => {
    const ms   = expiresAt - Date.now();
    const secs = Math.max(0, Math.ceil(ms / 1000));
    if (countEl) countEl.textContent = secs + 's';
    if (secs === 0) {
      clearInterval(_pairTimer);
      if (codeEl)    codeEl.value         = '(expired)';
      if (countEl)   countEl.textContent  = '0s';
      if (copyBtn)   copyBtn.disabled     = true;
      if (refreshEl) refreshEl.style.display = '';
    }
  };
  tick();
  _pairTimer = setInterval(tick, 500);
}

// Called when device A clicks "Generate Code" — verifies password then shows code
async function _pairApplyEncPw() {
  const encPwEl  = document.getElementById('pair-enc-pw');
  const encPwErr = document.getElementById('pair-enc-pw-err');
  if (!encPwEl) return;
  const pw = encPwEl.value.trim();
  if (!pw) { if (encPwErr) encPwErr.textContent = 'Enter your encryption password.'; return; }
  if (encPwErr) encPwErr.textContent = '';

  const genBtn = document.querySelector('#pair-enc-row button');
  if (genBtn) { genBtn.disabled = true; genBtn.textContent = 'Checking…'; }

  try {
    const ok = await verifyEncryptionPassword(pw);
    if (!ok) {
      if (encPwErr) encPwErr.textContent = 'Incorrect password.';
      if (genBtn) { genBtn.disabled = false; genBtn.textContent = 'Generate Code'; }
      return;
    }
    encPwEl.value = ''; // clear password from UI immediately
    if (genBtn) { genBtn.disabled = false; genBtn.textContent = '✓ Authenticated'; }
    await _generateAndShowCode(pw);
  } catch(e) {
    if (encPwErr) encPwErr.textContent = 'Error: ' + e.message;
    if (genBtn) { genBtn.disabled = false; genBtn.textContent = 'Generate Code'; }
  }
}

function closePairModal() {
  clearInterval(_pairTimer);
  _pairTimer = null;
  const overlay = document.getElementById('pair-modal');
  if (overlay) overlay.classList.remove('active');
  // Don't wipe pending_pair here — peer_paired may arrive after the modal is
  // closed (e.g. user closes modal to paste code on Device B). The expiry
  // check in onPeerPaired() already prevents stale nonces from being accepted.
}

// ── Modal: enter pairing code (device B shows this) ──────────────────────────
async function openJoinPairModal() {
  const overlay = document.getElementById('join-pair-modal');
  if (!overlay) return;
  overlay.classList.add('active');
  const input   = document.getElementById('join-pair-input');
  const btn     = document.getElementById('join-pair-btn');
  const err     = document.getElementById('join-pair-err');
  const success = document.getElementById('join-pair-success');
  if (input)   input.value    = '';
  if (err)     err.textContent = '';
  if (success) success.style.display = 'none';
  if (btn)     { btn.disabled = false; btn.textContent = 'Pair'; }
  setTimeout(() => input?.focus(), 60);
}

function closeJoinPairModal() {
  const overlay    = document.getElementById('join-pair-modal');
  const confirmRow = document.getElementById('join-pair-confirm');
  const btn        = document.getElementById('join-pair-btn');
  if (overlay) overlay.classList.remove('active');
  if (confirmRow) confirmRow.style.display = 'none';
  if (btn) { btn.disabled = false; btn.textContent = 'Pair this device'; }
}

async function submitJoinPairCode() {
  const input      = document.getElementById('join-pair-input');
  const btn        = document.getElementById('join-pair-btn');
  const err        = document.getElementById('join-pair-err');
  const success    = document.getElementById('join-pair-success');
  const confirmRow = document.getElementById('join-pair-confirm');
  if (!input) return;
  if (err) err.textContent = '';
  const code = (input.value || '').trim();
  if (!code) { if (err) err.textContent = 'Paste the pairing code from your other device.'; return; }

  // First press: show inline confirm row (confirm2 sits behind this overlay's z-index)
  if (confirmRow && confirmRow.style.display === 'none') {
    confirmRow.style.display = '';
    if (btn) btn.textContent = 'Yes, pair this device';
    return;
  }

  if (btn) { btn.disabled = true; btn.textContent = 'Pairing…'; }
  try {
    const result    = await importPairingCode(code);
    const myId      = await getDeviceId();
    const myLabel   = getDeviceLabel();
    // Device sync requires the signal WS — force P2P online so it connects after reload
    // and sends device_pair_claim to device A (notify even if pair_complete was lost)
    localStorage.setItem('frj_p2p_offline', '0');
    // Mark that on next load this device should do a full pull from device A
    await idbMetaPut('pendingInitialSync', result.deviceId);
    // Notify device A (the code generator) of our UUID before we reload.
    // Device A will add us to its paired list and re-register with the signal server.
    if (typeof _sigSendPairComplete === 'function') {
      await _sigSendPairComplete(result.nonce, myId, myLabel);
    }
    if (success) {
      success.style.display = '';
      success.textContent   = `Paired with "${result.label}" ✓ Getting your data…`;
    }
    renderDevicesList();
    if (result.hasEncryption) {
      // Peer device uses encryption — prompt user for their password to adopt it
      closeJoinPairModal();
      _promptPairingEncryptionPassword();
    } else {
      // Re-initialize in-place instead of reloading — avoids WS race conditions.
      // p2pReinit reconnects the WS with the newly-imported identity, triggers
      // _sigRegisterDevice (which sends device_pair_claim), then syncInit starts
      // the initial data pull once the WS is confirmed open.
      closeJoinPairModal();
      _postPairInit();
    }
  } catch(e) {
    if (err) err.textContent = e.message;
    if (btn) { btn.disabled = false; btn.textContent = 'Pair this device'; }
    if (confirmRow) confirmRow.style.display = 'none';
  }
}

// ── Post-pair in-place init (no reload) ──────────────────────────────────────
async function _postPairInit() {
  try {
    // Reconnect WS with the newly-imported identity; this triggers _sigRegisterDevice
    // which sends device_pair_claim to Device A and triggers syncNow after 1.5s
    if (typeof p2pReinit === 'function') await p2pReinit();
    // Start the sync engine (detects pendingInitialSync and begins full pull)
    if (typeof syncInit === 'function') await syncInit();
    // Re-render UI with any data that arrived
    if (typeof renderDash === 'function') renderDash();
    if (typeof renderEntryList === 'function') renderEntryList();
    renderDevicesList();
  } catch(e) {
    console.warn('[devices] _postPairInit error:', e);
    // Fallback: reload if in-place init fails
    location.reload();
  }
}

// ── Device status UI (header pill) ───────────────────────────────────────────

async function updateDeviceStatusUI() {
  const dot = document.getElementById('dotDevices');
  const btn = document.getElementById('deviceStatusBtn');
  const lbl = document.getElementById('deviceLabel');
  if (!dot) return;

  const paired = await getPairedDevices();
  const onlineIds = (typeof getOnlineDeviceIds === 'function') ? getOnlineDeviceIds() : new Set();
  const onlineCount = paired.filter(d => onlineIds.has(d.deviceId)).length;

  dot.classList.toggle('on', onlineCount > 0);
  if (btn) btn.title = onlineCount > 0
    ? `${onlineCount} device${onlineCount !== 1 ? 's' : ''} online`
    : paired.length ? 'My devices: none online' : 'My devices: none paired';

  const chipLbl = document.getElementById('deviceChipLabel');
  if (chipLbl) {
    if (onlineCount > 0) { chipLbl.textContent = onlineCount; chipLbl.style.display = ''; }
    else chipLbl.style.display = 'none';
  }
}

// ── Device dropdown (header) ──────────────────────────────────────────────────

function toggleDeviceDropdown() {
  const dd = document.getElementById('deviceDropdown');
  if (!dd) return;
  const open = dd.style.display !== 'none';
  // Close buddy dropdown if open
  const bd = document.getElementById('buddyDropdown');
  if (bd) bd.style.display = 'none';
  if (open) {
    dd.style.display = 'none';
    _hideDropdownBackdrop();
  } else {
    dd.style.display = '';
    _showDropdownBackdrop();
    _renderDeviceDropdown();
  }
}

function closeDeviceDropdown() {
  const dd = document.getElementById('deviceDropdown');
  if (dd) dd.style.display = 'none';
  _hideDropdownBackdrop();
}

async function _renderDeviceDropdown() {
  const dd = document.getElementById('deviceDropdown');
  if (!dd) return;

  const paired = await getPairedDevices();
  const onlineIds = (typeof getOnlineDeviceIds === 'function') ? getOnlineDeviceIds() : new Set();
  const syncState = (await idbMetaGet('sync_state')) || {};

  const hdrRow = `<div style="display:flex;align-items:center;padding:10px 14px 8px;border-bottom:1px solid var(--border2)">
    <span style="font-family:var(--mono);font-size:11px;font-weight:600;flex:1">My Devices</span>
    <button data-action="closeDeviceDropdown" style="background:none;border:none;cursor:pointer;font-size:16px;color:var(--muted2);padding:0 0 0 8px;line-height:1">✕</button>
  </div>`;

  // Network toggle -- master switch for device sync (and buddies too, but from
  // this surface we describe it as the device-sync gate).
  const netOn = (typeof getNetworkToggles === 'function') && getNetworkToggles().network;
  const netRow = `<button id="deviceDropdown-toggle-network" type="button" class="dd-toggle-row${netOn ? ' on' : ''}" data-action="toggleNetworkBtn">
    <span class="dd-toggle-dot"></span>
    <span class="dd-toggle-label">Sync with my devices</span>
    <span class="dd-toggle-state">${netOn ? 'ON' : 'OFF'}</span>
  </button>`;

  if (!paired.length) {
    dd.innerHTML = hdrRow + netRow + `<div style="padding:12px 14px;font-family:var(--mono);font-size:11px;color:var(--muted2)">No paired devices.<br>Use Profile → Add Device to pair.</div>`;
    return;
  }

  // Sort: online first, then by addedAt descending
  const sorted = [...paired].sort((a, b) => {
    const aOn = onlineIds.has(a.deviceId) ? 1 : 0;
    const bOn = onlineIds.has(b.deviceId) ? 1 : 0;
    if (aOn !== bOn) return bOn - aOn;
    return (b.addedAt || 0) - (a.addedAt || 0);
  });

  const rows = sorted.map(d => {
    const online = onlineIds.has(d.deviceId);
    const dotColor = online ? 'var(--green)' : 'var(--muted2)';
    const lastSyncTs = syncState[d.deviceId];
    const lastSync = lastSyncTs ? timeAgo(new Date(lastSyncTs)) : 'never';
    const eName = esc(d.label || 'Device');
    const eId = d.deviceId.replace(/'/g, '');
    return `<div style="padding:8px 12px;display:flex;align-items:center;gap:8px;border-bottom:1px solid var(--border2)">
      <span style="width:8px;height:8px;border-radius:50%;background:${dotColor};flex-shrink:0;display:inline-block"></span>
      <span style="flex:1;font-size:12px;font-weight:500">${eName}</span>
      <span style="font-family:var(--mono);font-size:11px;color:var(--muted2)">${lastSync}</span>
      <button class="btn-icon" data-action="removePairedDeviceDropdown" data-a0="${eId}" title="Remove">×</button>
    </div>`;
  }).join('');

  dd.innerHTML = hdrRow + netRow + rows + `<div style="padding:8px 12px">
    <button class="btn btn-ghost btn-sm" style="width:100%" data-action="syncAllDevices">↻ Sync all devices</button>
  </div>`;
}

// Light-touch updater for when the network toggle flips while the device
// dropdown is mounted: avoids a full re-render (which would re-fetch paired
// devices) and just flips the existing row's classes + text.
function _renderDeviceDropdownToggle() {
  const row = document.getElementById('deviceDropdown-toggle-network');
  if (!row) return;
  const netOn = (typeof getNetworkToggles === 'function') && getNetworkToggles().network;
  row.classList.toggle('on', netOn);
  const stateEl = row.querySelector('.dd-toggle-state');
  if (stateEl) stateEl.textContent = netOn ? 'ON' : 'OFF';
}

function _promptPairingEncryptionPassword() {
  // Show a simple modal asking the user to enter their encryption password
  // so we can configure at-rest encryption on this device
  let overlay = document.getElementById('pairingEncPwModal');
  if (!overlay) {
    overlay = document.createElement('div');
    overlay.id = 'pairingEncPwModal';
    overlay.className = 'modal-overlay';
    document.body.appendChild(overlay);
  }
  overlay.innerHTML = `<div class="modal-box" style="max-width:340px">
    <div class="modal-hdr"><span>🔒 Set up encryption</span></div>
    <div class="modal-body" style="padding:16px 18px">
      <p style="font-size:13px;color:var(--muted);margin:0 0 14px;line-height:1.5">Your primary device uses encryption. Enter your encryption password to protect data on this device too. Encryption is required for multi-device sync.</p>
      <input type="password" id="pairing-enc-pw-input" placeholder="Encryption password" autocomplete="current-password"
        style="width:100%;box-sizing:border-box;padding:10px 12px;background:var(--surface2);border:1px solid var(--border2);border-radius:6px;font-family:var(--mono);font-size:13px;color:var(--text);margin-bottom:8px">
      <div id="pairing-enc-pw-err" style="font-family:var(--mono);font-size:11px;color:var(--red);min-height:14px;margin-bottom:10px"></div>
      <div style="display:flex;gap:8px;justify-content:flex-end">
        <button class="btn btn-primary btn-sm" id="pairing-enc-pw-btn" data-action="applyPairingEncPw">Set up encryption</button>
      </div>
    </div>
  </div>`;
  overlay.classList.add('open');
  setTimeout(() => {
    const inp = document.getElementById('pairing-enc-pw-input');
    if (inp) inp.focus();
  }, 60);
}

function _skipPairingEncPw() {
  closeModal('pairingEncPwModal');
  toast('Pairing complete. Syncing…');
  _postPairInit();
}

async function _applyPairingEncPw() {
  const inp = document.getElementById('pairing-enc-pw-input');
  const err = document.getElementById('pairing-enc-pw-err');
  const btn = document.getElementById('pairing-enc-pw-btn');
  if (!inp || !err || !btn) return;
  const pw = inp.value;
  if (!pw) { err.textContent = 'Please enter your password.'; return; }
  btn.disabled = true; btn.textContent = 'Checking…';
  try {
    if (typeof setupEncryptionFromPairing === 'function') {
      await setupEncryptionFromPairing(pw);
      inp.value = ''; // clear password from UI
      const v = typeof getSchemaVersion === 'function' ? await getSchemaVersion() : null;
      if (v !== 2 && typeof runMigration === 'function') {
        btn.textContent = 'Encrypting…';
        err.textContent = '';
        err.style.color = 'var(--muted2)';
        try {
          await runMigration(({ current, complete }) => {
            if (err) err.textContent = current;
          });
          closeModal('pairingEncPwModal');
          toast('Encryption set up ✓ Syncing…');
          _postPairInit();
        } catch(e) {
          err.style.color = 'var(--red)';
          err.textContent = 'Encryption failed: ' + e.message;
          btn.disabled = false; btn.textContent = 'Retry';
        }
      } else {
        closeModal('pairingEncPwModal');
        toast('Encryption set up ✓ Syncing…');
        _postPairInit();
      }
    }
  } catch(e) {
    err.textContent = 'Incorrect password. Please try again.';
    btn.disabled = false; btn.textContent = 'Set up';
    inp.value = ''; inp.focus();
  }
}
