// daygig-boot.js — Boot, health, dashboard, settings, integrations, backup, reset

// ── Boot ──────────────────────────────────────────────────────────────────
  document.addEventListener('DOMContentLoaded', async () => {
    checkHealth();
    await loadSettings();
    if (typeof window.initNav === 'function') await window.initNav();
    loadDashboard();
    loadAlerts();       // populate the topbar alerts bell
    checkForUpdate();   // non-blocking; silent if disabled or offline
    if (typeof window.initWelcomeBanner === 'function') window.initWelcomeBanner();
    const today = new Date().toISOString().slice(0, 10);
    ['comp-deadline-date','comp-mileage-date'].forEach(id => { const el = document.getElementById(id); if (el) el.value = today; });
    ['comp-checklist-year','comp-packet-year'].forEach(id => { const el = document.getElementById(id); if (el) el.value = new Date().getFullYear(); });
    // Pre-fill the mileage rate with the IRS standard rate for the trip date's
    // year (year-aware — no more stale hardcoded value), and refresh it whenever
    // the user changes the trip date.
    dgSyncMileageRate();
    const mDate = document.getElementById('comp-mileage-date');
    if (mDate && !mDate._rateSyncBound) {
      mDate.addEventListener('change', dgSyncMileageRate);
      mDate._rateSyncBound = true;
    }
  });

  // Fetch the IRS standard mileage rate for the selected trip date and pre-fill
  // the rate field. Only overwrites a field the user hasn't typed into, so we
  // never clobber a rate they entered themselves.
  async function dgSyncMileageRate() {
    const rateEl = document.getElementById('comp-mileage-rate');
    if (!rateEl) return;
    if (rateEl.dataset.userEdited === '1') return;
    const dateEl = document.getElementById('comp-mileage-date');
    const d = (dateEl && dateEl.value) || new Date().toISOString().slice(0, 10);
    try {
      const r = await fetch('/api/compliance/mileage-rate?date=' + encodeURIComponent(d));
      const j = await r.json();
      if (j && j.ok && typeof j.rate === 'number') rateEl.value = j.rate.toFixed(2);
    } catch (_) { /* offline / older build — leave field as-is */ }
  }
  if (typeof window !== 'undefined') {
    window.dgSyncMileageRate = dgSyncMileageRate;
    // Once the user types in the rate, stop auto-syncing it.
    document.addEventListener('input', (e) => {
      if (e.target && e.target.id === 'comp-mileage-rate') e.target.dataset.userEdited = '1';
    });
  }

  // ── Alerts feed (topbar bell) ───────────────────────────────────────────────
  let _alertsData = null;
  async function loadAlerts() {
    try {
      const res = await fetch('/api/alerts');
      const d = await res.json();
      _alertsData = d;
      const badge = document.getElementById('alerts-badge');
      if (badge) {
        const n = d.total || 0;
        badge.textContent = n > 99 ? '99+' : String(n);
        badge.style.display = n > 0 ? 'block' : 'none';
        badge.style.background = (d.overdue_count > 0) ? 'var(--err)' : 'var(--warn,#b8860b)';
      }
    } catch (_) { /* offline / older build */ }
  }
  function toggleAlerts() {
    const panel = document.getElementById('alerts-panel');
    if (!panel) return;
    if (panel.style.display === 'block') { panel.style.display = 'none'; return; }
    const d = _alertsData || { low_stock: [], deadlines: [], pending_imports: 0, total: 0 };
    const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
    const sect = (title, items) => items.length
      ? `<div style="font-size:10px;font-family:var(--mono);text-transform:uppercase;color:var(--text-dim);margin:8px 2px 4px;">${title}</div>` + items.join('')
      : '';
    const ls = (d.low_stock || []).map(p =>
      `<div style="font-size:12px;padding:5px 6px;border-bottom:1px solid var(--border);">${esc(p.name)} <span style="color:var(--text-dim);">— ${p.on_hand} on hand (reorder at ${p.reorder_point})</span></div>`);
    const dl = (d.deadlines || []).map(x => {
      const when = x.overdue ? `<span style="color:var(--err);">overdue ${-x.days_until}d</span>` : `in ${x.days_until}d`;
      return `<div style="font-size:12px;padding:5px 6px;border-bottom:1px solid var(--border);">${esc(x.title)} <span style="color:var(--text-dim);">— ${when} (${esc(x.due_date)})</span></div>`;
    });
    const imp = d.pending_imports
      ? [`<div style="font-size:12px;padding:5px 6px;border-bottom:1px solid var(--border);">${d.pending_imports} transaction${d.pending_imports===1?'':'s'} waiting in the review queue</div>`] : [];
    const body = (d.total ? (sect('Deadlines', dl) + sect('Low stock', ls) + sect('Imports', imp))
      : '<div style="font-size:12px;color:var(--text-dim);padding:8px 6px;">Nothing needs attention.</div>');
    panel.innerHTML = `<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;"><strong style="font-size:13px;">Alerts</strong><button onclick="refreshAlerts()" style="background:transparent;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;">refresh</button></div>${body}`;
    panel.style.display = 'block';
  }
  async function refreshAlerts() {
    await loadAlerts();
    const panel = document.getElementById('alerts-panel');
    if (panel) { panel.style.display = 'none'; toggleAlerts(); }   // rebuild open
  }
  if (typeof window !== 'undefined') window.refreshAlerts = refreshAlerts;
  if (typeof window !== 'undefined') {
    window.loadAlerts = loadAlerts;
    window.toggleAlerts = toggleAlerts;
    document.addEventListener('click', (e) => {
      const panel = document.getElementById('alerts-panel');
      const bell = document.getElementById('alerts-bell');
      if (panel && panel.style.display === 'block' && bell &&
          !panel.contains(e.target) && !bell.contains(e.target)) {
        panel.style.display = 'none';
      }
    });
  }

  // ── Dashboard data ────────────────────────────────────────────────────────
  async function loadDashboard() {
    try {
      const res  = await fetch('/api/dashboard?period=monthly');
      const data = await res.json();
      const pos  = data.position || {};
      const isPersonalDash = !!data.is_personal;
      const pd = data.personal_dashboard || {};

      const cashCents = isPersonalDash && pd.cash_cents != null
                      ? Number(pd.cash_cents || 0)
                      : (pos.checking_cents || 0) + (pos.savings_cents || 0)
                        + (pos.stripe_cents   || 0) + (pos.square_cents  || 0)
                        + (pos.cash_cents     || 0);
      const cashEl = document.getElementById('stat-cash');
      if (cashEl) {
        cashEl.textContent = dgDashMoney(cashCents);
        cashEl.className   = 'stat-value' + (cashCents >= 0 ? ' positive' : ' negative');
        const cashNote = cashEl.nextElementSibling;
        if (cashNote) {
          if (data.pending_imports_count > 0) cashNote.textContent = `${data.pending_imports_count} rows pending reconcile`;
          else if ((data.ledger_txn_count || 0) > 0) cashNote.textContent = 'Across cash and bank accounts';
          else cashNote.textContent = 'No transactions yet';
        }
      }
      const revEl = document.getElementById('stat-revenue');
      if (revEl) {
        const rc = isPersonalDash ? (data.money_in_cents || data.personal_money_in_cents || 0) : (data.revenue_cents || 0);
        revEl.textContent = dgDashMoney(rc);
        revEl.className   = 'stat-value' + (rc > 0 ? ' positive' : '');
      }
      const expEl = document.getElementById('stat-expenses');
      if (expEl) {
        const ec = isPersonalDash ? (data.personal_spending_cents || data.money_out_cents || 0) : (data.money_out_cents || 0);
        expEl.textContent = dgDashMoney(ec);
        expEl.className   = 'stat-value' + (ec > 0 ? ' negative' : '');
      }
      const netEl = document.getElementById('stat-net');
      if (netEl) {
        const nc = isPersonalDash ? (data.personal_left_over_cents || data.net_cash_cents || 0) : (data.net_cash_cents || 0);
        netEl.textContent = dgDashMoney(nc);
        netEl.className   = 'stat-value' + (nc >= 0 ? ' positive' : ' negative');
      }
      const labels = data.labels || {};
      if (labels.revenue) { const el = document.getElementById('stat-revenue-label'); if (el) el.textContent = labels.revenue + ' (MTD)'; }
      if (labels.expenses) { const el = document.querySelector('#stat-expenses')?.previousElementSibling; if (el) el.textContent = labels.expenses + ' (MTD)'; }
      if (labels.net) { const el = document.getElementById('stat-net-label'); if (el) el.textContent = labels.net + ' (MTD)'; }

      const taxEl = document.getElementById('stat-tax');
      if (taxEl) {
        const salesTax = pos.sales_tax_cents || 0;
        const useTax   = pos.use_tax_cents   || 0;
        const totalTax = pos.total_tax_liability_cents || (salesTax + useTax);
        taxEl.textContent = dgDashMoney(totalTax);
        taxEl.className   = 'stat-value' + (totalTax > 0 ? ' negative' : '');
        const taxNote = document.getElementById('stat-tax-note');
        if (taxNote) {
          const parts = [];
          if (salesTax > 0) parts.push('Sales ' + dgMoneyC(salesTax));
          if (useTax   > 0) parts.push('Use '   + dgMoneyC(useTax));
          taxNote.textContent = parts.length ? parts.join(' · ') : 'None accrued';
        }
      }
      renderBusinessRecentActivity(data);
      renderPersonalDashboard(data);
    } catch (e) {
      console.warn('Dashboard load failed:', e);
    }
  }

  function renderBusinessRecentActivity(data) {
    const el = document.getElementById('dashboardRecentActivity');
    if (!el || data.is_personal) return;
    const rows = data.recent_activity || [];
    if (!rows.length) {
      el.className = 'empty-state';
      el.innerHTML = '<div class="es-icon" data-icon="inbox" data-icon-size="30"></div><div class="es-msg">No transactions recorded yet.<br>Import a bank CSV to get started.</div>';
      return;
    }
    el.className = 'activity-list';
    el.innerHTML = rows.map(row => `
      <div class="activity-row">
        <div class="activity-mark" aria-hidden="true">${dgDashEscape((row.source || 'L').slice(0, 1))}</div>
        <div class="activity-copy">
          <div class="activity-name">${dgDashEscape(row.description || 'Ledger entry')}</div>
          <div class="activity-meta">${dgDashEscape(row.date || '')} · ${dgDashEscape(row.source || 'Manual')}</div>
        </div>
        <div class="activity-amount">${dgDashEscape(row.amount || dgDashMoney(row.amount_cents))}</div>
      </div>`).join('');
  }

  function dgDashMoney(cents, showSign) {
    cents = Number(cents || 0);
    const abs = Math.abs(cents);
    const txt = '$' + (abs / 100).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    if (!showSign) return txt;
    return cents < 0 ? '-' + txt : txt;
  }

  function dgDashEscape(value) {
    return String(value ?? '').replace(/[&<>'"]/g, ch => ({ '&':'&amp;', '<':'&lt;', '>':'&gt;', "'":'&#39;', '"':'&quot;' }[ch]));
  }

  function dgEmptyDashboardLine(message) {
    return `<div style="padding:18px;text-align:center;color:var(--text-dim);font-size:13px;">${dgDashEscape(message)}</div>`;
  }

  function dgMiniAction(tab, subtab) {
    if (!tab) return '';
    if (tab === 'transactions') {
      return `showTab('transactions'); if (typeof txnTab === 'function') txnTab('${subtab || 'review'}');`;
    }
    return `showTab('${tab}');`;
  }

  function renderPersonalDashboard(data) {
    const pd = data.personal_dashboard || null;
    const isPersonal = !!data.is_personal;
    const shell = document.getElementById('personalDashboard');
    if (shell) shell.style.display = isPersonal && pd ? '' : 'none';
    if (!isPersonal || !pd) return;

    const setText = (id, text) => { const el = document.getElementById(id); if (el) el.textContent = text; };
    const setClass = (id, cents) => {
      const el = document.getElementById(id);
      if (el) el.className = 'stat-value' + (Number(cents || 0) >= 0 ? ' positive' : ' negative');
    };

    const period = pd.period || {};
    setText('personal-period-label', period.from && period.to ? `${period.from} – ${period.to}` : 'This month at a glance');
    setText('personal-money-in', dgDashMoney(pd.money_in_cents));
    setText('personal-money-out', dgDashMoney(pd.money_out_cents));
    setText('personal-left-over', dgDashMoney(pd.left_over_cents, true));
    setClass('personal-left-over', pd.left_over_cents);
    setText('personal-pending-count', pd.pending_review?.total || 0);
    setText('personal-pending-note', (pd.pending_review?.total || 0) ? `${pd.pending_review.money_in} in · ${pd.pending_review.money_out} out` : 'Nothing waiting');

    const catEl = document.getElementById('personal-category-list');
    const categories = pd.top_categories || [];
    if (catEl) {
      catEl.innerHTML = categories.length ? categories.map(c => {
        const pct = Number(c.pct || 0);
        return `<div>
          <div style="display:flex;justify-content:space-between;gap:12px;font-size:13px;margin-bottom:5px;">
            <span style="color:var(--text);font-weight:600;">${dgDashEscape(c.label || 'Category')}</span>
            <span style="font-family:var(--mono);color:var(--text);">${dgDashEscape(c.amount || dgDashMoney(c.cents))}</span>
          </div>
          <div style="height:7px;background:var(--surface2);border:1px solid var(--border);border-radius:999px;overflow:hidden;">
            <div style="height:100%;width:${Math.max(2, Math.min(100, pct))}%;background:var(--accent);"></div>
          </div>
          <div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);margin-top:4px;">${pct}% · ${c.txn_count || 0} transaction${(c.txn_count || 0) === 1 ? '' : 's'}</div>
        </div>`;
      }).join('') : dgEmptyDashboardLine('No categorized spending this month yet. If you already imported transactions, they may be from an earlier period — head to Transactions → Import to check.');
    }

    const attEl = document.getElementById('personal-attention-list');
    const attention = pd.attention || [];
    if (attEl) {
      attEl.innerHTML = attention.length ? attention.map(item => {
        const action = item.action_label ? `<button class="btn btn-ghost btn-sm" onclick="${dgMiniAction(item.action_tab, item.action_subtab)}">${dgDashEscape(item.action_label)}</button>` : '';
        return `<div style="border:1px solid var(--border);background:var(--surface2);border-radius:10px;padding:12px 14px;">
          <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:12px;">
            <div>
              <div style="font-size:13px;font-weight:700;color:var(--text);">${dgDashEscape(item.title)}</div>
              <div style="font-size:12px;color:var(--text-soft);line-height:1.55;margin-top:3px;">${dgDashEscape(item.detail)}</div>
            </div>
            ${action}
          </div>
        </div>`;
      }).join('') : dgEmptyDashboardLine('Nothing urgent right now.');
    }

    const largeEl = document.getElementById('personal-largest-expenses');
    const largest = pd.largest_expenses || [];
    if (largeEl) {
      largeEl.innerHTML = largest.length ? largest.map(x => `<div style="display:flex;justify-content:space-between;gap:12px;border-bottom:1px solid var(--border);padding:7px 0;">
        <div style="min-width:0;">
          <div style="font-size:13px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${dgDashEscape(x.description)}</div>
          <div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);">${dgDashEscape(x.date)} · ${dgDashEscape(x.category)}</div>
        </div>
        <div style="font-family:var(--mono);font-size:12px;color:var(--err);white-space:nowrap;">${dgDashEscape(x.amount || dgDashMoney(x.cents))}</div>
      </div>`).join('') : dgEmptyDashboardLine('No spending posted this month yet. If you imported transactions, they may be from an earlier period.');
    }

    const subEl = document.getElementById('personal-subscriptions');
    const subs = pd.subscription_candidates || [];
    if (subEl) {
      subEl.innerHTML = subs.length ? subs.map(x => `<div style="display:flex;justify-content:space-between;gap:12px;border-bottom:1px solid var(--border);padding:7px 0;">
        <div style="min-width:0;">
          <div style="font-size:13px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${dgDashEscape(x.label)}</div>
          <div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);">${dgDashEscape(x.category)} · seen ${x.count || 0}x · ${dgDashEscape(x.confidence || 'possible')}</div>
        </div>
        <div style="font-family:var(--mono);font-size:12px;color:var(--text);white-space:nowrap;">${dgDashEscape(x.avg_amount || x.last_amount || '')}</div>
      </div>`).join('') : dgEmptyDashboardLine('No recurring charge patterns yet. This will improve after more imports.');
    }
  }

  // ── Update check ──────────────────────────────────────────────────────────
  // Asks the server (which fetches your hosted version.json) whether a newer
  // build exists, and shows a dismissible banner with your download link.
  // Built with DOM methods so manifest text can't inject markup.
  async function checkForUpdate() {
    try {
      const res  = await fetch('/api/version-check');
      const data = await res.json();
      if (!data || !data.update_available) return;
      const banner = document.getElementById('update-banner');
      if (!banner) return;

      const bar = document.createElement('div');
      bar.style.cssText = 'display:flex;align-items:center;gap:12px;background:var(--accent-bg);border:1px solid var(--accent-ring);border-radius:10px;padding:10px 14px;margin-bottom:16px;';

      const msg = document.createElement('span');
      msg.style.cssText = 'font-size:13px;color:var(--text);';
      msg.textContent = `A new version of Daygig (v${data.latest || ''}) is available`
        + (data.notes ? ` — ${data.notes}` : '');
      bar.appendChild(msg);

      if (data.download_url) {
        const a = document.createElement('a');
        a.href = data.download_url;
        a.target = '_blank';
        a.rel = 'noopener';
        a.textContent = 'Download update';
        a.style.cssText = 'margin-left:auto;background:var(--accent);color:#06210f;font-size:12px;font-weight:600;text-decoration:none;border-radius:6px;padding:5px 12px;';
        bar.appendChild(a);
      }

      const x = document.createElement('button');
      x.textContent = '×';
      x.title = 'Dismiss';
      x.style.cssText = 'background:transparent;border:none;color:var(--text-dim);font-size:18px;cursor:pointer;line-height:1;' + (data.download_url ? '' : 'margin-left:auto;');
      x.onclick = () => { banner.style.display = 'none'; };
      bar.appendChild(x);

      banner.innerHTML = '';
      banner.appendChild(bar);
      banner.style.display = '';
    } catch (e) { /* offline or check disabled — ignore silently */ }
  }

  // ── Health check ──────────────────────────────────────────────────────────
  async function checkHealth() {
    try {
      const res  = await fetch('/api/health');
      const data = await res.json();
      if (data.ok) {
        document.getElementById('healthDot').className   = 'health-dot ok';
        document.getElementById('healthLabel').textContent = 'Connected';
        // Keep the footer version in sync with the running build (single source
        // of truth = constants.APP_VERSION via /api/health).
        const ver = document.getElementById('app-version');
        if (ver && data.version) ver.textContent = data.version;
        const rb = document.getElementById('reconnectBtn'); if (rb) rb.style.display = 'none';
        const logo = document.getElementById('brandLogo');
        if (logo) { logo.classList.remove('logo-disconnected'); logo.classList.add('logo-connected'); }
      }
    } catch {
      document.getElementById('healthDot').className   = 'health-dot err';
      document.getElementById('healthLabel').textContent = 'Offline';
      const rb = document.getElementById('reconnectBtn'); if (rb) rb.style.display = 'block';
      const logo = document.getElementById('brandLogo');
      if (logo) { logo.classList.remove('logo-connected'); logo.classList.add('logo-disconnected'); }
    }
  }

  // ── Settings ──────────────────────────────────────────────────────────────

  async function loadSettings() {
    try {
      const res  = await fetch('/api/settings');
      const data = await res.json();
      _settingsData = data;
      settings = typeof dgSettingsFromPayload === 'function' ? dgSettingsFromPayload(data) : (data.public_settings?.settings || {});
      window.DAYGIG_SETTINGS = settings;
      applySettings(data);
      if (typeof dgApplyProfileShell === 'function') dgApplyProfileShell(settings);
      if (typeof applyProfileNavVisibility === 'function') applyProfileNavVisibility(settings);
      loadIntegrations(data.integrations);
      return settings;
    } catch (e) {
      console.error('Could not load settings:', e);
      settings = typeof dgNormalizeSettings === 'function' ? dgNormalizeSettings({}) : {};
      window.DAYGIG_SETTINGS = settings;
      return settings;
    }
  }

  function applySettings(data) {
    const s = typeof dgSettingsFromPayload === 'function' ? dgSettingsFromPayload(data) : (data?.public_settings?.settings || {});
    const isPersonal = String(s.user_profile || 'business').toLowerCase() === 'personal';
    const biz = isPersonal ? (s.business_name || 'Personal Finance') : (s.business_name || 'Your Business');
    const setText = (id, text) => { const el = document.getElementById(id); if (el) el.textContent = text; };

    setText('bizName', biz);
    setText('topbarState', s.state || s.business_state || '');

    // Encryption status (passphrase layer on/off)
    const encActive = !!(data && data.encryption && data.encryption.passphrase_active);
    const encEl = document.getElementById('db-passphrase-status');
    if (encEl) encEl.textContent = encActive
      ? 'A passphrase is required to unlock your API keys on this device.'
      : 'API keys are encrypted at rest with a key stored on this device.';

    // Setup summary
    setText('ss-biz', biz);
    setText('ss-owner', s.owner_name || '—');
    setText('ss-state', s.state || s.business_state || '—');
    setText('ss-tax', isPersonal ? 'Hidden' : (s.sales_tax_rate ? s.sales_tax_rate + '%' : '—'));
    setText('ss-bank', s.bank_name || s.bank_account_label || '—');

    // Prefill profile fields. Save aliases are included so settings edited
    // after setup stay aligned with the first-run wizard payload.
    const profileFields = ['user_profile','business_type','entity_type','ein','creative_type','business_name','legal_name','owner_name','business_email',
                           'website','city','state','business_state','tax_home_state','default_tax_state','zip'];
    profileFields.forEach(k => { if (typeof dgSetValue === 'function') dgSetValue('set-' + k, s[k]); });

    // Prefill preference/module fields
    const prefFields = [
      'state_tax_rate','local_tax_rate','sales_tax_rate','default_sales_channel','creator_profile_type',
      'enable_personal_budgeting','enable_spending_categories','enable_subscription_tracking',
      'enable_inventory','enable_materials','enable_bom','enable_receipts','enable_shipments','enable_campaigns','enable_deadlines',
      'backup_retention'
    ];
    prefFields.forEach(k => { if (typeof dgSetValue === 'function') dgSetValue('set-' + k, s[k]); });

    // Dashboard pills
    const procs = (s.processors || '').split(',').filter(Boolean);
    const ppEl = document.getElementById('ss-processors');
    if (ppEl) ppEl.innerHTML = procs.length
      ? procs.map(p => `<span class="pill on">${cap(p)}</span>`).join('')
      : '<span class="pill">None</span>';

    let feats = (s.features || '').split(',').map(x => x.trim()).filter(Boolean);
    const featureFromFlag = {
      enable_campaigns:'campaigns', enable_inventory:'inventory', enable_bom:'bom', enable_materials:'materials',
      enable_receipts:'receipts', enable_deadlines:'deadlines', enable_shipments:'shipments',
      enable_personal_budgeting:'personal_budgeting', enable_spending_categories:'spending_categories',
      enable_subscription_tracking:'subscriptions'
    };
    if (!feats.length) {
      Object.entries(featureFromFlag).forEach(([flag, feature]) => {
        if (typeof dgTruth === 'function' ? dgTruth(s[flag]) : ['1','true','yes','on'].includes(String(s[flag] || '').toLowerCase())) feats.push(feature);
      });
    }
    const featLabels = { campaigns:'Campaigns', inventory:'Inventory', bom:'BOM', materials:'Materials',
                         receipts:'Receipts', deadlines:'Deadlines', shipments:'Shipments',
                         personal_budgeting:'Budgeting', spending_categories:'Spending Categories',
                         subscriptions:'Subscriptions', subscription_tracking:'Subscriptions' };
    const fpEl = document.getElementById('ss-features');
    if (fpEl) fpEl.innerHTML = feats.length
      ? feats.map(f => `<span class="pill on">${featLabels[f] || cap(f)}</span>`).join('')
      : '<span class="pill">None</span>';
  }

  async function saveBusinessProfile() {
    const fields = ['user_profile','business_type','entity_type','ein','creative_type','business_name','legal_name','owner_name','business_email','website','city','state','business_state','tax_home_state','default_tax_state','zip'];
    const payload = {};
    fields.forEach(k => {
      const v = typeof dgReadSettingInput === 'function' ? dgReadSettingInput(k) : undefined;
      if (v !== undefined) payload[k] = v;
    });
    if (payload.business_type && !payload.entity_type) payload.entity_type = payload.business_type;
    if (payload.entity_type && !payload.business_type) payload.business_type = payload.entity_type;
    if (payload.state) {
      payload.business_state = payload.business_state || payload.state;
      payload.tax_home_state = payload.tax_home_state || payload.state;
      payload.default_tax_state = payload.default_tax_state || payload.state;
    }
    try {
      const res  = await fetch('/api/settings', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) });
      const data = await res.json();
      if (data.ok) { flashMsg('profile-msg'); loadSettings(); }
      else alert('Save failed: ' + (data.error || 'unknown error'));
    } catch(e) { alert('Save failed: ' + e.message); }
  }

  async function savePreferences() {
    const fields = ['state_tax_rate','local_tax_rate','sales_tax_rate','default_sales_channel','creator_profile_type','enable_personal_budgeting','enable_spending_categories','enable_subscription_tracking','enable_inventory','enable_materials','enable_bom','enable_receipts','enable_shipments','enable_campaigns','enable_deadlines'];
    const payload = {};
    fields.forEach(k => {
      const v = typeof dgReadSettingInput === 'function' ? dgReadSettingInput(k) : undefined;
      if (v !== undefined) payload[k] = v;
    });
    try {
      const res  = await fetch('/api/settings', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) });
      const data = await res.json();
      if (data.ok) { flashMsg('prefs-msg'); loadSettings(); }
      else alert('Save failed: ' + (data.error || 'unknown error'));
    } catch(e) { alert('Save failed: ' + e.message); }
  }

  // Keep old saveSettings as alias for any residual calls
  async function saveSettings() { return saveBusinessProfile(); }
  async function saveApiKeys()   { /* now handled via integrations */ }

  // ── Integrations ──────────────────────────────────────────────────────────
  async function loadIntegrations(preloaded) {
    const container = document.getElementById('integrations-list');
    if (!container) return;
    try {
      let integrations = preloaded;
      let providers = _settingsData.provider_definitions;
      if (!integrations || !providers) {
        const res  = await fetch('/api/settings');
        const data = await res.json();
        integrations = data.integrations || [];
        providers    = data.provider_definitions || [];
      }

      // Group providers by category
      const catLabels = {};
      (_settingsData.integration_categories || []).forEach(c => { catLabels[c.category] = c.label; });

      // Build a map of configured integrations
      const configuredMap = {};
      (integrations || []).forEach(i => { configuredMap[i.provider] = i; });

      // Group providers
      const byCategory = {};
      (providers || []).forEach(p => {
        if (!byCategory[p.category]) byCategory[p.category] = [];
        byCategory[p.category].push(p);
      });

      const catOrder = ['payments','banking','commerce','marketplace','fulfillment',
                        'analytics','marketing','creative_platform','music','ai','accounting','storage','other'];

      let html = '';
      catOrder.forEach(cat => {
        const provs = byCategory[cat];
        if (!provs || !provs.length) return;
        const label = catLabels[cat] || cat;
        html += `<div style="margin-bottom:20px;">
          <div style="font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.1em;color:var(--text-dim);padding:10px 0 6px;">${label}</div>`;

        const comingSoon = [];
        provs.forEach(p => {
          // Plaid gets a dedicated card with Connect Bank / Sync / disconnect UI
          if (p.provider === 'plaid') {
            html += plaidIntegrationCard(configuredMap['plaid']);
            return;
          }

          const state = p.connect_state || 'coming_soon';

          // Not wired up yet — collect into a compact "coming soon" row, no
          // editable key fields (avoids keys that silently go nowhere).
          if (state === 'coming_soon') {
            comingSoon.push(p.display_name);
            return;
          }

          // No direct connection exists for this provider — point at CSV import.
          if (state === 'manual') {
            html += `<div style="border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin-bottom:8px;display:flex;align-items:center;justify-content:space-between;gap:12px;">
              <div style="display:flex;align-items:center;gap:10px;">
                <span style="font-size:13px;font-weight:600;color:var(--text);">${p.display_name}</span>
                <span style="font-family:var(--mono);font-size:10px;color:var(--text-dim);">manual / CSV</span>
              </div>
              <button class="btn btn-ghost btn-sm" onclick="showTab('transactions', { txnTarget: 'import' }); if (typeof txnTab === 'function') txnTab('import');">Import CSV</button>
            </div>`;
            return;
          }

          // state === 'available' — a real, working paste-a-key / connect form.
          const cfg = configuredMap[p.provider];
          const isConfigured = cfg && cfg.status !== 'not_configured';
          const statusColor = isConfigured ? 'var(--accent)' : 'var(--text-dim)';
          const statusLabel = isConfigured ? cfg.status.replace('_',' ') : 'available';

          // Secret fields
          const secretInputs = p.secret_fields.map(f => `
            <div style="margin-bottom:8px;">
              <label style="font-family:var(--mono);font-size:10px;color:var(--text-soft);display:block;margin-bottom:4px;">${f.replace(/_/g,' ')}</label>
              <input type="password" id="int-${p.provider}-secret-${f}"
                placeholder="${cfg?.secrets?.[f]?.masked_value || '— not set —'}"
                style="background:var(--surface2);border:1px solid var(--border);border-radius:7px;color:var(--text);font-family:var(--mono);font-size:12px;padding:7px 10px;width:100%;max-width:380px;outline:none;">
            </div>`).join('');

          // Public fields
          const publicInputs = p.public_fields.map(f => {
            const val = cfg?.public_config?.[f] || '';
            return `
            <div style="margin-bottom:8px;">
              <label style="font-family:var(--mono);font-size:10px;color:var(--text-soft);display:block;margin-bottom:4px;">${f.replace(/_/g,' ')}</label>
              <input type="text" id="int-${p.provider}-pub-${f}" value="${val}"
                placeholder="—"
                style="background:var(--surface2);border:1px solid var(--border);border-radius:7px;color:var(--text);font-size:12px;padding:7px 10px;width:100%;max-width:380px;outline:none;">
            </div>`;
          }).join('');

          const hasFields = p.secret_fields.length > 0 || p.public_fields.length > 0;
          const hint = p.setup_hint ? `<div style="font-size:11px;color:var(--text-soft);line-height:1.5;margin-bottom:10px;">${p.setup_hint}${p.setup_url ? ` <a href="${p.setup_url}" target="_blank" rel="noopener" style="color:var(--accent);text-decoration:none;">Open ↗</a>` : ''}</div>` : '';

          html += `<div style="border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin-bottom:8px;">
            <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;">
              <div style="display:flex;align-items:center;gap:10px;">
                <span style="font-size:13px;font-weight:600;color:var(--text);">${p.display_name}</span>
                <span style="font-family:var(--mono);font-size:10px;color:${statusColor};">${statusLabel}</span>
              </div>
              ${hasFields ? `<button class="btn btn-ghost btn-sm" onclick="toggleIntForm('${p.provider}')">Configure</button>` : ''}
            </div>
            ${hasFields ? `<div id="int-form-${p.provider}" style="display:none;margin-top:14px;padding-top:14px;border-top:1px solid var(--border);">
              ${hint}${secretInputs}${publicInputs}
              <div style="margin-top:10px;display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
                <button class="btn btn-primary btn-sm" onclick="saveIntegration('${p.provider}', ${JSON.stringify(p.secret_fields)}, ${JSON.stringify(p.public_fields)})">Save</button>
                ${p.can_test ? `<button class="btn btn-ghost btn-sm" onclick="runProviderTest('${p.provider}')">Test</button>` : ''}
                ${p.can_sync ? `<button class="btn btn-ghost btn-sm" onclick="runProviderSync('${p.provider}')">Sync now</button>` : ''}
                <span id="int-msg-${p.provider}" style="font-family:var(--mono);font-size:11px;display:none;"></span>
              </div>
            </div>` : ''}
          </div>`;
        });

        // Recognized-but-unwired providers, collapsed out of the default view so
        // they don't clutter or distract — available on click for the curious.
        if (comingSoon.length) {
          html += `<details style="border:1px dashed var(--border);border-radius:8px;padding:8px 14px;margin-bottom:8px;">
            <summary style="cursor:pointer;font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.1em;color:var(--text-dim);padding:4px 0;">${comingSoon.length} more coming soon</summary>
            <div style="display:flex;flex-wrap:wrap;gap:6px;padding-top:10px;">
              ${comingSoon.map(n => `<span style="font-size:11px;color:var(--text-soft);background:var(--surface2);border:1px solid var(--border);border-radius:999px;padding:3px 10px;">${n}</span>`).join('')}
            </div>
          </details>`;
        }
        html += '</div>';
      });

      container.innerHTML = html || '<div style="padding:20px;color:var(--text-dim);font-size:13px;">No integrations found.</div>';
      // Populate the connected banks list inside the Plaid card if it rendered
      plaidLoadBanks();
    } catch(e) {
      container.innerHTML = `<div style="padding:20px;color:var(--err);font-size:13px;">Error loading integrations: ${e.message}</div>`;
    }
  }

  function toggleIntForm(provider) {
    const el = document.getElementById('int-form-' + provider);
    if (el) el.style.display = el.style.display === 'none' ? '' : 'none';
  }

  async function saveIntegration(provider, secretFields, publicFields) {
    const msgEl = document.getElementById('int-msg-' + provider);
    const secret_config = {};
    const public_config = {};

    secretFields.forEach(f => {
      const el = document.getElementById(`int-${provider}-secret-${f}`);
      if (el && el.value.trim()) secret_config[f] = el.value.trim();
    });
    publicFields.forEach(f => {
      const el = document.getElementById(`int-${provider}-pub-${f}`);
      if (el && el.value.trim()) public_config[f] = el.value.trim();
    });

    const hasAnySecret = Object.keys(secret_config).length > 0;
    const payload = { provider, secret_config, public_config,
                      enabled: hasAnySecret, status: hasAnySecret ? 'configured' : 'not_configured' };

    if (msgEl) { msgEl.style.display = ''; msgEl.style.color = 'var(--text-soft)'; msgEl.textContent = 'Saving…'; }
    try {
      const res  = await fetch(`/api/integrations/${provider}`, {
        method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(payload),
      });
      const data = await res.json();
      if (data.provider || data.ok) {
        if (msgEl) { msgEl.style.color = 'var(--accent)'; msgEl.textContent = '✓ Saved.'; }
        // Clear secret fields — they're stored, don't leave them in the DOM
        secretFields.forEach(f => {
          const el = document.getElementById(`int-${provider}-secret-${f}`);
          if (el) el.value = '';
        });
        setTimeout(() => loadSettings(), 800);
      } else {
        if (msgEl) { msgEl.style.color = 'var(--err)'; msgEl.textContent = data.error || 'Error saving.'; }
      }
    } catch(e) {
      if (msgEl) { msgEl.style.color = 'var(--err)'; msgEl.textContent = 'Network error.'; }
    }
  }

  // ── Generic sync providers (Stripe, future paste-a-key syncs) ─────────────
  async function runProviderTest(provider) {
    const msgEl = document.getElementById('int-msg-' + provider);
    if (msgEl) { msgEl.style.display = ''; msgEl.style.color = 'var(--text-soft)'; msgEl.textContent = 'Testing…'; }
    try {
      const res  = await fetch(`/api/${provider}/test`);
      const data = await res.json();
      if (msgEl) {
        msgEl.style.color = data.ok ? 'var(--accent)' : 'var(--err)';
        msgEl.textContent = data.ok ? ('✓ ' + (data.message || 'Connected.')) : (data.error || 'Test failed.');
      }
    } catch (e) {
      if (msgEl) { msgEl.style.color = 'var(--err)'; msgEl.textContent = 'Network error.'; }
    }
  }

  async function runProviderSync(provider) {
    const msgEl = document.getElementById('int-msg-' + provider);
    if (msgEl) { msgEl.style.display = ''; msgEl.style.color = 'var(--text-soft)'; msgEl.textContent = 'Syncing…'; }
    try {
      const res  = await fetch(`/api/${provider}/sync`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}',
      });
      const data = await res.json();
      if (msgEl) {
        msgEl.style.color = data.ok ? 'var(--accent)' : 'var(--err)';
        msgEl.textContent = data.ok
          ? `✓ Synced — ${data.added} new in Transactions → Review.`
          : (data.error || 'Sync failed.');
      }
    } catch (e) {
      if (msgEl) { msgEl.style.color = 'var(--err)'; msgEl.textContent = 'Network error.'; }
    }
  }

  // ── Plaid bank integration UI ─────────────────────────────────────────────

  function plaidIntegrationCard(cfg) {
    const isConfigured = cfg && cfg.status !== 'not_configured';
    const statusColor  = isConfigured ? 'var(--accent)' : 'var(--text-dim)';
    const statusLabel  = cfg ? cfg.status.replace(/_/g, ' ') : 'not configured';
    const env          = cfg?.public_config?.environment || 'production';

    return `<div id="plaid-card" style="border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin-bottom:8px;">

      <!-- Header row -->
      <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;">
        <div style="display:flex;align-items:center;gap:10px;">
          <span style="font-size:13px;font-weight:600;color:var(--text);">Plaid</span>
          <span style="font-family:var(--mono);font-size:10px;color:${statusColor};">${statusLabel}</span>
        </div>
        <button class="btn btn-ghost btn-sm" onclick="toggleIntForm('plaid')">Configure</button>
      </div>

      <!-- Credentials form (collapsed by default) -->
      <div id="int-form-plaid" style="display:none;margin-top:14px;padding-top:14px;border-top:1px solid var(--border);">
        <div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);margin-bottom:12px;line-height:1.6;">
          Get your free credentials at
          <a href="https://dashboard.plaid.com/signup" target="_blank"
             style="color:var(--accent);text-decoration:none;">dashboard.plaid.com/signup</a>
          — Team Settings → Keys. Set Environment to <strong>sandbox</strong> while testing,
          <strong>production</strong> when ready to connect real accounts.
        </div>

        <div style="margin-bottom:8px;">
          <label style="font-family:var(--mono);font-size:10px;color:var(--text-soft);display:block;margin-bottom:4px;">client id</label>
          <input type="password" id="int-plaid-secret-client_id"
            placeholder="${cfg?.secrets?.client_id?.masked_value || '— not set —'}"
            style="background:var(--surface2);border:1px solid var(--border);border-radius:7px;color:var(--text);font-family:var(--mono);font-size:12px;padding:7px 10px;width:100%;max-width:380px;outline:none;">
        </div>
        <div style="margin-bottom:8px;">
          <label style="font-family:var(--mono);font-size:10px;color:var(--text-soft);display:block;margin-bottom:4px;">secret</label>
          <input type="password" id="int-plaid-secret-secret"
            placeholder="${cfg?.secrets?.secret?.masked_value || '— not set —'}"
            style="background:var(--surface2);border:1px solid var(--border);border-radius:7px;color:var(--text);font-family:var(--mono);font-size:12px;padding:7px 10px;width:100%;max-width:380px;outline:none;">
        </div>
        <div style="margin-bottom:12px;">
          <label style="font-family:var(--mono);font-size:10px;color:var(--text-soft);display:block;margin-bottom:4px;">environment</label>
          <select id="int-plaid-pub-environment"
            style="background:var(--surface2);border:1px solid var(--border);border-radius:7px;color:var(--text);font-size:12px;padding:7px 10px;width:100%;max-width:380px;outline:none;">
            <option value="sandbox"    ${env === 'sandbox'    ? 'selected' : ''}>sandbox (testing)</option>
            <option value="production" ${env === 'production' ? 'selected' : ''}>production (real accounts)</option>
          </select>
        </div>

        <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
          <button class="btn btn-primary btn-sm"
            onclick="savePlaidCredentials()">Save Credentials</button>
          <button class="btn btn-ghost btn-sm"
            onclick="plaidTestCredentials()">Test Connection</button>
          <span id="int-msg-plaid" style="font-family:var(--mono);font-size:11px;display:none;"></span>
        </div>
      </div>

      <!-- Connected banks section -->
      <div id="plaid-banks-section" style="margin-top:12px;">
        <div id="plaid-banks-list" style="margin-bottom:10px;"></div>
        <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
          <button class="btn btn-primary btn-sm" id="plaid-connect-btn"
            onclick="plaidOpenLink()" ${isConfigured ? '' : 'disabled title="Save credentials first"'}>
            + Connect Bank
          </button>
          <button class="btn btn-ghost btn-sm" id="plaid-sync-btn"
            onclick="plaidSync()" ${isConfigured ? '' : 'disabled title="Connect a bank first"'}>
            Sync Transactions
          </button>
          <span id="plaid-sync-msg" style="font-family:var(--mono);font-size:11px;color:var(--text-soft);display:none;"></span>
        </div>
      </div>

    </div>`;
  }

  async function savePlaidCredentials() {
    const msgEl    = document.getElementById('int-msg-plaid');
    const clientId = (document.getElementById('int-plaid-secret-client_id')?.value || '').trim();
    const secret   = (document.getElementById('int-plaid-secret-secret')?.value || '').trim();
    const env      = document.getElementById('int-plaid-pub-environment')?.value || 'production';

    if (!clientId || !secret) {
      if (msgEl) { msgEl.style.display=''; msgEl.style.color='var(--err)'; msgEl.textContent='Both Client ID and Secret are required.'; }
      return;
    }

    if (msgEl) { msgEl.style.display=''; msgEl.style.color='var(--text-soft)'; msgEl.textContent='Saving…'; }

    try {
      const res  = await fetch('/api/integrations/plaid', {
        method: 'POST',
        headers: {'Content-Type':'application/json'},
        body: JSON.stringify({
          provider:      'plaid',
          secret_config: { client_id: clientId, secret },
          public_config: { environment: env },
          enabled:       true,
          status:        'configured',
        }),
      });
      const data = await res.json();
      if (data.provider || data.ok) {
        if (msgEl) { msgEl.style.color='var(--accent)'; msgEl.textContent='✓ Saved.'; }
        document.getElementById('int-plaid-secret-client_id').value = '';
        document.getElementById('int-plaid-secret-secret').value    = '';
        setTimeout(() => loadSettings(), 800);
      } else {
        if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = data.error || 'Save failed.'; }
      }
    } catch(e) {
      if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = 'Network error.'; }
    }
  }

  async function plaidTestCredentials() {
    const msgEl = document.getElementById('int-msg-plaid');
    if (msgEl) { msgEl.style.display=''; msgEl.style.color='var(--text-soft)'; msgEl.textContent='Testing…'; }
    try {
      const res  = await fetch('/api/plaid/test');
      const data = await res.json();
      if (data.ok) {
        if (msgEl) { msgEl.style.color='var(--accent)'; msgEl.textContent='✓ ' + (data.message || 'Connected.'); }
      } else {
        if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = data.error || 'Test failed.'; }
      }
    } catch(e) {
      if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = 'Network error.'; }
    }
  }

  async function plaidOpenLink(options = {}) {
    // Step 1: get a link_token from our backend
    const connectBtn = document.getElementById(options.buttonId || 'plaid-connect-btn');
    const msgEl      = document.getElementById(options.messageTargetId || 'plaid-sync-msg');
    if (connectBtn) connectBtn.disabled = true;
    if (msgEl) { msgEl.style.display=''; msgEl.style.color='var(--text-soft)'; msgEl.textContent='Opening bank connection…'; }

    let linkToken;
    try {
      const res  = await fetch('/api/plaid/link-token');
      const data = await res.json();
      if (!data.ok || !data.link_token) {
        if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = data.error || 'Could not start bank connection.'; }
        if (connectBtn) connectBtn.disabled = false;
        return;
      }
      linkToken = data.link_token;
    } catch(e) {
      if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = 'Network error getting link token.'; }
      if (connectBtn) connectBtn.disabled = false;
      return;
    }

    // Step 2: load Plaid Link script and open it
    // Plaid Link JS is loaded on-demand so it's only fetched when the user
    // actually clicks Connect Bank — not on every page load.
    const loadPlaidScript = () => new Promise((resolve, reject) => {
      if (window.Plaid) { resolve(); return; }
      const s = document.createElement('script');
      s.src = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
      s.onload  = resolve;
      s.onerror = () => reject(new Error('Could not load Plaid Link script. Check your internet connection.'));
      document.head.appendChild(s);
    });

    try {
      await loadPlaidScript();
    } catch(e) {
      if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = e.message; }
      if (connectBtn) connectBtn.disabled = false;
      return;
    }

    if (msgEl) msgEl.style.display = 'none';

    // Step 3: open Plaid Link UI
    const handler = window.Plaid.create({
      token: linkToken,
      onSuccess: async (publicToken, metadata) => {
        const institution = metadata?.institution?.name || 'Bank';
        if (msgEl) { msgEl.style.display=''; msgEl.style.color='var(--text-soft)'; msgEl.textContent=`Connecting ${institution}…`; }
        try {
          const res  = await fetch('/api/plaid/exchange', {
            method: 'POST',
            headers: {'Content-Type':'application/json'},
            body: JSON.stringify({ public_token: publicToken, institution_name: institution }),
          });
          const data = await res.json();
          if (data.ok) {
            if (msgEl) { msgEl.style.color='var(--accent)'; msgEl.textContent=`✓ ${institution} connected. Syncing transactions…`; }
            // Auto-sync immediately after connecting
            await plaidSync(null, options);
            if (typeof options.afterSuccess === 'function') await options.afterSuccess();
          } else {
            if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = data.error || 'Connection failed.'; }
          }
        } catch(e) {
          if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = 'Network error finalizing connection.'; }
        }
        plaidLoadBanks();
        if (connectBtn) connectBtn.disabled = false;
      },
      onExit: (err) => {
        if (connectBtn) connectBtn.disabled = false;
        if (err && msgEl) { msgEl.style.display=''; msgEl.style.color='var(--text-dim)'; msgEl.textContent='Connection cancelled.'; }
        setTimeout(() => { if (msgEl) msgEl.style.display='none'; }, 3000);
      },
      onEvent: () => {},
    });
    handler.open();
  }

  async function plaidSync(itemId, options = {}) {
    const msgEl   = document.getElementById(options.messageTargetId || 'plaid-sync-msg');
    const syncBtn = document.getElementById(options.buttonId || 'plaid-sync-btn');
    if (msgEl)   { msgEl.style.display=''; msgEl.style.color='var(--text-soft)'; msgEl.textContent='Syncing…'; }
    if (syncBtn)   syncBtn.disabled = true;
    try {
      const body = itemId ? JSON.stringify({ item_id: itemId }) : '{}';
      const res  = await fetch('/api/plaid/sync', {
        method: 'POST', headers: {'Content-Type':'application/json'}, body,
      });
      const data = await res.json();
      if (data.ok) {
        const n = data.added || 0;
        if (msgEl) {
          msgEl.style.color = 'var(--accent)';
          msgEl.textContent = n > 0
            ? `✓ ${n} transaction${n === 1 ? '' : 's'} added to review queue.`
            : '✓ Up to date — no new transactions.';
        }
        if (typeof options.afterSync === 'function') await options.afterSync(data);
      } else {
        if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = data.error || 'Sync failed.'; }
      }
    } catch(e) {
      if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = 'Network error.'; }
    } finally {
      if (syncBtn) syncBtn.disabled = false;
    }
  }

  async function plaidLoadBanks() {
    const listEl = document.getElementById('plaid-banks-list');
    if (!listEl) return;
    try {
      const res  = await fetch('/api/plaid/items');
      const data = await res.json();
      const items = data.items || [];
      if (!items.length) {
        listEl.innerHTML = '<div style="font-size:12px;color:var(--text-dim);padding:4px 0 8px;">No banks connected yet.</div>';
        return;
      }
      listEl.innerHTML = items.map(item => `
        <div style="display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--border);">
          <div>
            <span style="font-size:13px;color:var(--text);font-weight:500;">${item.institution_name || 'Bank'}</span>
            ${item.last_synced_at
              ? `<span style="font-family:var(--mono);font-size:10px;color:var(--text-dim);margin-left:8px;">synced ${item.last_synced_at.slice(0,10)}</span>`
              : ''}
          </div>
          <div style="display:flex;gap:6px;">
            <button class="btn btn-ghost btn-sm" onclick="plaidSync('${item.item_id}')">Sync</button>
            <button class="btn btn-ghost btn-sm" style="color:var(--err);"
              onclick="plaidDisconnect('${item.item_id}', '${(item.institution_name||'Bank').replace(/'/g,"\\'")}')">
              Disconnect
            </button>
          </div>
        </div>`).join('');
    } catch(e) {
      if (listEl) listEl.innerHTML = '<div style="font-size:12px;color:var(--err);">Could not load connected banks.</div>';
    }
  }

  async function plaidDisconnect(itemId, institutionName) {
    if (!confirm(`Disconnect ${institutionName}?\n\nThis removes the bank connection. Transactions already in your review queue are not deleted.`)) return;
    const msgEl = document.getElementById('plaid-sync-msg');
    if (msgEl) { msgEl.style.display=''; msgEl.style.color='var(--text-soft)'; msgEl.textContent=`Disconnecting ${institutionName}…`; }
    try {
      const res  = await fetch(`/api/plaid/remove/${itemId}`, { method: 'POST' });
      const data = await res.json();
      if (data.ok) {
        if (msgEl) { msgEl.style.color='var(--accent)'; msgEl.textContent=`✓ ${institutionName} disconnected.`; }
        plaidLoadBanks();
      } else {
        if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = data.error || 'Disconnect failed.'; }
      }
    } catch(e) {
      if (msgEl) { msgEl.style.color='var(--err)'; msgEl.textContent = 'Network error.'; }
    }
  }

  Object.assign(window, {
    loadSettings,
    toggleIntForm,
    saveIntegration,
    runProviderTest,
    runProviderSync,
    savePlaidCredentials,
    plaidTestCredentials,
    plaidOpenLink,
    plaidSync,
    plaidLoadBanks,
    plaidDisconnect,
    doBackup,
    loadBackupPanel,
    restoreFromBackup,
  });

  async function doBackup() {
    try {
      const res  = await fetch('/api/backup', { method: 'POST' });
      const data = await res.json();
      const msg  = document.getElementById('backup-msg');
      msg.textContent = data.backup ? 'Saved: ' + data.backup.filename : 'Backup created.';
      msg.style.display = 'inline';
      setTimeout(() => msg.style.display = 'none', 4000);
      loadBackupPanel();   // refresh list after creating
    } catch (e) { alert('Backup failed: ' + e.message); }
  }

  async function saveBackupRetention() {
    const el = document.getElementById('set-backup_retention');
    if (!el) return;
    let n = parseInt(el.value, 10);
    if (!Number.isFinite(n)) { alert('Enter a number of backups to keep.'); return; }
    n = Math.max(10, Math.min(100000, n));   // mirror the server clamp
    el.value = n;
    try {
      const res = await fetch('/api/settings', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ backup_retention: String(n) }),
      });
      const data = await res.json();
      const msg = document.getElementById('backup-retention-msg');
      if (data.ok) {
        if (msg) { msg.textContent = 'Saved — keeping ' + n + '.'; msg.style.display = 'inline'; setTimeout(() => msg.style.display = 'none', 4000); }
        loadSettings();
      } else { alert('Save failed: ' + (data.error || 'unknown error')); }
    } catch (e) { alert('Save failed: ' + e.message); }
  }
  if (typeof window !== 'undefined') window.saveBackupRetention = saveBackupRetention;

  async function loadBackupPanel() {
    // ── Data location ──────────────────────────────────────────────────────────
    const locEl = document.getElementById('db-location');
    if (locEl) {
      try {
        const r = await fetch('/api/data-location');
        const d = await r.json();
        locEl.innerHTML =
          `<span style="color:var(--text-dim)">Data folder:</span> ${d.data_dir}<br>` +
          `<span style="color:var(--text-dim)">Backups:</span>     ${d.backups_dir}`;
      } catch (_) { locEl.textContent = 'Could not load data location.'; }
    }

    // ── Backup list ────────────────────────────────────────────────────────────
    const listEl = document.getElementById('backup-list');
    if (!listEl) return;
    listEl.innerHTML = '<span style="color:var(--text-dim);font-family:var(--mono);font-size:11px;">Loading…</span>';
    try {
      const r  = await fetch('/api/backups');
      const files = await r.json();
      if (!Array.isArray(files) || files.length === 0) {
        listEl.innerHTML = '<span style="color:var(--text-dim);font-family:var(--mono);font-size:11px;">No backups yet. Create one above.</span>';
        return;
      }
      listEl.innerHTML = files.map(f => {
        const mb   = (f.size_bytes / 1024 / 1024).toFixed(2);
        const date = (f.modified_at || '').replace('T', ' ').slice(0, 16);
        return `<div style="display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border);">
          <span style="font-family:var(--mono);font-size:11px;color:var(--text-soft);flex:1;">${f.filename}</span>
          <span style="font-family:var(--mono);font-size:11px;color:var(--text-dim);min-width:80px;text-align:right;">${date}</span>
          <span style="font-family:var(--mono);font-size:11px;color:var(--text-dim);min-width:52px;text-align:right;">${mb} MB</span>
          <a href="/api/backups/download?filename=${encodeURIComponent(f.filename)}"
             download="${f.filename}"
             class="btn btn-ghost btn-sm"
             style="font-size:11px;padding:3px 8px;">Export</a>
          <button class="btn btn-ghost btn-sm"
                  style="font-size:11px;padding:3px 8px;color:var(--warn,var(--warn-amber));"
                  onclick="restoreFromBackup('${f.filename.replace(/'/g, "\\'")}')">Restore</button>
        </div>`;
      }).join('');
    } catch (e) {
      listEl.innerHTML = `<span style="color:var(--err);font-size:11px;">Error: ${e.message}</span>`;
    }
  }

  async function restoreFromBackup(filename) {
    if (!confirm(`Restore from backup:\n${filename}\n\nThe current database will be backed up first. Daygig must be restarted after restoring.\n\nContinue?`)) return;
    try {
      const res  = await fetch('/api/restore', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({ filename }),
      });
      const data = await res.json();
      if (data.ok) {
        alert(`✓ ${data.message}`);
        loadBackupPanel();
      } else {
        alert('Restore failed: ' + (data.error || 'Unknown error'));
      }
    } catch (e) { alert('Restore failed: ' + e.message); }
  }

