// daygig-reconcile.js — Import/reconcile, transaction sub-tabs, post-from-queue
(function () {

// ── Reconcile ─────────────────────────────────────────────────────────────
  let recCsvB64    = null;
  let recHeaders   = [];
  let recPreview   = [];
  let recDetected  = {};
  let recTotalRows = 0;

  function dgIsPersonalMode() {
    const s = window.DAYGIG_SETTINGS || window.settings || {};
    return String(s.user_profile || '').toLowerCase() === 'personal';
  }

  function recLabel(key) {
    const personal = dgIsPersonalMode();
    const labels = {
      importTitle: personal ? 'Import bank transactions' : 'Import rows',
      reviewTitle: personal ? 'Review Transactions' : 'Review',
      ledgerTitle: personal ? 'History' : 'Ledger',
      reconcileVerb: personal ? 'review' : 'reconcile',
      category: personal ? 'Category' : 'Account',
      moneyIn: personal ? 'Money In' : 'Inflow',
      moneyOut: personal ? 'Money Out' : 'Outflow',
      noPending: personal ? 'No transactions waiting for review.' : 'No pending rows.',
      categorize: personal ? 'Categorize' : 'Post',
      skip: personal ? 'Skip' : 'Ignore',
      transfer: personal ? 'Mark as Transfer' : 'Transfer',
    };
    return labels[key] || key;
  }

  function recApplyProfileLabels() {
    const personal = dgIsPersonalMode();
    const importTab = document.querySelector('.txn-tab[data-txn="import"]');
    const reviewTab = document.querySelector('.txn-tab[data-txn="review"]');
    const ledgerTab = document.querySelector('.txn-tab[data-txn="ledger"]');
    if (importTab) importTab.firstChild ? null : null;
    if (importTab) importTab.childNodes.forEach(n => { if (n.nodeType === Node.TEXT_NODE) n.textContent = personal ? 'Import' : 'Import'; });
    if (reviewTab) reviewTab.childNodes.forEach(n => { if (n.nodeType === Node.TEXT_NODE) n.textContent = personal ? 'Review ' : 'Review '; });
    if (ledgerTab) ledgerTab.childNodes.forEach(n => { if (n.nodeType === Node.TEXT_NODE) n.textContent = personal ? 'History' : 'Ledger'; });

    const uploadTitle = document.querySelector('#rec-panel-1 .card-title');
    if (uploadTitle) uploadTitle.textContent = personal ? 'Upload a bank CSV' : 'Upload a bank CSV';
    const uploadSub = document.querySelector('#rec-panel-1 .card-sub');
    if (uploadSub) uploadSub.textContent = personal
      ? 'Export a transaction CSV from your bank. Daygig will help you import, review, and categorize money in and money out.'
      : 'Export a transaction CSV from your bank or processor. Works with any format.';
  }

  function recReset() {
    recCsvB64 = null; recHeaders = []; recPreview = [];
    recDetected = {}; recTotalRows = 0;
    const fileInput = document.getElementById('rec-file-input');
    const uploadStatus = document.getElementById('rec-upload-status');
    const importResult = document.getElementById('rec-import-result');
    if (fileInput) fileInput.value = '';
    if (uploadStatus) uploadStatus.style.display = 'none';
    if (importResult) importResult.style.display = 'none';
    recGoToStep(1);
  }

  function recGoToStep(n) {
    [1,2,3].forEach(i => {
      const panel = document.getElementById('rec-panel-' + i);
      if (panel) panel.style.display = i === n ? 'block' : 'none';
      const s = document.getElementById('rstep-' + i);
      if (!s) return;
      s.style.color = i === n ? 'var(--text)' : 'var(--text-dim)';
      const num = s.querySelector('span');
      if (num) num.style.color = i === n ? 'var(--accent)' : 'var(--text-dim)';
    });
  }

  // Drag and drop
  function recDragOver(e) {
    e.preventDefault();
    document.getElementById('rec-drop-zone').style.borderColor = 'var(--accent)';
  }
  function recDragLeave(e) {
    document.getElementById('rec-drop-zone').style.borderColor = 'var(--border-hi)';
  }
  function recDrop(e) {
    e.preventDefault();
    recDragLeave(e);
    const file = e.dataTransfer.files[0];
    if (file) recProcessFile(file);
  }
  function recFileSelected(input) {
    if (input.files[0]) recProcessFile(input.files[0]);
  }

  async function recProcessFile(file) {
    const status = document.getElementById('rec-upload-status');
    if (!file) return;
    if (status) {
      status.style.display = 'block';
      status.style.color = 'var(--text-soft)';
      status.textContent = `Reading ${file.name || 'file'}…`;
    }

    const reader = new FileReader();
    reader.onerror = () => {
      if (status) {
        status.style.display = 'block';
        status.style.color = 'var(--err)';
        status.textContent = 'Could not read that file. Try exporting the CSV again.';
      }
    };
    reader.onload = async (e) => {
      const dataUrl = String(e.target.result || '');
      recCsvB64 = dataUrl.includes(',') ? dataUrl.split(',').pop() : dataUrl;
      const sourceEl = document.getElementById('rec-source-name');
      const source = ((sourceEl && sourceEl.value) || file.name || 'csv').trim();
      if (sourceEl && !sourceEl.value) sourceEl.value = source.replace(/\.(csv|txt)$/i, '');
      if (status) {
        status.style.display = 'block';
        status.style.color = 'var(--text-soft)';
        status.textContent = 'Detecting columns…';
      }
      try {
        const res  = await fetch('/api/import/preview', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ csv_b64: recCsvB64, source_name: source }),
        });
        const data = await res.json();
        if (!res.ok || data.error) throw new Error(data.error || 'Preview failed.');
        recHeaders   = data.headers   || [];
        recPreview   = data.preview_rows || [];
        recDetected  = data.detected  || {};
        recTotalRows = data.total_rows || 0;
        const mapping = data.mapping  || recDetected;
        if (status) status.style.display = 'none';
        recBuildMapStep(mapping);
        recGoToStep(2);
      } catch(err) {
        if (status) {
          status.style.display = 'block';
          status.style.color = 'var(--err)';
          status.textContent = 'Error: ' + (err.message || err);
        }
      }
    };
    reader.readAsDataURL(file);
  }

  function recBuildMapStep(mapping) {
    const info = document.getElementById('rec-preview-info');
    info.textContent = `${recTotalRows} rows detected · ${recHeaders.length} columns`;

    const selects = ['map-date','map-desc','map-amount','map-debit','map-credit'];
    selects.forEach(id => {
      const sel = document.getElementById(id);
      sel.innerHTML = '<option value="">— none —</option>' +
        recHeaders.map(h => `<option value="${h}">${h}</option>`).join('');
    });

    // Apply mapping
    const set = (id, val) => { if (val) document.getElementById(id).value = val; };
    set('map-date',   mapping.date_col);
    set('map-desc',   mapping.description_col);
    set('map-amount', mapping.amount_col);
    set('map-debit',  mapping.debit_col);
    set('map-credit', mapping.credit_col);

    // Toggle split/single format based on detection
    if (mapping.debit_col && mapping.credit_col && !mapping.amount_col) {
      document.getElementById('fmt-split').checked = true;
      recToggleAmtFormat();
    } else {
      document.getElementById('fmt-single').checked = true;
      recToggleAmtFormat();
    }

    recBuildPreviewTable();
  }

  function recToggleAmtFormat() {
    const isSplit = document.getElementById('fmt-split').checked;
    document.getElementById('amt-single-row').style.display = isSplit ? 'none'  : 'grid';
    document.getElementById('amt-split-row').style.display  = isSplit ? 'grid' : 'none';
  }

  function recBuildPreviewTable() {
    const head = document.getElementById('rec-preview-head');
    const body = document.getElementById('rec-preview-body');
    const thStyle = 'text-align:left;padding:6px 10px;font-family:var(--mono);font-size:10px;color:var(--text-dim);text-transform:uppercase;border-bottom:1px solid var(--border);white-space:nowrap;';
    const tdStyle = 'padding:6px 10px;border-bottom:1px solid var(--border);color:var(--text-soft);white-space:nowrap;max-width:200px;overflow:hidden;text-overflow:ellipsis;';
    head.innerHTML = '<tr>' + recHeaders.map(h => `<th style="${thStyle}">${h}</th>`).join('') + '</tr>';
    body.innerHTML = recPreview.map(row =>
      '<tr>' + recHeaders.map(h => `<td style="${tdStyle}">${row[h] || ''}</td>`).join('') + '</tr>'
    ).join('');
  }

  function recGetCurrentMapping() {
    const isSplit = document.getElementById('fmt-split').checked;
    return {
      date_col:        document.getElementById('map-date').value   || null,
      description_col: document.getElementById('map-desc').value   || null,
      amount_col:      isSplit ? null : (document.getElementById('map-amount').value || null),
      debit_col:       isSplit ? (document.getElementById('map-debit').value  || null) : null,
      credit_col:      isSplit ? (document.getElementById('map-credit').value || null) : null,
    };
  }

  function recProceedToImport() {
    const mapping = recGetCurrentMapping();
    if (!mapping.date_col) { alert('Please select a date column.'); return; }
    if (!mapping.amount_col && !(mapping.debit_col || mapping.credit_col)) {
      alert('Please select an amount column (or debit/credit columns).'); return;
    }
    const source = (document.getElementById('rec-source-name').value || 'csv').trim();
    const summary = document.getElementById('rec-import-summary');
    summary.innerHTML = `
      <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
        <div class="review-card"><h4>Source</h4>
          <div class="rrow"><span class="rk">Name</span><span class="rv">${source}</span></div>
          <div class="rrow"><span class="rk">Rows to import</span><span class="rv">${recTotalRows}</span></div>
        </div>
        <div class="review-card"><h4>Column Mapping</h4>
          <div class="rrow"><span class="rk">Date</span><span class="rv">${mapping.date_col || '—'}</span></div>
          <div class="rrow"><span class="rk">Amount</span><span class="rv">${mapping.amount_col || (mapping.debit_col + ' / ' + mapping.credit_col) || '—'}</span></div>
          <div class="rrow"><span class="rk">Description</span><span class="rv">${mapping.description_col || '—'}</span></div>
        </div>
      </div>`;
    recGoToStep(3);
  }

  async function recDoImport() {
    const btn    = document.getElementById('rec-import-btn');
    const result = document.getElementById('rec-import-result');
    const source = (document.getElementById('rec-source-name').value || 'csv').trim();
    const mapping = recGetCurrentMapping();
    const saveMappingFlag = document.getElementById('rec-save-mapping').checked;
    btn.disabled = true;
    btn.textContent = 'Importing…';
    result.style.display = 'none';
    try {
      const res  = await fetch('/api/import/upload', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          csv_b64: recCsvB64,
          source_name: source,
          mapping: mapping,
          save_mapping: saveMappingFlag,
        }),
      });
      const data = await res.json();
      result.style.display = 'block';
      if (data.ok) {
        result.style.background = 'var(--accent-bg)';
        result.style.border     = '1px solid var(--accent-ring)';
        result.style.color      = 'var(--accent)';
        result.style.padding    = '12px 16px';
        result.style.borderRadius = '8px';
        result.innerHTML = `
          <strong>Import complete.</strong><br>
          <span style="font-family:var(--mono);font-size:12px;">
            ${data.inserted} ${dgIsPersonalMode() ? 'transactions' : 'rows'} inserted &nbsp;·&nbsp;
            ${data.inserted_money_in || 0} ${recLabel('moneyIn').toLowerCase()} &nbsp;·&nbsp;
            ${data.inserted_money_out || 0} ${recLabel('moneyOut').toLowerCase()} &nbsp;·&nbsp;
            ${data.skipped_duplicates} duplicates skipped &nbsp;·&nbsp;
            ${data.skipped_invalid} invalid skipped
          </span>
          ${data.errors.length ? '<br><span style="color:var(--warn);font-size:11px;">' + data.errors.join(' · ') + '</span>' : ''}`;
        // Switch to Review sub-panel after a moment so user can see the summary
        setTimeout(() => txnTab('review'), 1800);
      } else {
        result.style.background = 'var(--err-bg)';
        result.style.border     = '1px solid rgba(255,92,108,.25)';
        result.style.color      = 'var(--err)';
        result.style.padding    = '12px 16px';
        result.style.borderRadius = '8px';
        result.textContent = 'Import failed: ' + (data.error || 'Unknown error');
      }
    } catch(err) {
      result.style.display = 'block';
      result.textContent = 'Network error: ' + err.message;
    }
    btn.disabled = false;
    btn.textContent = 'Import Rows';
  }

  async function recLoadQueue() {
    const res  = await fetch('/api/import/pending?limit=50');
    const data = await res.json();
    const body = document.getElementById('rec-queue-body');
    const foot = document.getElementById('rec-queue-footer');
    const personal = data.is_personal === true || dgIsPersonalMode();
    if (!body) return;
    if (!data.rows || data.rows.length === 0) {
      body.innerHTML = `<tr><td colspan="6" style="padding:20px;text-align:center;color:var(--text-dim);">${recLabel('noPending')}</td></tr>`;
      if (foot) foot.textContent = '';
      return;
    }
    const fmtAmt = (cents) => {
      const abs = Math.abs(Number(cents || 0));
      const str = dgMoneyC(abs);
      return cents < 0
        ? `<span style="color:var(--err);">−${str}</span>`
        : `<span style="color:var(--accent);">+${str}</span>`;
    };
    const flowBadge = (r) => {
      const isIn = r.is_money_in || r.direction === 'in' || Number(r.amount_cents || 0) > 0;
      const label = r.direction_label || (isIn ? recLabel('moneyIn') : recLabel('moneyOut'));
      const color = isIn ? 'var(--accent)' : 'var(--err)';
      return `<span style="font-family:var(--mono);font-size:10px;color:${color};background:var(--surface2);border:1px solid var(--border);border-radius:999px;padding:2px 8px;white-space:nowrap;">${label}</span>`;
    };
    body.innerHTML = data.rows.map(r => {
      const desc = r.description || (r._payload ? Object.values(r._payload).find(v => v && String(v).length > 3) : null) || '—';
      return `<tr style="border-bottom:1px solid var(--border);">
        <td style="padding:7px 10px;color:var(--text-soft);font-family:var(--mono);font-size:12px;">${r.external_date || '—'}</td>
        <td style="padding:7px 10px;color:var(--text-dim);font-size:12px;">${r.source}</td>
        <td style="padding:7px 10px;color:var(--text-soft);font-size:12px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${String(desc).replace(/"/g, '&quot;')}">${flowBadge(r)} <span style="margin-left:8px;">${String(desc).substring(0,60)}</span></td>
        <td style="padding:7px 10px;text-align:right;font-family:var(--mono);font-size:12px;">${fmtAmt(r.amount_cents)}</td>
        <td style="padding:7px 10px;">
          <button onclick="recIgnoreRow(${r.id}, this)" style="background:transparent;border:1px solid var(--border);border-radius:5px;color:var(--text-dim);font-size:11px;padding:3px 8px;cursor:pointer;">${recLabel('skip')}</button>
        </td>
      </tr>`;
    }).join('');
    const inCount = data.rows.filter(r => r.is_money_in || r.direction === 'in' || Number(r.amount_cents || 0) > 0).length;
    const outCount = data.rows.filter(r => r.is_money_out || r.direction === 'out' || Number(r.amount_cents || 0) < 0).length;
    if (foot) foot.textContent = personal
      ? `Showing ${data.rows.length} of ${data.total} waiting for review · ${inCount} money in · ${outCount} money out`
      : `Showing ${data.rows.length} of ${data.total} pending rows`;
    recShowTransferHints();
  }

  // Surface equal-and-opposite pairs in the queue that look like transfers
  // between the user's own accounts — the most common bookkeeping mistake is
  // posting both legs as income/expense and double-counting the money.
  async function recShowTransferHints() {
    const box = document.getElementById('rec-transfer-hint');
    if (!box) return;
    try {
      const res = await fetch('/api/import/transfer-candidates');
      const data = await res.json();
      const pairs = (data && data.pairs) || [];
      if (!pairs.length) { box.style.display = 'none'; box.innerHTML = ''; return; }
      const money = (c) => dgMoneyC(Math.abs(Number(c || 0)));
      const esc = (s) => String(s == null ? '' : s).replace(/[&<>"]/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[m]));
      const rows = pairs.slice(0, 6).map(p => {
        const oNote = esc((p.money_out.notes || p.money_out.source || '').toString().substring(0, 36));
        const iNote = esc((p.money_in.notes || p.money_in.source || '').toString().substring(0, 36));
        return `<li style="margin:3px 0;">
          <strong>${money(p.amount_cents)}</strong> —
          <span style="color:var(--err);">out</span> ${oNote} (${esc(p.money_out.date)})
          ↔ <span style="color:var(--accent);">in</span> ${iNote} (${esc(p.money_in.date)})
          ${p.cross_account ? '' : '<span style="color:var(--text-dim);"> · same source, verify</span>'}
        </li>`;
      }).join('');
      box.innerHTML = `
        <div style="border:1px solid var(--accent-ring,#3b5);background:var(--accent-bg,rgba(60,160,90,.08));border-radius:8px;padding:10px 12px;">
          <div style="font-weight:600;font-size:12px;margin-bottom:6px;">
            ↔ ${pairs.length} possible transfer${pairs.length === 1 ? '' : 's'} detected
            <span style="font-weight:400;color:var(--text-dim);">— money moving between your own accounts. Mark these as transfers (not income/expense) so they aren't double-counted.</span>
          </div>
          <ul style="margin:0;padding-left:18px;font-size:12px;color:var(--text-soft);">${rows}</ul>
        </div>`;
      box.style.display = 'block';
    } catch (_) {
      box.style.display = 'none';
    }
  }
  if (typeof window !== 'undefined') window.recShowTransferHints = recShowTransferHints;

  async function recIgnoreRow(id, btn) {
    btn.disabled = true;
    btn.textContent = '…';
    await fetch('/api/import/ignore/' + id, { method: 'POST' });
    btn.closest('tr').style.opacity = '0.3';
    setTimeout(() => btn.closest('tr').remove(), 500);
  }



  // ── Review queue: categorize pending imports and post to ledger ──────────
  let _postQueueRows = [];
  let _postAccounts = [];
  let postSelectedRow = null;

  const PERSONAL_QUICK_CATEGORIES = [
    { label: 'Groceries', terms: ['grocery','groceries','supermarket','market','aldi','trader joe','whole foods','wegmans','giant','safeway','kroger'] },
    { label: 'Dining Out', terms: ['restaurant','coffee','cafe','doordash','ubereats','grubhub','pizza','bar','starbucks','dunkin'] },
    { label: 'Rent / Mortgage', terms: ['rent','mortgage','landlord','apartment'] },
    { label: 'Utilities', terms: ['electric','gas','water','utility','utilities','power','peco','pge','coned','trash'] },
    { label: 'Internet / Phone', terms: ['internet','phone','mobile','verizon','att','at&t','tmobile','xfinity','comcast','spectrum'] },
    { label: 'Subscriptions', terms: ['subscription','netflix','spotify','hulu','apple','google','adobe','patreon','substack','prime','membership'] },
    { label: 'Transportation', terms: ['gas','fuel','uber','lyft','parking','transit','train','bus','septA','toll','auto'] },
    { label: 'Shopping', terms: ['amazon','target','walmart','ebay','etsy','store','shop'] },
    { label: 'Medical / Health', terms: ['pharmacy','cvs','walgreens','doctor','medical','health','dentist','hospital'] },
    { label: 'Bank Fees', terms: ['fee','overdraft','atm','interest charge','finance charge'] },
  ];

  function postEscapeHtml(value) {
    return String(value ?? '')
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;');
  }

  function postMoney(cents) {
    const n = Number(cents || 0);
    const abs = Math.abs(n);
    const str = dgMoneyC(abs);
    return n < 0
      ? `<span style="color:var(--err);">−${str}</span>`
      : `<span style="color:var(--accent);">+${str}</span>`;
  }

  function postFlowBadge(row) {
    const isIn = row?.is_money_in || row?.direction === 'in' || Number(row?.amount_cents || 0) > 0;
    const label = row?.direction_label || (isIn ? recLabel('moneyIn') : recLabel('moneyOut'));
    const color = isIn ? 'var(--accent)' : 'var(--err)';
    return `<span style="font-family:var(--mono);font-size:10px;color:${color};background:var(--surface2);border:1px solid var(--border);border-radius:999px;padding:2px 8px;white-space:nowrap;">${postEscapeHtml(label)}</span>`;
  }

  function postRowDescription(row) {
    if (!row) return '';
    if (row.description) return String(row.description);
    const payload = row._payload || {};
    const found = Object.values(payload).find(v => v && String(v).trim().length > 3);
    return found ? String(found) : '';
  }

  async function postLoadAccounts() {
    if (_postAccounts.length) return _postAccounts;
    const res = await fetch('/api/accounts');
    const data = await res.json();
    _postAccounts = Array.isArray(data) ? data : (data.accounts || []);
    return _postAccounts;
  }

  function postAccountLabel(account) {
    return account.display_name || account.label || account.name || ('Account ' + account.id);
  }


  function postAccountDisplayText(account) {
    const label = postAccountLabel(account);
    const role = account.display_type || account.category_role || account.account_type || '';
    return dgIsPersonalMode() && role ? `${label} · ${role}` : label;
  }

  function postFindAccountByCleanName(accounts, cleanName) {
    const target = String(cleanName || '').toLowerCase();
    return accounts.find(a => {
      const label = postAccountLabel(a).toLowerCase();
      const raw = String(a.name || '').replace(/^Expenses:/,'').replace(/^Income:/,'').replace(/^Personal:/,'').toLowerCase();
      return label === target || raw === target;
    });
  }

  function postSuggestedCategory(accounts, row, isMoneyIn) {
    if (!dgIsPersonalMode()) return null;
    if (row && row.suggested_category_id) {
      const remembered = accounts.find(a => String(a.id) === String(row.suggested_category_id));
      if (remembered) return remembered;
    }
    const desc = postRowDescription(row).toLowerCase();
    if (isMoneyIn) {
      const paycheck = ['payroll','paycheck','salary','direct deposit','adp','gusto'];
      if (paycheck.some(t => desc.includes(t))) return postFindAccountByCleanName(accounts, 'Paycheck');
      if (['refund','reimbursement','venmo','zelle'].some(t => desc.includes(t))) return postFindAccountByCleanName(accounts, 'Reimbursements');
      return postFindAccountByCleanName(accounts, 'Other Personal Income') || accounts[0];
    }
    for (const rule of PERSONAL_QUICK_CATEGORIES) {
      if (rule.terms.some(t => desc.includes(t))) {
        const match = postFindAccountByCleanName(accounts, rule.label);
        if (match) return match;
      }
    }
    return postFindAccountByCleanName(accounts, 'Uncategorized Personal') || accounts[0];
  }

  function postRenderQuickActions(row, categoryAccounts, bankAccountId) {
    const box = document.getElementById('post-quick-actions');
    if (!box) return;
    const personal = dgIsPersonalMode();
    if (!personal) { box.style.display = 'none'; box.innerHTML = ''; return; }
    const isMoneyIn = row.is_money_in || row.direction === 'in' || Number(row.amount_cents || 0) > 0;
    const expenseAccounts = categoryAccounts.filter(a => a.account_type === 'expense');
    const assetAccounts = categoryAccounts.filter(a => a.account_type === 'asset' && String(a.id) !== String(bankAccountId));
    const incomeAccounts = categoryAccounts.filter(a => a.account_type === 'income');
    const preferredLabels = isMoneyIn
      ? ['Paycheck','Freelance / Side Work','Reimbursements','Other Personal Income']
      : ['Groceries','Dining Out','Subscriptions','Rent / Mortgage','Utilities','Transportation','Shopping','Uncategorized Personal'];
    const chips = [];
    preferredLabels.forEach(label => {
      const account = postFindAccountByCleanName(isMoneyIn ? incomeAccounts : expenseAccounts, label);
      if (account && !chips.some(c => c.id === account.id)) chips.push(account);
    });
    const chipHtml = chips.slice(0, 8).map(a =>
      `<button type="button" onclick="postPickCategory(${a.id})" style="background:var(--surface2);border:1px solid var(--border);border-radius:999px;color:var(--text-soft);font-size:11px;padding:4px 9px;cursor:pointer;">${postEscapeHtml(postAccountLabel(a))}</button>`
    ).join('');
    // Task 29: Show a dropdown when multiple asset accounts exist so the user
    // chooses the destination rather than silently landing on the first one.
    const transferBtn = assetAccounts.length === 0 ? ''
      : assetAccounts.length === 1
        ? `<button type="button" onclick="postPickCategory(${assetAccounts[0].id})" style="background:var(--accent-bg);border:1px solid var(--accent-ring);border-radius:999px;color:var(--accent);font-size:11px;padding:4px 9px;cursor:pointer;">${postEscapeHtml(recLabel('transfer'))}</button>`
        : `<select onchange="if(this.value){postPickCategory(parseInt(this.value,10));}" style="background:var(--accent-bg);border:1px solid var(--accent-ring);border-radius:999px;color:var(--accent);font-size:11px;padding:4px 9px;cursor:pointer;outline:none;">
            <option value="">↔ ${postEscapeHtml(recLabel('transfer'))} to…</option>
            ${assetAccounts.map(a => `<option value="${a.id}">${postEscapeHtml(postAccountLabel(a))}</option>`).join('')}
          </select>`;
    box.style.display = '';
    box.innerHTML = `<div style="font-family:var(--mono);font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.08em;margin-bottom:7px;">Quick picks — Daygig remembers what you choose</div><div style="display:flex;flex-wrap:wrap;gap:6px;">${chipHtml}${transferBtn}</div>`;
  }

  function postPickCategory(accountId) {
    const sel = document.getElementById('post-cat-acct');
    if (sel) sel.value = String(accountId);
  }

  function postFillSelect(select, accounts, selectedId) {
    if (!select) return;
    select.innerHTML = '<option value="">— select —</option>' + accounts.map(a =>
      `<option value="${a.id}" ${String(a.id) === String(selectedId || '') ? 'selected' : ''}>${postEscapeHtml(postAccountDisplayText(a))}</option>`
    ).join('');
  }

  async function postQueueLoad() {
    const body = document.getElementById('post-queue-body');
    const foot = document.getElementById('post-queue-footer');
    const badge = document.getElementById('txn-review-badge');
    const title = document.querySelector('#txn-review .card-title');
    const sub = document.querySelector('#txn-review .card-sub');
    const personal = dgIsPersonalMode();

    if (title) title.textContent = personal ? 'Transactions to Review' : 'Pending Rows';
    if (sub) sub.textContent = personal
      ? 'Imported money-in and money-out rows waiting for a category.'
      : 'Imported transactions waiting to be categorized and posted to the ledger';

    if (!body) return;
    body.innerHTML = '<tr><td colspan="5" style="padding:32px;text-align:center;color:var(--text-dim);">Loading…</td></tr>';
    try {
      const res = await fetch('/api/import/pending?limit=100');
      const data = await res.json();
      _postQueueRows = data.rows || [];
      if (badge) {
        badge.style.display = _postQueueRows.length ? '' : 'none';
        badge.textContent = _postQueueRows.length;
      }
      if (!_postQueueRows.length) {
        body.innerHTML = `<tr><td colspan="5" style="padding:32px;text-align:center;color:var(--text-dim);">
          <div style="margin-bottom:14px;">${recLabel('noPending')}</div>
          <button onclick="loadSampleData(this)" title="Imports a small example bank statement so you can try the flow without using real data." style="background:var(--surface2);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:7px 14px;cursor:pointer;">Load sample data to try it out</button>
        </td></tr>`;
        if (foot) foot.textContent = '';
        postCancel();
        return;
      }
      body.innerHTML = _postQueueRows.map(row => {
        const desc = postRowDescription(row) || '—';
        const suggested = row.suggested_category_label
          ? `<span style="margin-left:8px;font-family:var(--mono);font-size:10px;color:var(--accent);">Suggested: ${postEscapeHtml(row.suggested_category_label)}</span>`
          : '';
        return `<tr style="border-bottom:1px solid var(--border);">
          <td style="padding:7px 10px;color:var(--text-soft);font-family:var(--mono);font-size:12px;white-space:nowrap;">${postEscapeHtml(row.external_date || '—')}</td>
          <td style="padding:7px 10px;color:var(--text-dim);font-size:12px;">${postEscapeHtml(row.source || '—')}</td>
          <td style="padding:7px 10px;color:var(--text-soft);font-size:12px;max-width:360px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${postEscapeHtml(desc)}">${postFlowBadge(row)} <span style="margin-left:8px;">${postEscapeHtml(desc.substring(0, 72))}</span>${suggested}</td>
          <td style="padding:7px 10px;text-align:right;font-family:var(--mono);font-size:12px;">${postMoney(row.amount_cents)}</td>
          <td style="padding:7px 10px;text-align:right;white-space:nowrap;">
            <button onclick="postSelectRow(${row.id})" style="background:var(--accent-bg);border:1px solid var(--accent-ring);border-radius:5px;color:var(--accent);font-size:11px;padding:3px 8px;cursor:pointer;margin-right:6px;">${recLabel('categorize')}</button>
            <button onclick="recIgnoreRow(${row.id}, this)" style="background:transparent;border:1px solid var(--border);border-radius:5px;color:var(--text-dim);font-size:11px;padding:3px 8px;cursor:pointer;">${recLabel('skip')}</button>
          </td>
        </tr>`;
      }).join('');
      const inCount = _postQueueRows.filter(r => r.is_money_in || r.direction === 'in' || Number(r.amount_cents || 0) > 0).length;
      const outCount = _postQueueRows.filter(r => r.is_money_out || r.direction === 'out' || Number(r.amount_cents || 0) < 0).length;
      // Personal mode offers a one-click "accept all confident suggestions".
      const acceptable = personal
        ? _postQueueRows.filter(r => r.suggested_category_id &&
            (r.suggested_category_confidence === 'high' || r.suggested_category_confidence === 'medium')).length
        : 0;
      if (foot) {
        const countText = personal
          ? `${_postQueueRows.length} waiting · ${inCount} money in · ${outCount} money out`
          : `${_postQueueRows.length} pending row${_postQueueRows.length === 1 ? '' : 's'}`;
        foot.innerHTML = `<span>${countText}</span>` + (acceptable > 0
          ? ` <button id="accept-suggestions-btn" onclick="acceptAllSuggestions()" title="Categorize every row we're confident about. You can still review them in the ledger afterward." style="margin-left:12px;background:var(--accent);border:none;border-radius:6px;color:#06210f;font-size:12px;font-weight:600;padding:5px 12px;cursor:pointer;">Accept ${acceptable} suggestion${acceptable === 1 ? '' : 's'}</button>`
          : '');
      }
    } catch (e) {
      body.innerHTML = `<tr><td colspan="5" style="padding:20px;color:var(--err);">Error: ${postEscapeHtml(e.message)}</td></tr>`;
    }
  }

  async function postSelectRow(id) {
    const row = _postQueueRows.find(r => Number(r.id) === Number(id));
    if (!row) return;
    postSelectedRow = row;
    const card = document.getElementById('post-form-card');
    const sub = document.getElementById('post-form-sub');
    const result = document.getElementById('post-result');
    const descEl = document.getElementById('post-description');
    const bankSel = document.getElementById('post-bank-acct');
    const catSel = document.getElementById('post-cat-acct');
    const personal = dgIsPersonalMode();
    const isMoneyIn = row.is_money_in || row.direction === 'in' || Number(row.amount_cents || 0) > 0;

    if (card) {
      card.style.display = '';
      // The form sits above the list (order:-1); bring it into view so a row
      // clicked far down the list scrolls up to the populated box.
      try { card.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch (e) { card.scrollIntoView(); }
    }
    if (result) { result.style.display = 'none'; result.textContent = ''; }
    if (sub) {
      const suggestionNote = personal && row.suggested_category_label
        ? `<span style="margin-left:8px;color:var(--accent);">Suggested: ${postEscapeHtml(row.suggested_category_label)}</span>`
        : '';
      sub.innerHTML = `${postFlowBadge(row)} <span style="margin-left:8px;">${postEscapeHtml(row.external_date || '')} · ${postMoney(row.amount_cents)}</span>${suggestionNote}`;
    }
    if (descEl) descEl.value = postRowDescription(row) || '';

    try {
      const accounts = await postLoadAccounts();
      const bankAccounts = accounts.filter(a => a.account_type === 'asset');
      // Task 28: In personal mode, remove raw Assets:Bank: accounts when Personal:-prefixed
      // equivalents exist, preventing duplicate entries in the category dropdown.
      let categoryAccounts = accounts.filter(a => {
        if (personal) {
          if (isMoneyIn) return ['income', 'asset', 'liability'].includes(a.account_type);
          return ['expense', 'asset', 'liability'].includes(a.account_type);
        }
        if (isMoneyIn) return ['income', 'liability', 'equity'].includes(a.account_type);
        return a.account_type === 'expense' || a.account_type === 'cogs';
      });
      if (personal) {
        const hasPersonalAssets = categoryAccounts.some(a => a.account_type === 'asset' && String(a.name || '').startsWith('Personal:'));
        if (hasPersonalAssets) {
          categoryAccounts = categoryAccounts.filter(a => !(a.account_type === 'asset' && String(a.name || '').startsWith('Assets:Bank:')));
        }
      }

      const preferredBank = bankAccounts.find(a => String(a.name || '').includes('Personal:Checking')) || bankAccounts[0];
      const suggested = postSuggestedCategory(categoryAccounts, row, isMoneyIn);
      postFillSelect(bankSel, bankAccounts, preferredBank?.id);
      postFillSelect(catSel, categoryAccounts, suggested?.id);
      postRenderQuickActions(row, categoryAccounts, preferredBank?.id);

      const catLabel = document.querySelector('label[for="post-cat-acct"]') || catSel?.closest('div')?.querySelector('label');
      if (catLabel) catLabel.innerHTML = `${isMoneyIn ? (personal ? 'Income / Source / Transfer' : 'Income Account') : (personal ? 'Spending Category / Transfer' : 'Expense / COGS Account')} <span style="color:var(--err);">*</span>`;
    } catch (e) {
      if (result) {
        result.style.display = '';
        result.style.background = 'var(--err-bg)';
        result.style.color = 'var(--err)';
        result.textContent = 'Could not load accounts: ' + e.message;
      }
    }
  }

  function postCancel() {
    postSelectedRow = null;
    const card = document.getElementById('post-form-card');
    const result = document.getElementById('post-result');
    if (card) card.style.display = 'none';
    if (result) { result.style.display = 'none'; result.textContent = ''; }
  }

  async function postSubmit() {
    if (!postSelectedRow) return;
    const bankSel = document.getElementById('post-bank-acct');
    const catSel = document.getElementById('post-cat-acct');
    const descEl = document.getElementById('post-description');
    const result = document.getElementById('post-result');
    const bankAccount = _postAccounts.find(a => String(a.id) === String(bankSel?.value));
    const categoryId = catSel?.value;
    const categoryAccount = _postAccounts.find(a => String(a.id) === String(categoryId));
    const description = (descEl?.value || '').trim();

    if (!bankAccount || !categoryId || !description) {
      if (result) {
        result.style.display = '';
        result.style.background = 'var(--err-bg)';
        result.style.border = '1px solid rgba(255,92,108,.25)';
        result.style.color = 'var(--err)';
        result.textContent = 'Choose a bank account, choose a category, and enter a description.';
      }
      return;
    }

    if (result) {
      result.style.display = '';
      result.style.background = 'var(--surface2)';
      result.style.border = '1px solid var(--border)';
      result.style.color = 'var(--text-soft)';
      result.textContent = dgIsPersonalMode() ? 'Saving…' : 'Posting…';
    }

    try {
      const res = await fetch('/api/import/post/' + postSelectedRow.id, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          bank_account_name: bankAccount.name,
          category_account_id: Number(categoryId),
          description,
          memo: description,
        }),
      });
      const data = await res.json();
      if (!res.ok || !data.ok) throw new Error(data.error || (dgIsPersonalMode() ? 'Could not save this transaction.' : 'Post failed.'));
      if (result) {
        result.style.background = 'var(--accent-bg)';
        result.style.border = '1px solid var(--accent-ring)';
        result.style.color = 'var(--accent)';
        const isTransfer = dgIsPersonalMode() && categoryAccount && categoryAccount.account_type === 'asset';
        result.textContent = isTransfer ? '✓ Transfer marked.' : (dgIsPersonalMode() ? '✓ Transaction categorized.' : '✓ Row posted to ledger.');
      }
      postSelectedRow = null;
      setTimeout(() => {
        postCancel();
        postQueueLoad();
        if (typeof ledgerLoad === 'function') ledgerLoad();
        if (typeof loadDashboard === 'function') loadDashboard();
      }, 650);
    } catch (e) {
      if (result) {
        result.style.background = 'var(--err-bg)';
        result.style.border = '1px solid rgba(255,92,108,.25)';
        result.style.color = 'var(--err)';
        result.textContent = e.message;
      }
    }
  }

  // Load saved mappings on reconcile tab open
  async function recLoadSavedMappings() {
    recApplyProfileLabels();
    const res  = await fetch('/api/import/mappings');
    const data = await res.json();
    const keys = Object.keys(data);
    const container = document.getElementById('rec-saved-mappings');
    const list      = document.getElementById('rec-mappings-list');
    if (!container || !list) return;
    if (keys.length === 0) { container.style.display = 'none'; return; }
    container.style.display = 'block';
    list.innerHTML = keys.map(k =>
      `<button onclick="recApplyMapping('${k}')"
        style="background:var(--surface2);border:1px solid var(--border);border-radius:6px;color:var(--text-soft);font-size:12px;padding:5px 12px;cursor:pointer;">
        ${k}
      </button>`
    ).join('');
  }

  async function recApplyMapping(sourceName) {
    document.getElementById('rec-source-name').value = sourceName;
    const res  = await fetch('/api/import/mappings');
    const data = await res.json();
    if (data[sourceName] && recCsvB64) {
      recBuildMapStep(data[sourceName]);
      recGoToStep(2);
    }
  }

  // ── Helpers ───────────────────────────────────────────────────────────────

  // ── Transactions tab sub-nav ──────────────────────────────────────────────
  function txnTab(name) {
    recApplyProfileLabels();
    ['import','review','ledger'].forEach(t => {
      const el = document.getElementById('txn-' + t);
      if (el) el.style.display = t === name ? 'block' : 'none';
    });
    document.querySelectorAll('.txn-tab').forEach(btn => {
      const active = btn.dataset.txn === name;
      btn.classList.toggle('active', active);
      btn.style.color        = active ? 'var(--accent)'           : 'var(--text-soft)';
      btn.style.borderBottom = active ? '2px solid var(--accent)' : '2px solid transparent';
      btn.style.fontWeight   = active ? '600' : '500';
    });
    if (name === 'review') {
      if (typeof postQueueLoad === 'function') postQueueLoad();
      else recLoadQueue();
    }
    if (name === 'ledger') {
      if (typeof ledgerOffset !== 'undefined') ledgerOffset = 0;
      if (typeof ledgerLoad === 'function') ledgerLoad();
    }
    if (name === 'import') plaidImportTabInit();
  }

  // ── Plaid sync from import tab ────────────────────────────────────────

  function plaidImportMessage(text, kind = 'info') {
    const msg = document.getElementById('import-plaid-msg');
    if (!msg) return;
    msg.style.display = '';
    msg.style.color = kind === 'error' ? 'var(--err)' : (kind === 'ok' ? 'var(--accent)' : 'var(--text-soft)');
    msg.textContent = text;
  }

  async function plaidImportTabInit() {
    // Always show the bank-sync card. Missing credentials/connections should be
    // explained in-place instead of hiding the feature from users.
    const syncBlock   = document.getElementById('plaid-sync-block');
    const csvDivider  = document.getElementById('plaid-csv-divider');
    const banksEl     = document.getElementById('import-plaid-banks');
    const btn         = document.getElementById('import-plaid-sync-btn');
    if (syncBlock) syncBlock.style.display = 'block';
    if (csvDivider) csvDivider.style.display = 'flex';
    if (btn) btn.textContent = 'Connect Bank';
    if (!syncBlock) return;
    try {
      const res  = await fetch('/api/plaid/items');
      const data = await res.json();
      const items = data.items || [];
      if (banksEl) {
        banksEl.innerHTML = items.length ? items.map(item =>
          `<span style="background:var(--surface2);border:1px solid var(--border);border-radius:20px;padding:4px 12px;font-size:12px;color:var(--text-soft);">
            ${item.institution_name || 'Bank'}
            ${item.last_synced_at ? '<span style="font-family:var(--mono);font-size:10px;color:var(--text-dim);margin-left:6px;">synced ' + item.last_synced_at.slice(0,10) + '</span>' : ''}
          </span>`
        ).join('') : '<span style="font-family:var(--mono);font-size:11px;color:var(--text-dim);">No connected bank yet. Click Connect Bank and Daygig will tell you what is missing.</span>';
      }
      if (!items.length) {
        if (btn) btn.textContent = 'Connect Bank';
        plaidImportMessage('No bank is connected yet. Click Connect Bank to open Plaid Link, or import a CSV below.', 'info');
      } else {
        if (btn) btn.textContent = 'Sync from Bank';
        plaidImportMessage(`${items.length} connected bank${items.length === 1 ? '' : 's'} ready to sync.`, 'ok');
      }
    } catch(e) {
      if (banksEl) banksEl.innerHTML = '';
      plaidImportMessage('Bank sync is available, but Daygig could not check connected banks yet.', 'error');
    }
  }

  async function plaidSyncFromImport() {
    const btn  = document.getElementById('import-plaid-sync-btn');
    let finalButtonText = 'Connect Bank';
    if (btn) { btn.disabled = true; btn.textContent = 'Checking Plaid…'; }
    plaidImportMessage('Checking Plaid credentials…', 'info');
    try {
      const testRes = await fetch('/api/plaid/test');
      const testData = await testRes.json();
      if (!testData.ok) {
        plaidImportMessage(testData.error || 'Plaid is not configured. Add your Plaid Client ID and Secret in Settings → Integrations → Plaid.', 'error');
        if (btn) { btn.disabled = false; btn.textContent = 'Connect Bank'; }
        return;
      }

      const itemsRes = await fetch('/api/plaid/items');
      const itemsData = await itemsRes.json();
      const items = itemsData.items || [];
      if (items.length) finalButtonText = 'Sync from Bank';
      if (!items.length) {
        if (typeof window.plaidOpenLink === 'function') {
          plaidImportMessage('Opening Plaid Link so you can connect a bank…', 'info');
          if (btn) { btn.disabled = false; btn.textContent = 'Connect Bank'; }
          await window.plaidOpenLink({ messageTargetId: 'import-plaid-msg', buttonId: 'import-plaid-sync-btn', afterSuccess: plaidImportTabInit });
          return;
        }
        plaidImportMessage('Plaid credentials are saved. Go to Settings → Integrations → Plaid and click Connect Bank.', 'error');
        if (btn) { btn.disabled = false; btn.textContent = 'Connect Bank'; }
        return;
      }

      if (btn) btn.textContent = 'Syncing…';
      plaidImportMessage('Syncing transactions from Plaid…', 'info');
      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;
        plaidImportMessage(n > 0
          ? `✓ ${n} transaction${n === 1 ? '' : 's'} added — switching to Review…`
          : '✓ Up to date — no new transactions.', 'ok');
        plaidImportTabInit();
        if (n > 0) setTimeout(() => txnTab('review'), 1400);
      } else {
        plaidImportMessage(data.error || 'Sync failed. If this is your first time, connect a bank from Settings → Integrations → Plaid.', 'error');
      }
    } catch(e) {
      plaidImportMessage('Network error while contacting Plaid.', 'error');
    } finally {
      if (btn) { btn.disabled = false; btn.textContent = finalButtonText; }
    }
  }


  // Robust event bindings for packaged/browser builds. Inline handlers are kept
  // for compatibility, but these listeners make CSV import work even if a browser
  // blocks or misses an inline onchange/drop handler.
  function recBindImportFileControls() {
    const fileInput = document.getElementById('rec-file-input');
    const dropZone = document.getElementById('rec-drop-zone');
    if (fileInput && !fileInput.dataset.dgBound) {
      fileInput.dataset.dgBound = '1';
      fileInput.addEventListener('change', () => {
        const file = fileInput.files && fileInput.files[0];
        if (file) recProcessFile(file);
      });
    }
    if (dropZone && !dropZone.dataset.dgBound) {
      dropZone.dataset.dgBound = '1';
      dropZone.addEventListener('click', (e) => {
        e.preventDefault();
        if (fileInput) fileInput.click();
      });
      dropZone.addEventListener('dragover', recDragOver);
      dropZone.addEventListener('dragleave', recDragLeave);
      dropZone.addEventListener('drop', recDrop);
    }
  }

  document.addEventListener('DOMContentLoaded', () => {
    recBindImportFileControls();
    setTimeout(() => { recBindImportFileControls(); plaidImportTabInit(); }, 250);
  });

  async function acceptAllSuggestions() {
    const btn = document.getElementById('accept-suggestions-btn');
    const bankSel = document.getElementById('post-bank-acct');
    const bankAccount = _postAccounts.find(a => String(a.id) === String(bankSel?.value));
    if (btn) { btn.disabled = true; btn.textContent = 'Accepting…'; }
    try {
      const res = await fetch('/api/import/accept-suggestions', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ bank_account_name: bankAccount ? bankAccount.name : null }),
      });
      const data = await res.json();
      if (!res.ok || !data.ok) throw new Error(data.error || 'Could not accept suggestions.');
      // Refresh the queue, ledger, and dashboard to reflect the posted rows.
      postQueueLoad();
      if (typeof ledgerLoad === 'function') ledgerLoad();
      if (typeof loadDashboard === 'function') loadDashboard();
    } catch (e) {
      if (btn) { btn.disabled = false; btn.textContent = 'Accept suggestions'; }
      alert(e.message);
    }
  }

  async function loadSampleData(btn) {
    if (btn) { btn.disabled = true; btn.textContent = 'Loading…'; }
    try {
      const res = await fetch('/api/import/load-sample', {
        method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}',
      });
      const data = await res.json();
      if (!res.ok || !data.ok) throw new Error(data.error || 'Could not load sample data.');
      postQueueLoad();
      if (typeof loadDashboard === 'function') loadDashboard();
    } catch (e) {
      if (btn) { btn.disabled = false; btn.textContent = 'Load sample data to try it out'; }
      alert(e.message);
    }
  }

  Object.assign(window, {
    recFileSelected,
    recDrop,
    recDragOver,
    recDragLeave,
    recReset,
    recGoToStep,
    recToggleAmtFormat,
    recProceedToImport,
    recDoImport,
    recLoadQueue,
    recIgnoreRow,
    recLoadSavedMappings,
    recApplyMapping,
    txnTab,
    postSelectRow,
    postPickCategory,
    postCancel,
    postSubmit,
    postQueueLoad,
    acceptAllSuggestions,
    loadSampleData,
    plaidSyncFromImport,
  });
})();