// ── Factory reset ─────────────────────────────────────────────────────────
  function resetStep1() {
    document.getElementById('reset-step1').style.display = 'none';
    document.getElementById('reset-step2').style.display = 'block';
    document.getElementById('reset-step3').style.display = 'none';
  }
  function resetStep2() {
    document.getElementById('reset-step2').style.display = 'none';
    document.getElementById('reset-step3').style.display = 'block';
    document.getElementById('reset-confirm-word').value = '';
    document.getElementById('reset-final-btn').disabled = true;
    document.getElementById('reset-final-btn').style.background = '#888';
    document.getElementById('reset-final-btn').style.cursor = 'not-allowed';
  }
  function resetCancel() {
    document.getElementById('reset-step1').style.display = 'block';
    document.getElementById('reset-step2').style.display = 'none';
    document.getElementById('reset-step3').style.display = 'none';
    document.getElementById('reset-confirm-word').value = '';
    document.getElementById('reset-msg').style.display = 'none';
  }
  function resetCheckWord() {
    const val = document.getElementById('reset-confirm-word').value;
    const btn = document.getElementById('reset-final-btn');
    const match = val === 'RESET';
    btn.disabled = !match;
    btn.style.background = match ? 'var(--neg)' : '#888';
    btn.style.cursor = match ? 'pointer' : 'not-allowed';
  }
  async function doFactoryReset() {
    const btn = document.getElementById('reset-final-btn');
    const msg = document.getElementById('reset-msg');
    btn.disabled = true;
    btn.textContent = 'Resetting…';
    msg.style.display = 'none';
    try {
      const res  = await fetch('/api/factory-reset', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ confirm: 'RESET' }),
      });
      const data = await res.json();
      if (data.ok) {
        // Brief pause so the user sees the message, then redirect to setup.
        msg.textContent = '✓ Reset complete. Redirecting to setup…';
        msg.style.color = 'var(--pos)';
        msg.style.display = 'inline';
        setTimeout(() => { window.location.href = '/setup'; }, 1500);
      } else {
        msg.textContent = 'Error: ' + (data.error || 'Unknown error.');
        msg.style.color = 'var(--neg)';
        msg.style.display = 'inline';
        btn.disabled = false;
        btn.textContent = 'Erase Everything';
      }
    } catch (e) {
      msg.textContent = 'Network error: ' + e.message;
      msg.style.color = 'var(--neg)';
      msg.style.display = 'inline';
      btn.disabled = false;
      btn.textContent = 'Erase Everything';
    }
  }



  // ── Shutdown ──────────────────────────────────────────────────────────────
  async function dgShutdown() {
    const confirmed = confirm('Stop the Daygig server?\n\nThe browser tab will stop working. Close it when done.');
    if (!confirmed) return;
    try {
      // X-Daygig-Internal satisfies the server's CSRF gate regardless of
      // referrer policy; a cross-origin page cannot forge this header.
      await fetch('/api/shutdown', { headers: { 'X-Daygig-Internal': '1' } });
    } catch(e) { /* server gone, expected */ }
    document.body.innerHTML = `
      <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;background:#0c0e11;color:#e4e9f0;font-family:'DM Sans',sans-serif;gap:16px;">
        <svg width="48" height="48" viewBox="0 0 48 48"><rect width="48" height="48" rx="11" fill="#4effa8"/><text x="24" y="34" text-anchor="middle" font-family="'Syne','Arial Black',sans-serif" font-weight="800" font-size="30" fill="#0c0e11">D</text></svg>
        <div style="font-size:18px;font-weight:600;">Daygig stopped.</div>
        <div style="font-size:13px;color:#6e7d93;">You can close this tab.</div>
        <div style="font-size:12px;color:#3d4758;margin-top:8px;">To restart: double-click Daygig.command</div>
      </div>`;
  }
