// T2 Falcon Admin — Basic Send Application (BSA)
// Marketplace & Applications .Mng → Basic Application.
// Phase 1: WhatsApp / Voice channel tabs · Outbox / Scheduled sub-tabs ·
// Quick Sort · Message List grid (status pills + row actions) · pagination.

const { useState: useStateBsa, useEffect: useEffectBsa, useRef: useRefBsa, useMemo: useMemoBsa } = React;

// ===== Status pill =====
const BSA_STATUS = {
  completed:   { label: 'Completed',           cls: 'bsa-st-completed' },
  in_progress: { label: 'In Progress',          cls: 'bsa-st-progress' },
  partial:     { label: 'Partially Processed',  cls: 'bsa-st-partial' },
  failed:      { label: 'Failed',               cls: 'bsa-st-failed' },
  canceled:    { label: 'Canceled',             cls: 'bsa-st-canceled' },
  scheduled:   { label: 'Scheduled',            cls: 'bsa-st-scheduled' },
  deleted:     { label: 'Deleted',              cls: 'bsa-st-deleted' },
};
const BsaStatusPill = ({ status, t }) => {
  const s = BSA_STATUS[status] || BSA_STATUS.completed;
  const label = (t && t['bsaStatus_' + status]) || s.label;
  return <span className={`status-badge bsa-status ${s.cls}`}><span className="dot" />{label}</span>;
};

// Date · time stamp → split for the two-line cell.
const bsaStamp = (s) => {
  if (!s) return { date: '—', time: '' };
  const [date, time] = String(s).split('·').map((x) => x.trim());
  return { date: date || '—', time: time || '' };
};
const BsaDateCell = ({ stamp }) => {
  const { date, time } = bsaStamp(stamp);
  return <span className="bsa-date"><span className="d">{date}</span>{time && <span className="tm">{time}</span>}</span>;
};

// Type chip (Marketing / Utility / Authentication / Dynamic / Static).
const bsaTypeCls = (type) => {
  const k = (type || '').toLowerCase();
  if (k === 'marketing') return 'mkt';
  if (k === 'utility') return 'util';
  if (k === 'authentication') return 'auth';
  if (k === 'dynamic') return 'dyn';
  if (k === 'static') return 'stat';
  return 'mkt';
};

// ===== SAR symbol (reuse the shared riyal glyph) =====
const BsaSar = () => (window.IcRiyal ? <IcRiyal size={12} /> : <span>SAR</span>);

// ===== Recipients label — contact group(s), manual recipient(s), or both =====
const bsaDial = (code) => { const C = (typeof COUNTRIES !== 'undefined' && COUNTRIES) || window.COUNTRIES || []; return (C.find((c) => c.code === (code || 'SA')) || {}).dial || '+966'; };
// Split a full "+966 57 283 8628" number into { phoneCountry, phone } for a manual-recipient input.
const bsaSplitNum = (full) => {
  const s = (full || '').replace(/\s/g, '');
  const C = (typeof COUNTRIES !== 'undefined' && COUNTRIES) || window.COUNTRIES || [];
  const m = C.filter((c) => c.dial && s.startsWith(c.dial)).sort((a, b) => b.dial.length - a.dial.length)[0];
  return m ? { phoneCountry: m.code, phone: s.slice(m.dial.length) } : { phoneCountry: 'SA', phone: s.replace(/^\+966/, '').replace(/^\+/, '') };
};
const bsaRecipientParts = (txn) => ({
  groups: Array.isArray(txn.recipientsList) ? txn.recipientsList.filter(Boolean) : (txn.recipients ? [String(txn.recipients)] : []),
  manual: Array.isArray(txn.manualRecipients) ? txn.manualRecipients.filter(Boolean) : [],
});
// True when the label needs the multi-token treatment (more than one recipient source/item).
const bsaRecipientRich = (txn) => { const { groups, manual } = bsaRecipientParts(txn); return (groups.length + manual.length) > 1; };
// Small icons used inside the recipients popover (contact group vs manual number).
const BsaGroupGlyph = () => (<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>);
const BsaPhoneGlyph = () => (<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.81.36 1.6.7 2.34a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.74-1.74a2 2 0 0 1 2.11-.45c.74.34 1.53.57 2.34.7A2 2 0 0 1 22 16.92z" /></svg>);
// Recipients: lead source + a "+N" badge that opens a "View all recipients" popover (share-list style).
const BsaRecipients = ({ txn }) => {
  const [open, setOpen] = useStateBsa(false);
  const [pos, setPos] = useStateBsa(null);
  const wrapRef = useRefBsa(null);
  const popRef = useRefBsa(null);
  useEffectBsa(() => {
    if (!open) return;
    const onDown = (e) => { if (popRef.current && popRef.current.contains(e.target)) return; if (wrapRef.current && wrapRef.current.contains(e.target)) return; setOpen(false); };
    const close = () => setOpen(false);
    document.addEventListener('mousedown', onDown);
    window.addEventListener('scroll', close, true);
    window.addEventListener('resize', close);
    return () => { document.removeEventListener('mousedown', onDown); window.removeEventListener('scroll', close, true); window.removeEventListener('resize', close); };
  }, [open]);
  const { groups, manual } = bsaRecipientParts(txn);
  const items = [...groups.map((x) => ({ kind: 'group', label: x })), ...manual.map((x) => ({ kind: 'manual', label: x }))];
  if (!items.length) return '—';
  if (items.length === 1) return items[0].label;
  const toggle = (e) => {
    e.stopPropagation();
    if (open) { setOpen(false); return; }
    const r = e.currentTarget.getBoundingClientRect();
    const spaceBelow = window.innerHeight - r.bottom;
    const POP = 300;
    let top, maxH;
    if (spaceBelow < 200 && r.top > spaceBelow) { maxH = Math.min(POP, r.top - 16); top = r.top - maxH - 6; }
    else { maxH = Math.min(POP, spaceBelow - 16); top = r.bottom + 6; }
    setPos({ top, left: Math.max(8, Math.min(r.right - 280, window.innerWidth - 308)), maxH });
    setOpen(true);
  };
  return (
    <span className="bsa-recip-multi" ref={wrapRef}>
      <span className="bsa-recip-name">{items[0].label}</span>
      <button type="button" className={`bsa-more-tag bsa-recip-more ${open ? 'on' : ''}`} onClick={toggle} title={items.map((it) => it.label).join(', ')}>+{items.length - 1}</button>
      {open && pos && ReactDOM.createPortal(
        <div ref={popRef} className="cg-shared-pop bsa-recip-pop" style={{ position: 'fixed', top: pos.top, left: pos.left, maxHeight: pos.maxH }}>
          <div className="cg-shared-pop-head">All recipients ({items.length})</div>
          <div className="cg-shared-pop-list">
            {items.map((it, i) => (
              <div key={i} className="cg-shared-pop-item">
                <span className="cg-shared-pop-check">{it.kind === 'manual' ? <BsaPhoneGlyph /> : <BsaGroupGlyph />}</span>
                <span className="cg-shared-pop-name">{it.label}</span>
              </div>
            ))}
          </div>
        </div>,
        document.body
      )}
    </span>
  );
};

// ===== Animated count-up number (animates 0→value on mount, and between values on change) =====
const BsaCountUp = ({ value, dur = 800, decimals = 0 }) => {
  const target = Number(value) || 0;
  const [disp, setDisp] = useStateBsa(0);
  const fromRef = useRefBsa(0);
  useEffectBsa(() => {
    const from = fromRef.current;
    let raf, start = null;
    const step = (ts) => {
      if (start === null) start = ts;
      const p = Math.min(1, (ts - start) / dur);
      const e = 1 - Math.pow(1 - p, 3);
      const cur = from + (target - from) * e;
      fromRef.current = cur; setDisp(cur);
      if (p < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [target, dur]);
  return <React.Fragment>{decimals ? disp.toFixed(decimals) : Math.round(disp).toLocaleString()}</React.Fragment>;
};

// ===== Lightweight dropdown (Quick Sort + filters) =====
const BsaSelect = ({ value, placeholder, options, onChange, width, disabled }) => {
  const [open, setOpen] = useStateBsa(false);
  const ref = useRefBsa(null);
  useEffectBsa(() => {
    if (!open) return;
    const f = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', f);
    return () => document.removeEventListener('mousedown', f);
  }, [open]);
  const cur = options.find((o) => (o.id || o) === value);
  return (
    <div className={`bsa-ddl ${open ? 'open' : ''} ${disabled ? 'is-disabled' : ''}`} ref={ref} style={width ? { width } : undefined}>
      <button type="button" className="bsa-ddl-control" disabled={disabled} onClick={() => !disabled && setOpen((o) => !o)}>
        <span className={cur ? '' : 'ph'}>{cur ? (cur.label || cur) : placeholder}</span>
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 9 12 15 18 9" /></svg>
      </button>
      {open && (
        <div className="bsa-ddl-pop">
          {options.map((o) => {
            const id = o.id || o, label = o.label || o;
            return <button key={id} type="button" className={`bsa-ddl-item ${id === value ? 'on' : ''} ${(o && o.cls) || ''}`} onClick={() => { onChange(id); setOpen(false); }}>{label}</button>;
          })}
        </div>
      )}
    </div>
  );
};

// ===== Contact-group multi-select (search + checkboxes + Apply) =====
const BsaGroupPicker = ({ groups, selected, onApply, disabled, t }) => {
  const [open, setOpen] = useStateBsa(false);
  const [q, setQ] = useStateBsa('');
  const [tab, setTab] = useStateBsa('mine'); // mine (Created by me) | shared (Shared with me)
  const ref = useRefBsa(null);
  useEffectBsa(() => { if (open) { setQ(''); setTab('mine'); } }, [open]);
  useEffectBsa(() => {
    if (!open) return;
    const f = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', f);
    return () => document.removeEventListener('mousedown', f);
  }, [open]);
  // Single select: only groups not already added are shown; clicking one adds it and closes.
  const avail = groups.filter((g) => !selected.includes(g.id) && (tab === 'shared' ? g.shared : !g.shared) && g.name.toLowerCase().includes(q.trim().toLowerCase()));
  const addOne = (id) => { onApply([...selected, id]); setOpen(false); };
  return (
    <div className={`bsa-gpick ${open ? 'open' : ''} ${disabled ? 'is-disabled' : ''}`} ref={ref}>
      <button type="button" className="bsa-gpick-btn" disabled={disabled} onClick={() => !disabled && setOpen((o) => !o)}>
        <span className="bsa-gpick-plus">＋</span> {t.bsaAddContactGroup || 'Add Contact Group'}
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 9 12 15 18 9" /></svg>
      </button>
      {open && (
        <div className="bsa-gpick-pop">
          <div className="bsa-gpick-tabs">
            <button type="button" className={tab === 'mine' ? 'on' : ''} onClick={() => setTab('mine')}>{t.bsaCreatedByMe || 'Created by me'}</button>
            <button type="button" className={tab === 'shared' ? 'on' : ''} onClick={() => setTab('shared')}>{t.bsaSharedWithMe || 'Shared with me'}</button>
          </div>
          <div className="bsa-gpick-search">
            <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7" /><line x1="21" y1="21" x2="16.65" y2="16.65" /></svg>
            <input value={q} onChange={(e) => setQ(e.target.value)} placeholder={t.bsaSearchGroups || 'Search groups…'} autoFocus />
          </div>
          <div className="bsa-gpick-list">
            {avail.map((g) => (
              <button key={g.id} type="button" className="bsa-gpick-opt bsa-gpick-opt-single" onClick={() => addOne(g.id)}>
                <span className="bsa-gpick-name">{g.name}</span>
                <span className="bsa-gpick-count">{(g.count || 0).toLocaleString()}</span>
              </button>
            ))}
            {avail.length === 0 && <div className="bsa-gpick-empty">{(selected.length && !q.trim()) ? (t.bsaAllGroupsAdded || 'All groups added') : (t.bsaNoGroupsFound || 'No groups found')}</div>}
          </div>
        </div>
      )}
    </div>
  );
};

// ===== Numbered step header (compose columns). Pass onToggle to make the whole header a hide/show control. =====
const BsaStepHead = ({ n, title, sub, onToggle, collapsed, toggleHint }) => (
  <div className={`bsa-step-h ${onToggle ? 'is-toggle' : ''}`} onClick={onToggle} role={onToggle ? 'button' : undefined} tabIndex={onToggle ? 0 : undefined} aria-expanded={onToggle ? !collapsed : undefined} title={onToggle ? toggleHint : undefined} onKeyDown={onToggle ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onToggle(); } } : undefined}>
    <span className="bsa-step-num">{n}</span>
    <span className="bsa-step-htext"><span className="bsa-step-title">{title}</span><span className="bsa-step-sub">{sub}</span></span>
    {onToggle && (
      <span className="bsa-step-eye" aria-hidden="true">
        {collapsed
          ? <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z" /><circle cx="12" cy="12" r="3" /></svg>
          : <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" /><line x1="1" y1="1" x2="23" y2="23" /></svg>}
      </span>
    )}
  </div>
);

// ===== Row action menu (3-dot) =====
const BsaRowMenu = ({ items, onClose }) => {
  const ref = useRefBsa(null);
  // Render as a FIXED element anchored to its trigger button so table overflow can't clip it.
  const [pos, setPos] = useStateBsa(null);
  useEffectBsa(() => {
    const menu = ref.current;
    const btn = menu && menu.parentElement && menu.parentElement.querySelector('.bsa-row-action-btn');
    if (btn) {
      const r = btn.getBoundingClientRect();
      const W = 152, H = Math.max(48, items.length * 40 + 12);
      let top = r.bottom + 4, left = r.right - W;
      if (top + H > window.innerHeight - 8) top = Math.max(8, r.top - H - 4); // flip up when near the bottom
      left = Math.min(left, window.innerWidth - W - 8);                      // keep inside the viewport
      if (left < 8) left = 8;
      setPos({ top, left });
    }
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target) && !e.target.closest('.bsa-row-action-btn')) onClose(); };
    const onScroll = () => onClose();
    document.addEventListener('mousedown', onDoc);
    window.addEventListener('scroll', onScroll, true);
    return () => { document.removeEventListener('mousedown', onDoc); window.removeEventListener('scroll', onScroll, true); };
  }, []);
  return (
    <div className="row-menu bsa-row-menu" ref={ref} onClick={(e) => e.stopPropagation()} style={pos ? { position: 'fixed', top: pos.top, left: pos.left, right: 'auto' } : { visibility: 'hidden' }}>
      {items.map((it) => (
        <button key={it.id} className={`row-menu-item ${it.danger ? 'cg-danger-item' : ''} ${it.disabled ? 'is-disabled' : ''}`} disabled={it.disabled} title={it.disabledHint || undefined} onClick={it.disabled ? undefined : it.onClick}>
          {it.icon} <span>{it.label}</span>
        </button>
      ))}
    </div>
  );
};

// ===== Confirm dialog (Cancel / Delete) =====
const BsaConfirm = ({ title, body, confirmLabel, cancelLabel, danger, onCancel, onConfirm }) => ReactDOM.createPortal(
  <div className="vis-warn-overlay" onMouseDown={onCancel}>
    <div className="vis-warn-modal" onMouseDown={(e) => e.stopPropagation()}>
      <div className="vis-warn-ic" style={danger ? { background: '#FDECEC', color: '#d92d20' } : undefined}>
        <svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /><line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" /></svg>
      </div>
      <div className="vis-warn-title">{title}</div>
      <div className="vis-warn-msg">{body}</div>
      <div className="vis-warn-actions">
        <button className="btn btn-secondary" onClick={onCancel}>{cancelLabel || 'Cancel'}</button>
        <button className={`btn ${danger ? 'meta-confirm-del' : 'btn-primary'}`} onClick={onConfirm}>{confirmLabel}</button>
      </div>
    </div>
  </div>,
  document.body
);

// ===== Outbox / Scheduled grid =====
const BsaMessageGrid = ({ rows, channel, mode, setMode, t, onDetails, onCancel, onEdit, onDelete, openMenuId, setOpenMenuId, page, pageSize, setPage, setPageSize, search, setSearch, typeFilter, setTypeFilter }) => {
  const scheduled = mode === 'scheduled';
  const isVoice = channel === 'voice';
  const start = (page - 1) * pageSize;
  const pageRows = rows.slice(start, start + pageSize);
  const nameCol = isVoice ? (t.bsaColIvrName || 'IVR Name') : (t.bsaColTemplate || 'Template Name');
  // Column count for the empty-state colSpan: base 10 + language (WhatsApp only) + scheduled date.
  const colCount = 10 + (isVoice ? 0 : 1) + (scheduled ? 1 : 0);

  return (
    <div className="table-panel bsa-table-panel">
      <div className="bsa-list-head">
        <div className="bsa-subtabs">
          <button className={`bsa-subtab ${mode === 'outbox' ? 'on' : ''}`} onClick={() => setMode('outbox')}>{t.bsaOutbox || 'Outbox'}</button>
          <button className={`bsa-subtab ${mode === 'scheduled' ? 'on' : ''}`} onClick={() => setMode('scheduled')}>{t.bsaScheduled || 'Scheduled'}</button>
        </div>
        <div className="bsa-list-tools">
          <div className="bsa-search"><IcSearch size={15} stroke={1.8} /><input placeholder={t.searchHere || 'Search Here ....'} value={search} onChange={(e) => { setSearch(e.target.value); setPage(1); }} /></div>
          <div className="bsa-daterange"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" /><line x1="16" y1="2" x2="16" y2="6" /><line x1="8" y1="2" x2="8" y2="6" /><line x1="3" y1="10" x2="21" y2="10" /></svg><span>{t.bsaDateFromTo || 'Date: From    To'}</span></div>
          <BsaSelect value={typeFilter} placeholder={t.bsaType || 'Type'} options={[{ id: 'all', label: t.cmShowAll || 'All' }, ...(isVoice ? [{ id: 'Dynamic', label: 'Dynamic' }, { id: 'Static', label: 'Static' }] : [{ id: 'Marketing', label: 'Marketing' }, { id: 'Utility', label: 'Utility' }, { id: 'Authentication', label: 'Authentication' }])]} onChange={(v) => { setTypeFilter(v); setPage(1); }} width={130} />
        </div>
      </div>
      <div className="table-scroll">
        <table className="bsa-table">
          <thead>
            <tr>
              <th>{t.bsaColTxnId || 'ID'}</th>
              <th>{t.bsaColSender || 'Sender ID'}</th>
              <th>{nameCol}</th>
              {!isVoice && <th>{t.bsaColLanguage || 'Language'}</th>}
              <th>{t.bsaColType || 'Type'}</th>
              <th>{t.bsaColCreated || 'Creation Date'}</th>
              {scheduled && <th>{t.bsaColScheduled || 'Scheduled Date'}</th>}
              <th>{t.bsaColCount || 'Recipient Count'}</th>
              <th>{t.bsaColCost || 'Transaction Cost'}</th>
              <th>{t.bsaColRecipient || 'Recipients'}</th>
              <th>{t.bsaColStatus || 'Status'}</th>
              <th className="bsa-col-action">{t.bsaColActions || 'Actions'}</th>
            </tr>
          </thead>
          <tbody>
            {pageRows.map((r) => {
              const isOpen = openMenuId === r.id;
              const canCancel = r.status === 'in_progress';
              const canDelete = scheduled && r.status === 'scheduled';
              const canEdit = scheduled && r.status === 'scheduled';
              const items = [];
              items.push({ id: 'details', label: t.bsaDetails || 'Details', icon: <IcInfo size={14} stroke={1.8} />, onClick: () => { setOpenMenuId(null); onDetails(r); } });
              if (canEdit) items.push({ id: 'edit', label: t.vsEdit || 'Edit', icon: <IcEdit size={14} stroke={1.8} />, onClick: () => { setOpenMenuId(null); onEdit(r); } });
              if (canCancel) items.push({ id: 'cancel', label: t.bsaCancel || 'Cancel', icon: <IcClose size={14} stroke={2} />, danger: true, onClick: () => { setOpenMenuId(null); onCancel(r); } });
              if (canDelete) items.push({ id: 'delete', label: t.delete || 'Delete', icon: <IcTrash size={14} stroke={1.8} />, danger: true, onClick: () => { setOpenMenuId(null); onDelete(r); } });
              return (
                <tr key={r.id} className={r.status === 'deleted' ? 'bsa-row-deleted' : ''}>
                  <td className="bsa-txnid">{r.id}</td>
                  <td className="bsa-sender">{r.sender}</td>
                  <td className="bsa-name">{r.templateName}</td>
                  {!isVoice && <td className="bsa-lang-cell">{r.language || '—'}</td>}
                  <td className="bsa-type-plain">{r.type}</td>
                  <td><BsaDateCell stamp={r.createdAt} /></td>
                  {scheduled && <td><BsaDateCell stamp={r.scheduledAt} /></td>}
                  <td className="bsa-count">{r.recipientsCount.toLocaleString()}</td>
                  <td className="bsa-cost"><IcRiyal size={12} /> {r.totalCost.toLocaleString()}</td>
                  <td className="bsa-recipient"><BsaRecipients txn={r} /></td>
                  <td><BsaStatusPill status={r.status} t={t} /></td>
                  <td className="bsa-col-action" style={{ position: 'relative' }}>
                    <button className={`row-action-btn bsa-row-action-btn ${isOpen ? 'open' : ''}`} onClick={(e) => { e.stopPropagation(); setOpenMenuId(isOpen ? null : r.id); }} aria-label="Actions"><IcMore size={16} /></button>
                    {isOpen && <BsaRowMenu items={items} onClose={() => setOpenMenuId(null)} />}
                  </td>
                </tr>
              );
            })}
            {pageRows.length === 0 && <tr><td colSpan={colCount} className="bsa-empty">{t.bsaNoTxns || 'No transactions yet.'}</td></tr>}
          </tbody>
        </table>
      </div>
      {window.TablePagination && <window.TablePagination total={rows.length} page={page} pageSize={pageSize} onPageChange={setPage} onPageSizeChange={(n) => { setPageSize(n); setPage(1); }} t={t} />}
    </div>
  );
};

// ===== Helpers: variable substitution, stats, recipient details =====
const bsaSubst = (text, vars) => String(text || '').replace(/\{\{\s*([\w]+)\s*\}\}/g, (m, k) => (vars && vars[k]) || (k === 'first_name' ? 'Ahmed' : k === 'last_name' ? 'Ali' : k === 'code' ? '482913' : k === 'ticket' ? 'TKT-2048' : k === 'amount' ? '1,500' : k === 'date' ? '02-Jul-2026' : `{{${k}}}`));

// WhatsApp-ish inline formatting (*bold*) → React nodes.
const bsaFmt = (text) => String(text || '').split('\n').map((line, li) => (
  <React.Fragment key={li}>{li > 0 && <br />}{line.split(/(\*[^*]+\*)/g).map((seg, si) => seg.startsWith('*') && seg.endsWith('*') ? <strong key={si}>{seg.slice(1, -1)}</strong> : seg)}</React.Fragment>
));

// Derive delivery funnel + rates from a transaction (deterministic, demo-only).
const bsaIsScheduled = (txn) => txn && (txn.status === 'scheduled' || txn.status === 'deleted');
// Per-status Details view: which figures + sections to show, and whether the cost is an estimate.
//  failed / scheduled → no delivery statistics, no cost breakdown, cost shown is the ESTIMATE.
//  in_progress / partial / canceled / completed → statistics + breakdown over what was processed.
const bsaStatusView = (txn) => {
  const s = txn && txn.status;
  if (s === 'failed' || bsaIsScheduled(txn)) return { stats: false, breakdown: false, costEstimated: true, processing: false };
  return { stats: true, breakdown: true, costEstimated: false, processing: s === 'in_progress' };
};
// Total Recipients / Total Cost shown on the Details upper row, per status.
const bsaHeadRecipients = (txn) => (txn.status === 'failed' ? (txn.plannedCount || 0) : (txn.recipientsCount || 0));
const bsaHeadCost = (txn) => (txn.status === 'failed' ? (txn.estimatedCost || 0) : (txn.totalCost || 0));
const bsaStatsFor = (txn) => {
  const total = Math.max(txn.recipientsCount || 0, 1);
  if (bsaIsScheduled(txn) || txn.status === 'failed') return {
    total, sent: 0, delivered: 0, read: 0, pending: total, replies: 0,
    sentPct: 0, deliveredPct: 0, readPct: 0, pendingPct: 0, deliveryRate: 0, readRate: 0, replyRate: 0,
    deliveredRate: 0, readRatePct: 0, playedRate: 0, seenRate: 0, failedRate: 0, avgDeliveryTime: '—',
    cost: 0, avg: 0, costByDest: [{ k: 'Saudi Arabia', v: 0 }, { k: 'Jordan', v: 0 }], scheduled: true,
  };
  const sent = Math.round(total * 0.983);
  const delivered = Math.round(total * 0.947);
  const read = Math.round(total * 0.761);
  const pending = total - sent;
  const replies = Math.round(read * 0.45);
  const cost = txn.totalCost || 0;
  const ksaCost = Math.round(cost * 0.82);
  return {
    total, sent, delivered, read, pending, replies,
    sentPct: 98.3, deliveredPct: 94.7, readPct: 76.1, pendingPct: 42.9,
    deliveryRate: 94.7, readRate: 87, replyRate: 45,
    // Spec rates (Delivered / Read / Played / Seen / Failed / Reply + avg delivery time)
    deliveredRate: 94.7, readRatePct: Math.round((read / delivered) * 100), playedRate: 68, seenRate: 72,
    failedRate: +(100 - 94.7).toFixed(1), avgDeliveryTime: '4.2s',
    cost, avg: total ? +(cost / total).toFixed(2) : 0,
    costByDest: [{ k: 'Saudi Arabia', v: ksaCost }, { k: 'Jordan', v: Math.max(0, cost - ksaCost) }],
  };
};

// Per-recipient detail rows (deterministic from count + status).
const BSA_RDIST = ['read', 'delivered', 'read', 'pending', 'seen', 'played', 'delivered', 'read', 'sent', 'read'];
const BSA_RNUMS = ['+966 57 283 8628', '+966 55 991 2030', '+966 53 384 4111', '+966 56 174 2284', '+966 50 998 2200', '+962 79 655 0500', '+966 54 220 7781', '+966 59 110 4467'];
const bsaRecipientsFor = (txn) => {
  const n = Math.min(txn.recipientsCount || 0, 24);
  const cost = txn.recipientsCount ? +((txn.totalCost || 0) / txn.recipientsCount).toFixed(0) : 0;
  const bp = (txn.createdAt || '27-Mar-2025 · 02:40 pm').split('·');
  const bDate = (bp[0] || '27-Mar-2025').trim(), bTime = (bp[1] || '02:40 pm').trim();
  const stamp = (m) => `${bDate} · ${bsaShiftTime(bTime, m)}`;
  const scheduled = bsaIsScheduled(txn);
  return Array.from({ length: n }, (_, i) => {
    if (scheduled) return { id: 'r' + i, number: BSA_RNUMS[i % BSA_RNUMS.length], status: 'pending', sendDate: null, deliveryDate: null, statusDate: null, replied: false, cost: 0 };
    const st = txn.status === 'failed' ? 'failed' : BSA_RDIST[i % BSA_RDIST.length];
    const sent = st !== 'pending';
    const delivered = st === 'delivered' || st === 'read' || st === 'played' || st === 'seen';
    const engaged = st === 'read' || st === 'played' || st === 'seen';
    return {
      id: 'r' + i, number: BSA_RNUMS[i % BSA_RNUMS.length], status: st,
      sendDate: sent ? stamp(0) : null,
      deliveryDate: delivered ? stamp(1 + (i % 3)) : null,
      statusDate: sent ? stamp((1 + (i % 3)) + (engaged ? 2 : 0)) : null,
      replied: engaged && i % 3 === 0,
      cost: cost || 250,
    };
  });
};

const BSA_RSTATUS = {
  read: { label: 'Read', cls: 'rd' }, delivered: { label: 'Delivered', cls: 'dl' },
  sent: { label: 'Sent', cls: 'st' }, pending: { label: 'Pending', cls: 'pd' },
  played: { label: 'Played', cls: 'pl' }, seen: { label: 'Seen', cls: 'sn' },
  failed: { label: 'Failed', cls: 'fl' },
};

// ===== Phone preview (WhatsApp-style bubble) =====
const BsaPhonePreview = ({ body, vars, empty }) => (
  <div className="bsa-phone">
    <div className="bsa-phone-screen">
      <div className="bsa-phone-body">
        <div className="bsa-phone-today">Today</div>
        {empty ? (
          <div className="bsa-phone-empty">Choose a template to view its content.</div>
        ) : (
          <div className="bsa-phone-bubble">
            {body.title && <div className="bsa-bubble-title">{bsaFmt(bsaSubst(body.title, vars))}</div>}
            <div className="bsa-bubble-text">{bsaFmt(bsaSubst(body.body, vars))}</div>
            {body.footer && <div className="bsa-bubble-footer">{body.footer}</div>}
            <div className="bsa-bubble-time">13:54 ✓✓</div>
            {body.button && <div className="bsa-bubble-btn">{body.button}</div>}
          </div>
        )}
      </div>
    </div>
    <div className="bsa-phone-frame" aria-hidden="true" />
  </div>
);

// ===== Confirmation overlay before send (duplicate handling + cost estimate) =====
const BsaSendConfirm = ({ recipients, costPerMsg, t, onCancel, onConfirm }) => {
  const [allowDup, setAllowDup] = useStateBsa(false);
  const est = (recipients * costPerMsg);
  return ReactDOM.createPortal(
    <div className="vis-warn-overlay" onMouseDown={onCancel}>
      <div className="vis-warn-modal bsa-sendconfirm" onMouseDown={(e) => e.stopPropagation()}>
        <div className="vis-warn-ic bsa-sendconfirm-ic">
          <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" strokeWidth="1.85" strokeLinecap="round" strokeLinejoin="round"><path d="m22 2-7 20-4-9-9-4Z" /><path d="M22 2 11 13" /></svg>
        </div>
        <div className="vis-warn-title">{t.bsaConfirmTitle || 'Confirm & send'}</div>
        <div className="vis-warn-msg bsa-sendconfirm-msg">{t.bsaCostNote || 'The cost below is an estimate based on your recipients, template category, and active contract. Your balance is charged at send time.'}</div>
        <div className="bsa-sc-panel">
          <div className="bsa-sc-item">
            <span className="bsa-sc-ic">{BSA_DICONS.recipients}</span>
            <span className="bsa-sc-meta"><span className="bsa-sc-label">{t.bsaRecipientsLbl || 'Recipients'}</span><span className="bsa-sc-value">{recipients.toLocaleString()}</span></span>
          </div>
          <span className="bsa-sc-div" aria-hidden="true" />
          <div className="bsa-sc-item bsa-sc-cost">
            <span className="bsa-sc-ic">{BSA_DICONS.coins}</span>
            <span className="bsa-sc-meta"><span className="bsa-sc-label">{t.bsaEstCost || 'Estimated cost'}</span><span className="bsa-sc-value"><IcRiyal size={17} /> {est.toLocaleString()}</span></span>
          </div>
        </div>
        <label className="bsa-dup-toggle">
          <input type="checkbox" checked={allowDup} onChange={(e) => setAllowDup(e.target.checked)} />
          <span className="bsa-dup-switch" aria-hidden="true"><span className="bsa-dup-knob" /></span>
          <span className="bsa-dup-toggle-txt">{t.bsaAllowDup || 'Allow duplicate recipients'}</span>
        </label>
        <div className="vis-warn-actions">
          <button className="btn btn-secondary" onClick={onCancel}>{t.bsaCancel || 'Cancel'}</button>
          <button className="btn btn-primary" onClick={() => onConfirm({ allowDup })}>{t.bsaConfirmSend || 'Confirm & Send'}</button>
        </div>
      </div>
    </div>,
    document.body
  );
};

// ===== WhatsApp composer helpers (preview sample values, column auto-mapping, simulated Meta status) =====
const BSA_SAMPLE = { first_name: 'Ahmed', last_name: 'Al-Otaibi', name: 'Ahmed', full_name: 'Ahmed Al-Otaibi', code: '4821', otp: '4821', ticket: 'TK-2207', amount: '250', date: '02 Jul', city: 'Riyadh', age: '29', gender: 'M', tier: 'Gold', order: 'OR-5589' };
const bsaSampleVal = (n) => BSA_SAMPLE[n] || (n ? String(n).replace(/_/g, ' ') : '');
// Per-recipient sample values — so a transaction's Details preview can show each recipient's own message.
const BSA_RVALS = {
  first_name: ['Ahmed', 'Sara', 'Khalid', 'Lina', 'Omar', 'Noura', 'Faisal', 'Maha', 'Yousef', 'Reem'],
  last_name: ['Al-Otaibi', 'Al-Harbi', 'Al-Qahtani', 'Al-Shehri', 'Al-Dosari'], name: ['Ahmed', 'Sara', 'Khalid', 'Lina', 'Omar'],
  code: ['4821', '7390', '1582', '9043', '6275'], otp: ['4821', '7390', '1582'], ticket: ['TK-2207', 'TK-3315', 'TK-9981'],
  amount: ['250', '480', '120', '600', '95'], date: ['02 Jul', '05 Jul', '10 Jul', '18 Jul'], city: ['Riyadh', 'Jeddah', 'Dammam'], order: ['OR-5589', 'OR-6610', 'OR-7732'],
};
const bsaRecipVal = (v, i) => { const pool = BSA_RVALS[v]; return pool ? pool[i % pool.length] : bsaSampleVal(v); };
const bsaPretty = (v) => String(v || '').replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
const bsaFmtDate = (d) => { try { const dt = d ? new Date(d) : new Date(); if (isNaN(dt.getTime())) return String(d || ''); return dt.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); } catch (e) { return String(d || ''); } };
const bsaAutoCol = (cols, name) => cols.find((c) => c.toLowerCase() === String(name).toLowerCase()) || cols.find((c) => c.toLowerCase().includes(String(name).toLowerCase()) || String(name).toLowerCase().includes(c.toLowerCase())) || '';
const bsaAutoMobile = (cols) => cols.find((c) => /mobile|phone|msisdn|number/i.test(c)) || cols[0] || '';
// Simulated Meta template status (everything Approved except one, to demo the "pick another template" path).
const BSA_TPL_META = { wt5: 'Paused' };
const bsaTplMeta = (id) => (id ? (BSA_TPL_META[id] || 'Approved') : null);
// Sample sheet data so each contact group can show its "first two rows" for column mapping.
const BSA_COL_SAMPLES = {
  mobile: ['+966 57 283 8628', '+966 55 991 2030'], phone: ['+966 57 283 8628', '+966 55 991 2030'], msisdn: ['+966 57 283 8628', '+966 55 991 2030'],
  first_name: ['Ahmed', 'Sara'], last_name: ['Al-Otaibi', 'Al-Harbi'], name: ['Ahmed Al-Otaibi', 'Sara Al-Harbi'],
  age: ['29', '34'], gender: ['M', 'F'], city: ['Riyadh', 'Jeddah'], tier: ['Gold', 'Silver'], code: ['4821', '7390'],
};
const bsaColSample = (col, i) => (BSA_COL_SAMPLES[col] ? BSA_COL_SAMPLES[col][i] : (bsaSampleVal(col) + (i ? ' ' + (i + 1) : '')));
const bsaGroupRows = (group) => [0, 1].map((i) => { const r = {}; (group.columns || []).forEach((c) => { r[c] = bsaColSample(c, i); }); return r; });

// ===== Falcon-themed time picker (hour / minute / AM-PM columns, teal selection) =====
// Time picker: dropdown (anchored to the field) with a big editable HH:MM, AM/PM toggle, clock dial + Cancel/OK.
const BsaTimePicker = ({ value, onChange }) => {
  const parse = (v) => { const mt = /(\d{1,2}):(\d{2})\s*(AM|PM)/i.exec(v || '09:00 AM') || []; return { h: mt[1] ? parseInt(mt[1], 10) : 9, mi: mt[2] ? parseInt(mt[2], 10) : 0, ap: (mt[3] || 'AM').toUpperCase() }; };
  const pad = (n) => String(n).padStart(2, '0');
  const cur = parse(value);
  const [open, setOpen] = useStateBsa(false);
  const [up, setUp] = useStateBsa(false);          // flip above the field when there isn't room below
  const [mode, setMode] = useStateBsa('hour');     // which field the dial edits
  const [hour, setHour] = useStateBsa(cur.h);
  const [minute, setMinute] = useStateBsa(cur.mi);
  const [ampm, setAmpm] = useStateBsa(cur.ap);
  const [editField, setEditField] = useStateBsa(null);  // field currently being typed into
  const [editStr, setEditStr] = useStateBsa('');
  const wrapRef = useRefBsa(null);
  useEffectBsa(() => {
    if (!open) return;
    const onDoc = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
    document.addEventListener('mousedown', onDoc); document.addEventListener('keydown', onKey);
    return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); };
  }, [open]);
  useEffectBsa(() => { if (!open || !wrapRef.current) return; const r = wrapRef.current.getBoundingClientRect(); setUp((window.innerHeight - r.bottom) < 430); }, [open]);
  const openDlg = () => { const c = parse(value); setHour(c.h); setMinute(c.mi); setAmpm(c.ap); setMode('hour'); setEditField(null); setOpen(true); };
  const commit = () => { onChange(`${pad(hour)}:${pad(minute)} ${ampm}`); setOpen(false); };
  // Dial geometry (240×240 face, numbers on a radius; 12 o'clock at the top).
  const C = 120, R = 90;
  const nums = mode === 'hour' ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 12 }, (_, i) => i * 5);
  const angleDeg = (v) => (mode === 'hour' ? (v % 12) * 30 : v * 6) - 90;
  const ha = angleDeg(mode === 'hour' ? hour : minute) * Math.PI / 180;
  const hx = C + R * Math.cos(ha), hy = C + R * Math.sin(ha);
  const pick = (n) => { setEditField(null); if (mode === 'hour') { setHour(n); setMode('minute'); } else { setMinute(n); } };
  // Editable HH / MM: show the raw string while typing, commit + clamp on blur.
  const dispHour = editField === 'hour' ? editStr : pad(hour);
  const dispMin = editField === 'minute' ? editStr : pad(minute);
  const focusField = (f) => { setMode(f); setEditField(f); setEditStr(f === 'hour' ? pad(hour) : pad(minute)); };
  const changeField = (f, raw) => { const d = raw.replace(/\D/g, '').slice(0, 2); setEditStr(d); const n = parseInt(d, 10); if (!isNaN(n)) { if (f === 'hour' && n >= 1 && n <= 12) setHour(n); if (f === 'minute' && n >= 0 && n <= 59) setMinute(n); } };
  const blurField = (f) => { const n = parseInt(editStr, 10); if (f === 'hour') setHour(isNaN(n) || n < 1 ? 12 : Math.min(12, n)); else setMinute(isNaN(n) || n < 0 ? 0 : Math.min(59, n)); setEditField(null); };
  return (
    <div className="bsa-tp-wrap" ref={wrapRef}>
      <button type="button" className="bsa-tp-trigger" onClick={() => (open ? setOpen(false) : openDlg())}>
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9" /><polyline points="12 7 12 12 15 14" /></svg>
        <span>{pad(cur.h)}:{pad(cur.mi)} {cur.ap}</span>
      </button>
      {open && (
        <div className={`bsa-tp-pop ${up ? 'up' : ''}`}>
          <div className="bsa-tp-h">Select time</div>
          <div className="bsa-tp-display">
            <input type="text" inputMode="numeric" aria-label="Hour" className={`bsa-tp-big ${mode === 'hour' ? 'on' : ''}`} value={dispHour} onFocus={(e) => { focusField('hour'); e.target.select(); }} onChange={(e) => changeField('hour', e.target.value)} onBlur={() => blurField('hour')} />
            <span className="bsa-tp-colon">:</span>
            <input type="text" inputMode="numeric" aria-label="Minute" className={`bsa-tp-big ${mode === 'minute' ? 'on' : ''}`} value={dispMin} onFocus={(e) => { focusField('minute'); e.target.select(); }} onChange={(e) => changeField('minute', e.target.value)} onBlur={() => blurField('minute')} />
            <div className="bsa-tp-ap">
              <button type="button" className={ampm === 'AM' ? 'on' : ''} onClick={() => setAmpm('AM')}>AM</button>
              <button type="button" className={ampm === 'PM' ? 'on' : ''} onClick={() => setAmpm('PM')}>PM</button>
            </div>
          </div>
          <div className="bsa-tp-clock">
            <svg className="bsa-tp-dial" width="220" height="220" viewBox="0 0 240 240">
              <circle className="bsa-tp-face" cx={C} cy={C} r="112" />
              <line className="bsa-tp-hand" x1={C} y1={C} x2={hx} y2={hy} />
              <circle className="bsa-tp-knob" cx={hx} cy={hy} r="19" />
              <circle className="bsa-tp-hub" cx={C} cy={C} r="4" />
            </svg>
            {nums.map((n) => {
              const a = angleDeg(n) * Math.PI / 180;
              const x = (C + R * Math.cos(a)) / 240 * 100, y = (C + R * Math.sin(a)) / 240 * 100;
              const on = mode === 'hour' ? n === hour : n === minute;
              return <button key={n} type="button" className={`bsa-tp-num ${on ? 'on' : ''}`} style={{ left: x + '%', top: y + '%' }} onClick={() => pick(n)}>{mode === 'hour' ? n : pad(n)}</button>;
            })}
          </div>
          <div className="bsa-tp-actions">
            <button type="button" className="bsa-tp-btn" onClick={() => setOpen(false)}>Cancel</button>
            <button type="button" className="bsa-tp-btn primary" onClick={commit}>OK</button>
          </div>
        </div>
      )}
    </div>
  );
};

// Voice retry-logic: which call results can trigger an automatic re-attempt.
const BSA_RETRY_STATUSES = [
  { id: 'no_answer', label: 'No Answer' },
  { id: 'busy', label: 'Busy' },
  { id: 'cancel', label: 'Cancel' },
  { id: 'failed', label: 'Failed' },
];
const BsaCheck = () => <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3.2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5" /></svg>;
// Info (ⓘ) badge for a stat card — top-right corner with a hover/focus tooltip describing the metric.
const BsaCostInfo = ({ tip }) => (
  <span className="bsa-cost-info" tabIndex={0} aria-label={tip}>
    <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><line x1="12" y1="16" x2="12" y2="12" /><line x1="12" y1="8" x2="12.01" y2="8" /></svg>
    <span className="bsa-cost-tip">{tip}</span>
  </span>
);

// ===== Combined Date & Time picker (single Falcon-styled component) =====
const BSA_MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const BSA_DOW = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
const bsaSameDay = (a, b) => !!a && !!b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
const BsaDateTimePicker = ({ date, time, onDate, onTime, t = {}, preferUp }) => {
  const [open, setOpen] = useStateBsa(false);
  const [up, setUp] = useStateBsa(!!preferUp);
  const [view, setView] = useStateBsa(() => (date ? new Date(date) : new Date()));
  const ref = useRefBsa(null);
  useEffectBsa(() => { if (!open) return; const f = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', f); return () => document.removeEventListener('mousedown', f); }, [open]);
  useEffectBsa(() => { if (!open || !ref.current) return; const r = ref.current.getBoundingClientRect(); const below = window.innerHeight - r.bottom, above = r.top, POP = 430; setUp((preferUp && above > 260) || (below < POP && above > below)); }, [open, preferUp]);
  const pad = (n) => String(n).padStart(2, '0');
  const tm = /(\d{1,2}):(\d{2})\s*(AM|PM)/i.exec(time || '09:00 AM') || [];
  const hr12 = tm[1] ? parseInt(tm[1], 10) : 9, min = tm[2] ? parseInt(tm[2], 10) : 0, ap = (tm[3] || 'AM').toUpperCase();
  const setT = (h, mi, a) => onTime(`${pad(h)}:${pad(mi)} ${a}`);
  const bumpHr = (d) => { let h = hr12 + d; if (h > 12) h = 1; if (h < 1) h = 12; setT(h, min, ap); };
  const bumpMin = (d) => { let mi = min + d; if (mi >= 60) mi = 0; if (mi < 0) mi = 55; setT(hr12, mi, ap); };
  const y = view.getFullYear(), mo = view.getMonth();
  const firstDow = new Date(y, mo, 1).getDay(), daysIn = new Date(y, mo + 1, 0).getDate();
  const today = new Date(); today.setHours(0, 0, 0, 0);
  const cells = []; for (let i = 0; i < firstDow; i++) cells.push(null); for (let d = 1; d <= daysIn; d++) cells.push(new Date(y, mo, d)); while (cells.length % 7 !== 0) cells.push(null);
  const label = date ? `${pad(date.getDate())} ${BSA_MONTHS[date.getMonth()].slice(0, 3)} ${date.getFullYear()} · ${time || '09:00 AM'}` : (t.bsaPickDateTime || 'Select date & time');
  return (
    <div className={`bsa-dtp ${open ? 'is-open' : ''}`} ref={ref}>
      <button type="button" className="bsa-dtp-trigger" onClick={() => setOpen((o) => !o)}>
        <span className={date ? '' : 'ph'}>{label}</span>
        <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" /><line x1="16" y1="2" x2="16" y2="6" /><line x1="8" y1="2" x2="8" y2="6" /><line x1="3" y1="10" x2="21" y2="10" /><circle cx="16" cy="15.5" r="2.6" /></svg>
      </button>
      {open && (
        <div className={`bsa-dtp-pop ${up ? 'up' : ''}`}>
          <div className="bsa-dtp-head">
            <button type="button" className="bsa-dtp-nav" onClick={() => setView(new Date(y, mo - 1, 1))} aria-label="Previous month"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6" /></svg></button>
            <span className="bsa-dtp-title">{BSA_MONTHS[mo]} {y}</span>
            <button type="button" className="bsa-dtp-nav" onClick={() => setView(new Date(y, mo + 1, 1))} aria-label="Next month"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6" /></svg></button>
          </div>
          <div className="bsa-dtp-dow">{BSA_DOW.map((d) => <span key={d}>{d}</span>)}</div>
          <div className="bsa-dtp-grid">
            {cells.map((d, i) => d ? (
              <button key={i} type="button" className={`bsa-dtp-cell ${bsaSameDay(d, date) ? 'on' : ''} ${bsaSameDay(d, today) ? 'today' : ''}`} onClick={() => { onDate(d); if (!time) onTime('09:00 AM'); }}>{d.getDate()}</button>
            ) : <span key={i} className="bsa-dtp-cell empty" />)}
          </div>
          <div className="bsa-dtp-time">
            <div className="bsa-dtp-time-lbl">{t.bsaTime || 'Time'}</div>
            <div className="bsa-dtp-time-row">
              <div className="bsa-dtp-stepper"><button type="button" onClick={() => bumpHr(1)}>▲</button><input className="bsa-dtp-num" type="text" inputMode="numeric" maxLength={2} value={pad(hr12)} onFocus={(e) => e.target.select()} onChange={(e) => { const d = e.target.value.replace(/\D/g, '').slice(-2); if (!d) return; setT(Math.min(12, Math.max(1, parseInt(d, 10))), min, ap); }} /><button type="button" onClick={() => bumpHr(-1)}>▼</button></div>
              <span className="bsa-dtp-colon">:</span>
              <div className="bsa-dtp-stepper"><button type="button" onClick={() => bumpMin(5)}>▲</button><input className="bsa-dtp-num" type="text" inputMode="numeric" maxLength={2} value={pad(min)} onFocus={(e) => e.target.select()} onChange={(e) => { const d = e.target.value.replace(/\D/g, '').slice(-2); if (!d) return; setT(hr12, Math.min(59, Math.max(0, parseInt(d, 10))), ap); }} /><button type="button" onClick={() => bumpMin(-5)}>▼</button></div>
              <div className="bsa-dtp-ampm"><button type="button" className={ap === 'AM' ? 'on' : ''} onClick={() => setT(hr12, min, 'AM')}>AM</button><button type="button" className={ap === 'PM' ? 'on' : ''} onClick={() => setT(hr12, min, 'PM')}>PM</button></div>
            </div>
          </div>
          <div className="bsa-dtp-foot">
            <button type="button" className="bsa-dtp-clear" onClick={() => { onDate(null); }}>{t.bsaClear || 'Clear'}</button>
            <button type="button" className="bsa-dtp-done" onClick={() => setOpen(false)}>{t.bsaDone || 'Done'}</button>
          </div>
        </div>
      )}
    </div>
  );
};

// ===== Compose wizard (Send / Edit) =====
const BsaCompose = ({ channel, t, pushToast, onCancel, onSent, editRow, prefill, fromConversation, onBackToConversation }) => {
  const isVoice = channel === 'voice';
  const senders = isVoice ? (window.bsaVoiceSenders || []) : (window.bsaWaSenders || []);
  const templates = isVoice ? (window.bsaVoiceTemplates || []) : (window.bsaWaTemplates || []);
  const groups = window.bsaContactGroups || [];

  const [sender, setSender] = useStateBsa(editRow ? editRow.sender : null);
  const [cat, setCat] = useStateBsa('');
  const [lang, setLang] = useStateBsa('');
  const [tplId, setTplId] = useStateBsa(editRow ? editRow.templateId : '');
  const [selGroups, setSelGroups] = useStateBsa(editRow ? ['cg1'] : []);
  const [groupCfg, setGroupCfg] = useStateBsa({}); // { [gid]: { mobileCol, varMap: { [var]: column } } }
  const [manual, setManual] = useStateBsa(() => (prefill && prefill.number) ? [{ ...bsaSplitNum(prefill.number), vars: {} }] : []); // [{ phoneCountry, phone, vars }] — max 3 manual recipients
  const [timing, setTiming] = useStateBsa(editRow && editRow.scheduledAt ? 'schedule' : 'immediate');
  const [schedDate, setSchedDate] = useStateBsa(null);
  const [schedTime, setSchedTime] = useStateBsa('09:00 AM');
  const [confirm, setConfirm] = useStateBsa(false);
  const [cancelConfirm, setCancelConfirm] = useStateBsa(false);
  // Voice retry logic (optional)
  const [retryOn, setRetryOn] = useStateBsa(false);
  const [retryStatuses, setRetryStatuses] = useStateBsa(['no_answer', 'busy']);
  const [retryAttempts, setRetryAttempts] = useStateBsa([{ wait: 5 }]);
  const [showPreview, setShowPreview] = useStateBsa(true); // collapse the Preview column to give Recipients more room
  const MAX_MANUAL = 3;
  const MAX_RETRY = 3;
  const toggleRetryStatus = (id) => setRetryStatuses((arr) => (arr.includes(id) ? arr.filter((x) => x !== id) : [...arr, id]));
  const addAttempt = () => setRetryAttempts((arr) => (arr.length >= MAX_RETRY ? arr : [...arr, { wait: 10 }]));
  const removeAttempt = (i) => setRetryAttempts((arr) => arr.filter((_, ix) => ix !== i));
  const setAttemptWait = (i, val) => setRetryAttempts((arr) => arr.map((a, ix) => (ix === i ? { ...a, wait: val } : a)));

  const cats = isVoice ? ['Dynamic', 'Static'] : ['Marketing', 'Utility', 'Authentication'];
  const langOpts = [...new Set(templates.filter((x) => x.type === cat).map((x) => x.language).filter(Boolean))];
  const tplReady = !!cat && (isVoice || !!lang);
  const tplOpts = tplReady ? templates.filter((x) => x.type === cat && (isVoice || x.language === lang)) : [];
  const tpl = templates.find((x) => x.id === tplId);
  const vars = (window.bsaTemplateVars || {})[tplId] || [];
  const body = (window.bsaWaTemplateBodies || {})[tplId];
  // Voice IVR preview = read-only flow canvas (shown full-width at the bottom; no phone mockup).
  const ivrSeeds = (window.seedTemplates || []).filter((x) => x.ivr && (x.ivr.nodes || []).length);
  const ivrPreview = isVoice ? (tpl ? (ivrSeeds.find((x) => x.ivr.type === String(tpl.type || '').toLowerCase()) || ivrSeeds[0]) : null) : null;

  // Simulated Meta template-status sync: if the picked template is no longer Approved, force a re-pick.
  const metaStatus = bsaTplMeta(tplId);
  const tplApproved = !tplId || metaStatus === 'Approved';

  // Keep per-group config (mobile column + variable→column map) seeded with sensible defaults.
  useEffectBsa(() => {
    setGroupCfg((prev) => {
      const next = {};
      selGroups.forEach((gid) => {
        const grp = groups.find((g) => g.id === gid); if (!grp) return;
        const cols = grp.columns || [];
        const cur = prev[gid] || {};
        const varMap = { ...(cur.varMap || {}) };
        // No auto-mapping — the user picks which columns map to Destination / each variable.
        next[gid] = { mobileCol: cur.mobileCol || '', varMap };
      });
      return next;
    });
  }, [selGroups, tplId]);

  const manualValid = manual.filter((m) => m.phone && String(m.phone).trim());
  // Every existing manual recipient must have its Destination + all template variables filled before another can be added.
  const manualComplete = manual.every((m) => m.phone && String(m.phone).trim() && vars.every((v) => (m.vars || {})[v] && String((m.vars || {})[v]).trim()));
  const recipientCount = selGroups.reduce((a, g) => a + ((groups.find((x) => x.id === g) || {}).count || 0), 0) + manualValid.length;
  const costPerMsg = isVoice ? 4 : 2.5;

  const toggleGroup = (id) => setSelGroups((s) => s.includes(id) ? s.filter((x) => x !== id) : [...s, id]);
  const addManual = () => { if (!tplId || !manualComplete) return; setManual((m) => (m.length >= MAX_MANUAL ? m : [...m, { phoneCountry: 'SA', phone: '', vars: {} }])); };
  const removeManual = (i) => setManual((m) => m.filter((_, idx) => idx !== i));
  const setManualField = (i, key, val) => setManual((m) => m.map((r, idx) => (idx === i ? { ...r, [key]: val } : r)));
  const setManualVar = (i, v, val) => setManual((m) => m.map((r, idx) => (idx === i ? { ...r, vars: { ...r.vars, [v]: val } } : r)));
  const setGroupMobile = (gid, col) => setGroupCfg((c) => ({ ...c, [gid]: { ...(c[gid] || {}), mobileCol: col } }));
  const setGroupVar = (gid, v, col) => setGroupCfg((c) => ({ ...c, [gid]: { ...(c[gid] || {}), varMap: { ...((c[gid] || {}).varMap || {}), [v]: col } } }));
  // Required: every selected contact group must map its Destination column + all template variables.
  const groupsReady = selGroups.every((gid) => { const cfg = groupCfg[gid] || {}; return !!cfg.mobileCol && vars.every((v) => !!(cfg.varMap || {})[v]); });
  // --- Column-based mapping: tick a column, then pick the template field it fills ---
  const [colChecked, setColChecked] = useStateBsa({}); // { [gid]: { [col]: bool } } — explicit checkbox overrides
  const [activePanel, setActivePanel] = useStateBsa(null); // { gid, kind:'map'|'prev' } | null — only ONE table shown at a time, driven by the selected chip
  const fieldOpts = [{ id: '__dest', label: t.bsaDestination || 'Destination' }, ...vars.map((v) => ({ id: v, label: `{{${v}}}` }))];
  const fieldOptsMap = [...fieldOpts, { id: '__none', label: t.bsaNotMapped || 'Not mapped', cls: 'bsa-ddl-none' }];
  const fieldOfCol = (cfg, col) => cfg.mobileCol === col ? '__dest' : (Object.keys(cfg.varMap || {}).find((v) => (cfg.varMap || {})[v] === col) || null);
  const colIsChecked = (gid, col) => { const ex = colChecked[gid] || {}; return (col in ex) ? ex[col] : (fieldOfCol(groupCfg[gid] || {}, col) != null); };
  const assignColField = (gid, col, field) => setGroupCfg((c) => {
    const cur = c[gid] || {}; let mobileCol = cur.mobileCol || ''; const varMap = { ...(cur.varMap || {}) };
    if (mobileCol === col) mobileCol = '';
    Object.keys(varMap).forEach((v) => { if (varMap[v] === col) varMap[v] = ''; });
    if (field === '__dest') mobileCol = col; else if (field) varMap[field] = col; // assigning a taken var moves it here
    return { ...c, [gid]: { ...cur, mobileCol, varMap } };
  });
  const setColCheck = (gid, col, checked) => { setColChecked((cc) => ({ ...cc, [gid]: { ...(cc[gid] || {}), [col]: checked } })); if (!checked) assignColField(gid, col, ''); };
  const isMapOpen = (gid) => !!activePanel && activePanel.gid === gid && activePanel.kind === 'map';
  const toggleMap = (gid) => setActivePanel((p) => (p && p.gid === gid && p.kind === 'map') ? null : { gid, kind: 'map' });
  const isPrevOpen = (gid) => !!activePanel && activePanel.gid === gid && activePanel.kind === 'prev';
  const togglePrev = (gid) => setActivePanel((p) => (p && p.gid === gid && p.kind === 'prev') ? null : { gid, kind: 'prev' });
  // Keep exactly one table selected: preserve the current pick while it stays selected, else focus the newest group's table.
  useEffectBsa(() => {
    setActivePanel((p) => {
      if (p && selGroups.includes(p.gid)) return p;
      const last = selGroups[selGroups.length - 1];
      return last ? { gid: last, kind: 'map' } : null;
    });
  }, [selGroups]);
  const canSend = !!sender && !!tplId && tplApproved && (selGroups.length > 0 || manualValid.length > 0) && groupsReady;

  // Live preview values — first recipient: first group's first sheet row (via the column map), else first manual recipient.
  const previewVals = (() => {
    const o = {};
    if (!vars.length) return o;
    if (selGroups.length) {
      const gid = selGroups[0]; const grp = groups.find((g) => g.id === gid); const cfg = groupCfg[gid] || {};
      const row0 = grp ? bsaGroupRows(grp)[0] : {};
      vars.forEach((v) => { const col = (cfg.varMap || {})[v]; o[v] = (col && row0[col]) || bsaSampleVal(v); });
      return o;
    }
    const m0 = manualValid[0]; if (m0) vars.forEach((v) => { o[v] = (m0.vars || {})[v] || `{{${v}}}`; });
    return o;
  })();

  const doSend = ({ allowDup }) => {
    setConfirm(false);
    onSent({
      sender, templateId: tplId, recipientsCount: recipientCount,
      totalCost: Math.round(recipientCount * costPerMsg), groups: selGroups, manual,
      scheduled: timing === 'schedule',
      retry: isVoice && retryOn ? { statuses: retryStatuses, attempts: retryAttempts } : null,
    });
  };

  const selTpl = tplOpts.find((x) => x.id === tplId);
  const timeControl = (
    <React.Fragment>
      <div className="bsa-seg">
        <button className={timing === 'immediate' ? 'on' : ''} onClick={() => setTiming('immediate')}>{t.bsaImmediate || 'Immediate'}</button>
        <button className={timing === 'schedule' ? 'on' : ''} onClick={() => setTiming('schedule')}>{t.bsaSchedule || 'Schedule'}</button>
      </div>
      {timing === 'schedule' && (
        <div className="bsa-sched-field">
          <span>{t.bsaScheduleAt || 'Send on'}</span>
          <div className="bsa-sched-row">
            {window.DatePicker && <window.DatePicker value={schedDate} onChange={setSchedDate} />}
            <span className="bsa-sched-div" />
            <BsaTimePicker value={schedTime} onChange={setSchedTime} />
          </div>
        </div>
      )}
    </React.Fragment>
  );
  // WhatsApp delivery: Falcon-themed Date & Time picker (native datetime-local popup can't be themed). A date ⇒ scheduled; empty ⇒ send immediately.
  const deliveryControl = (
    <React.Fragment>
      <div className="bsa-seg bsa-delivery-seg">
        <button type="button" className={timing === 'immediate' ? 'on' : ''} onClick={() => setTiming('immediate')}>{t.bsaImmediate || 'Immediate'}</button>
        <button type="button" className={timing === 'schedule' ? 'on' : ''} onClick={() => setTiming('schedule')}>{t.bsaSchedule || 'Schedule'}</button>
      </div>
      {timing === 'schedule' && (
        <div className="bsa-sched-field bsa-delivery-pick">
          <span>{t.bsaScheduleAt || 'Send on'}</span>
          <BsaDateTimePicker date={schedDate} time={schedTime} onDate={setSchedDate} onTime={setSchedTime} t={t} preferUp />
        </div>
      )}
    </React.Fragment>
  );
  // Retry Logic (voice only) — rendered inside Message Details, under Delivery.
  const retryControl = (
    <React.Fragment>
      <div className="bsa-retry-head">
        <span className="bsa-retry-title">{t.bsaRetryLogic || 'Retry Logic'} <span className="bsa-optional-tag">{t.bsaOptional || 'Optional'}</span></span>
        <label className="bsa-switch">
          <input type="checkbox" checked={retryOn} onChange={() => setRetryOn((v) => !v)} />
          <span className="bsa-switch-track"><span className="bsa-switch-thumb" /></span>
        </label>
      </div>
      {retryOn ? (
        <div className="bsa-retry-body">
          <div className="bsa-retry-sub">
            <div className="bsa-rec-sub-h">{t.bsaRetryWhen || 'Retry when the call result is'}</div>
            <div className="bsa-retry-chips">
              {BSA_RETRY_STATUSES.map((s) => {
                const on = retryStatuses.includes(s.id);
                return <button key={s.id} type="button" className={`bsa-retry-chip ${on ? 'on' : ''}`} onClick={() => toggleRetryStatus(s.id)}><span className="bsa-retry-box">{on ? <BsaCheck /> : null}</span> {s.label}</button>;
              })}
            </div>
          </div>
          <div className="bsa-retry-sub">
            <div className="bsa-rec-sub-h">{t.bsaRetryAttempts || 'Retry attempts'} <span className="bsa-rec-sub-cap">{retryAttempts.length}/{MAX_RETRY}</span></div>
            <div className="bsa-retry-list">
              {retryAttempts.map((a, i) => (
                <div key={i} className="bsa-retry-row">
                  <span className="bsa-retry-num">{i + 1}</span>
                  <span className="bsa-retry-lbl">{t.bsaWaitWord || 'Wait'}</span>
                  <input type="number" min="1" max="1440" className="bsa-retry-min" value={a.wait} onChange={(e) => setAttemptWait(i, e.target.value)} />
                  <span className="bsa-retry-unit">{t.bsaMinsBeforeRetry || 'minutes before the next attempt'}</span>
                  {retryAttempts.length > 1 ? <button type="button" className="bsa-retry-rm" onClick={() => removeAttempt(i)} aria-label={t.remove || 'Remove'}>×</button> : null}
                </div>
              ))}
            </div>
            <button type="button" className="bsa-add-recip" disabled={retryAttempts.length >= MAX_RETRY} onClick={addAttempt}>＋ {t.bsaAddAttempt || 'Add attempt'}{retryAttempts.length >= MAX_RETRY ? ` · ${t.bsaMaxReached || 'max 3'}` : ''}</button>
          </div>
        </div>
      ) : (
        <div className="bsa-retry-off">{t.bsaRetryOff || 'Turn on to automatically re-attempt unconnected calls (No Answer, Busy, Cancel, Failed).'}</div>
      )}
    </React.Fragment>
  );
  return (
    <div className="bsa-takeover bsa-compose">
      <div className="bsa-tk-top">
        <h2 className="bsa-tk-title">{isVoice ? (t.bsaSendVoice || 'Send Voice IVR Message') : (t.bsaSendWa || 'Send Whatsapp Message')}</h2>
        <div className="bsa-tk-actions">
          <button className="btn btn-secondary" onClick={() => fromConversation ? onCancel() : setCancelConfirm(true)}>{t.cancel || 'Cancel'}</button>
          {fromConversation
            ? <button className="btn btn-primary" disabled={!tplId} onClick={() => onBackToConversation && onBackToConversation({ templateId: tplId, vars: (manual[0] && manual[0].vars) || {}, number: (manual[0] && manual[0].phone) || (prefill && prefill.number) })}>{t.bsaBackToConversation || 'Send & back to conversation'}</button>
            : <button className="btn btn-primary" disabled={!canSend} onClick={() => setConfirm(true)}>{t.bsaSend || 'Send'}</button>}
        </div>
      </div>

      <div className="bsa-compose-2col bsa-compose-wa">
        <div className={`bsa-compose-main bsa-wa-cols ${showPreview ? '' : 'no-preview'}`}>
          {/* 1 — Message Details */}
          <div className="bsa-card bsa-step-card">
            <BsaStepHead n="1" title={t.bsaMsgStepTitle || 'Message Details'} sub={t.bsaMsgStepSub || 'Set up your message content and template.'} />
            <div className="bsa-step-scroll">
            <div className="bsa-tpl-grid">
              <label className="bsa-field"><span>{t.bsaSenderId || 'Sender ID'}</span><BsaSelect value={sender} placeholder={t.bsaSelect || 'Select'} options={senders.map((s) => ({ id: s, label: s }))} onChange={setSender} /></label>
              <label className="bsa-field"><span>{t.bsaCategory || 'Category'}</span><BsaSelect value={cat} placeholder={t.bsaSelect || 'Select'} options={cats.map((c) => ({ id: c, label: c }))} onChange={(v) => { setCat(v); setLang(''); setTplId(''); }} /></label>
              {!isVoice && <label className="bsa-field"><span>{t.bsaLanguage || 'Language'}</span><BsaSelect value={lang} disabled={!cat} placeholder={t.bsaSelect || 'Select'} options={langOpts.map((l) => ({ id: l, label: l }))} onChange={(v) => { setLang(v); setTplId(''); }} /></label>}
              <label className="bsa-field"><span>{t.bsaTemplateName || 'Template Name'}</span><BsaSelect value={tplId} disabled={!tplReady} placeholder={t.bsaChooseTemplate || 'Choose Template'} options={tplOpts.map((x) => ({ id: x.id, label: x.name }))} onChange={setTplId} /></label>
            </div>
            <div className="bsa-bc-hint">{t.bsaTemplateHint || 'Choose one of your pre-created, approved templates.'} <button className="bsa-link" onClick={() => pushToast(t.bsaCreateTpl || 'Create template')}>{t.bsaCreateTemplate || 'Create Template'}</button></div>
            {tplId && vars.length > 0 && (
              <div className="bsa-tpl-vars"><span className="bsa-tpl-vars-label">{t.bsaVariables || 'Variables'}</span>{vars.map((v) => <code key={v} className="bsa-tpl-var-chip">{`{{${v}}}`}</code>)}</div>
            )}
            {tplId && !tplApproved && (
              <div className="bsa-meta-sync warn"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /><line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" /></svg> {(t.bsaMetaChanged || 'This template is')} {metaStatus} {(t.bsaMetaChanged2 || 'on Meta — please select another template.')}</div>
            )}
            <div className="bsa-preview-delivery bsa-msg-delivery">
              <div className="bsa-sum-lbl"><span className="bsa-preview-delivery-ic">{BSA_DICONS.clock}</span> {t.bsaDelivery || 'Delivery'}</div>
              {deliveryControl}
            </div>
            {isVoice && <div className="bsa-preview-delivery bsa-msg-retry">{retryControl}</div>}
            </div>
          </div>

          {/* 2 — Recipients */}
          <div className="bsa-card bsa-step-card bsa-step-recip">
            <BsaStepHead n="2" title={t.bsaRecipientsHead || 'Recipients'} sub={t.bsaRecipStepSub || 'Choose your audience.'} />
            <div className="bsa-step-scroll">
            {!tplId && <div className="bsa-rec-locked"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></svg> {t.bsaPickTplForRecip || 'Choose a template name first to add contact groups or recipients.'}</div>}

            {!fromConversation && (
            <div className="bsa-rec-sub">
              <div className="bsa-sub-head">
                <div className="bsa-rec-sub-h">{t.bsaContactGroupsHead || 'Contact Groups'}</div>
                <BsaGroupPicker groups={groups} selected={selGroups} disabled={!tplId || !groupsReady} onApply={(next) => { setSelGroups(next); const added = next[next.length - 1]; if (added) setActivePanel({ gid: added, kind: 'map' }); }} t={t} />
              </div>
              <div className="bsa-groups-bar">
                {selGroups.map((gid) => {
                  const grp = groups.find((g) => g.id === gid) || {};
                  return (
                    <span key={gid} className={`bsa-group-chip ${isMapOpen(gid) ? 'is-mapping' : ''}`}>
                      <span className="bsa-group-chip-name" role="button" tabIndex={0} title={t.bsaToggleMap || 'Show / hide the variable table'} aria-pressed={isMapOpen(gid)} onClick={() => toggleMap(gid)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleMap(gid); } }}>{grp.name} <em>({(grp.count || 0).toLocaleString()})</em></span>
                      <button type="button" className="bsa-group-chip-x" title={t.remove || 'Remove'} onClick={() => toggleGroup(gid)}>×</button>
                    </span>
                  );
                })}
                {selGroups.length === 0 && <span className="bsa-groups-empty">{t.bsaNoGroupsYet || 'No groups selected yet'}</span>}
              </div>
              {selGroups.map((gid) => {
                if (!isMapOpen(gid)) return null;
                const grp = groups.find((g) => g.id === gid) || {}; const cols = grp.columns || []; const cfg = groupCfg[gid] || {}; const rows = bsaGroupRows(grp);
                const need = 1 + vars.length; const done = (cfg.mobileCol ? 1 : 0) + vars.filter((v) => (cfg.varMap || {})[v]).length;
                return (
                  <div key={gid} className="bsa-map-card">
                    <div className="bsa-map-card-h">
                      <span className="bsa-gb-name"><IcBuildingS size={15} /> {grp.name} <em>{(grp.count || 0).toLocaleString()} {t.bsaContactsWord || 'contacts'}</em></span>
                      <span className={`bsa-map-progress ${done >= need ? 'is-done' : ''}`}>{done}/{need} {t.bsaMapped || 'mapped'}</span>
                    </div>
                    <div className="bsa-map-guide">{t.bsaMapGuide || 'Use the dropdown above each column to link it to a template field.'}</div>
                    <div className="bsa-map-fieldchips">
                      <span className="bsa-map-fieldchips-lbl">{t.bsaFieldsToMap || 'Fields to map'}:</span>
                      <span className={`bsa-map-fieldchip ${cfg.mobileCol ? 'done' : ''}`}>{cfg.mobileCol ? <BsaCheck /> : <i className="bsa-map-dot" />} {t.bsaDestination || 'Destination'}</span>
                      {vars.map((v) => <span key={v} className={`bsa-map-fieldchip ${(cfg.varMap || {})[v] ? 'done' : ''}`}>{(cfg.varMap || {})[v] ? <BsaCheck /> : <i className="bsa-map-dot" />} {`{{${v}}}`}</span>)}
                    </div>
                    <div className="bsa-mapx-wrap">
                      <table className="bsa-xl-table bsa-mapx-table">
                        <thead>
                          <tr className="bsa-mapx-maprow">{cols.map((c) => {
                            const field = fieldOfCol(cfg, c);
                            const invalid = !field && done < need; // required-mapping not complete → flag unmapped columns
                            return (
                              <th key={c} className={field === '__dest' ? 'is-dest' : (field ? 'is-var' : '')}>
                                <span className={`bsa-mapx-assign ${invalid ? 'is-invalid' : ''}`}><BsaSelect value={field || ''} placeholder={t.bsaMapTo || 'Map to…'} options={fieldOptsMap} onChange={(f) => assignColField(gid, c, f === '__none' ? '' : f)} /></span>
                              </th>
                            );
                          })}</tr>
                          <tr className="bsa-mapx-namerow">{cols.map((c) => { const field = fieldOfCol(cfg, c); return <th key={c} className={field === '__dest' ? 'is-dest' : (field ? 'is-var' : '')}>{c}</th>; })}</tr>
                        </thead>
                        <tbody>{rows.map((r, ri) => <tr key={ri}>{cols.map((c) => { const f = fieldOfCol(cfg, c); return <td key={c} className={f === '__dest' ? 'is-dest' : (f ? 'is-var' : '')}>{r[c]}</td>; })}</tr>)}</tbody>
                      </table>
                    </div>
                  </div>
                );
              })}
              {selGroups.map((gid) => {
                if (!isPrevOpen(gid)) return null;
                const grp = groups.find((g) => g.id === gid) || {}; const cols = grp.columns || []; const rows = bsaGroupRows(grp);
                return (
                  <div key={gid} className="bsa-map-card bsa-prev-card">
                    <div className="bsa-map-card-h">
                      <span className="bsa-gb-name"><IcBuildingS size={15} /> {grp.name} <em>{(grp.count || 0).toLocaleString()} {t.bsaContactsWord || 'contacts'}</em></span>
                      <span className="bsa-prev-tag">{t.bsaPreview || 'Preview'}</span>
                    </div>
                    <div className="bsa-mapx-wrap">
                      <table className="bsa-xl-table bsa-mapx-table">
                        <thead><tr>{cols.map((c) => <th key={c}><div className="bsa-mapx-head"><span className="bsa-mapx-col">{c}</span></div></th>)}</tr></thead>
                        <tbody>{rows.map((r, ri) => <tr key={ri}>{cols.map((c) => <td key={c}>{r[c]}</td>)}</tr>)}</tbody>
                      </table>
                    </div>
                  </div>
                );
              })}
            </div>
            )}

            <div className="bsa-rec-sub bsa-mr">
              <div className="bsa-mr-head">
                <div>
                  <div className="bsa-rec-sub-h">{fromConversation ? (t.bsaRecipient || 'Recipient') : (t.bsaManualHead || 'Manual Recipients')}</div>
                </div>
                <div className="bsa-mr-head-actions">
                  {!fromConversation && <span className="bsa-mr-count">{manual.length} {t.bsaRecipientsWord || 'Recipients'}</span>}
                  {!fromConversation && <button type="button" className="bsa-add-recip" disabled={!tplId || manual.length >= MAX_MANUAL || !manualComplete} title={!manualComplete ? (t.bsaFillRecipFirst || 'Fill the current recipient’s destination and variables first') : undefined} onClick={addManual}>＋ {t.bsaAddRecipient || 'Add Recipient'}{manual.length >= MAX_MANUAL ? ` · ${t.bsaMaxReached || 'max 3'}` : ''}</button>}
                </div>
              </div>
              {manual.length > 0 && (
                <div className="bsa-mr-tablewrap">
                  <table className="bsa-mr-table">
                    <thead>
                      <tr>
                        <th>{t.bsaDestinationCol || 'Destination'}</th>
                        {tplId && vars.map((v) => <th key={v} className="bsa-mr-varcol">{`{{${v}}}`}</th>)}
                        <th className="bsa-mr-rmcol" aria-hidden="true" />
                      </tr>
                    </thead>
                    <tbody>
                      {manual.map((r, i) => (
                        <tr key={i}>
                          <td><input className="bsa-mr-input" value={r.phone || ''} placeholder={t.bsaDestinationPh || 'Phone, email, or username'} onChange={(e) => setManualField(i, 'phone', e.target.value)} /></td>
                          {tplId && vars.map((v) => (
                            <td key={v}><input className="bsa-mr-input" value={(r.vars || {})[v] || ''} placeholder={bsaPretty(v)} onChange={(e) => setManualVar(i, v, e.target.value)} /></td>
                          ))}
                          <td className="bsa-mr-rmcol"><button type="button" className="bsa-mr-rm" title={t.remove || 'Remove'} aria-label={t.remove || 'Remove'} onClick={() => removeManual(i)}><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg></button></td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              )}
            </div>
            </div>
          </div>

          {/* 3 — Preview (phone for WhatsApp, IVR flow canvas for Voice) */}
          {showPreview && (
          <div className="bsa-card bsa-step-card bsa-preview-card">
            <BsaStepHead n="3" title={t.bsaPreview || 'Preview'} sub={t.bsaPrevStepSub || 'See how your message will appear.'} onToggle={() => setShowPreview(false)} collapsed={false} toggleHint={t.bsaHidePreview || 'Hide preview'} />
            <div className="bsa-step-scroll">
            {isVoice
              ? (ivrPreview && window.TplIvrStep2
                  ? <div className="ivr-edit-canvas is-readonly td-ivr-flow bsa-ivr-flow"><window.TplIvrStep2 key={ivrPreview.id} data={ivrPreview} setData={() => {}} t={t} readOnly={true} inspect={true} /></div>
                  : <div className="bsa-var-empty">{t.bsaPickIvrTpl || 'Select a template to preview its IVR flow.'}</div>)
              : <BsaPhonePreview body={body} vars={previewVals} empty={!body} />}
            </div>
          </div>
          )}
          {!showPreview && (
          <button type="button" className="bsa-step-collapsed" onClick={() => setShowPreview(true)} aria-label={t.bsaShowPreview || 'Show preview'} aria-expanded="false" title={t.bsaShowPreview || 'Show preview'}>
            <span className="bsa-step-num">3</span>
            <span className="bsa-step-collapsed-eye"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z" /><circle cx="12" cy="12" r="3" /></svg></span>
          </button>
          )}
        </div>

        <div className="bsa-compose-summary">
          <div className="bsa-sum-tile">
            <span className="bsa-sum-ic">{BSA_DICONS.type}</span>
            <span className="bsa-sum-text"><span className="bsa-sum-lbl">{t.bsaMsgSummary || 'Message Summary'}</span><span className="bsa-sum-val">{t.bsaTemplateName || 'Template'}: <b>{tpl ? tpl.name : '—'}</b>{!isVoice ? <React.Fragment> · {t.bsaLanguage || 'Language'}: <b>{lang || '—'}</b></React.Fragment> : null} · {t.bsaCategory || 'Category'}: <b>{cat || '—'}</b></span></span>
          </div>
          <div className="bsa-sum-tile">
            <span className="bsa-sum-ic">{BSA_DICONS.recipients}</span>
            <span className="bsa-sum-text"><span className="bsa-sum-lbl">{t.bsaEstRecipients || 'Estimated Recipients'}</span><span className="bsa-sum-val"><b>{recipientCount.toLocaleString()}</b> {t.bsaRecipientsSel || 'recipients selected'} {selGroups.length ? <span className="bsa-sum-badge">{t.bsaFromWord || 'From'} {selGroups.length} {selGroups.length === 1 ? (t.bsaGroupWord || 'group') : (t.bsaGroupsWord || 'groups')}</span> : null}</span></span>
          </div>
          <div className="bsa-sum-tile">
            <span className="bsa-sum-ic">{BSA_DICONS.clock}</span>
            <span className="bsa-sum-text"><span className="bsa-sum-lbl">{t.bsaDateTime || 'Date & Time'}</span><span className="bsa-sum-val">{timing === 'schedule' ? <React.Fragment><b>{bsaFmtDate(schedDate)}</b> · {schedTime || '09:00 AM'}</React.Fragment> : <b>{t.bsaImmediate || 'Immediate'}</b>}</span></span>
          </div>
        </div>
      </div>

      {confirm && <BsaSendConfirm recipients={recipientCount} costPerMsg={costPerMsg} t={t} onCancel={() => setConfirm(false)} onConfirm={doSend} />}
      {cancelConfirm && <BsaConfirm title={t.bsaCancelMsgTitle || 'Are you sure you want to cancel this message?'} body={t.bsaCancelMsgBody || "You haven't sent this message yet. If you cancel now, everything you've entered will be lost."} confirmLabel={t.bsaCancelMsgConfirm || 'Yes, cancel'} cancelLabel={t.bsaKeepEditing || 'Keep editing'} danger onCancel={() => setCancelConfirm(false)} onConfirm={onCancel} />}
    </div>
  );
};

// ===== Outbox Detailed View (statistics) =====
const BsaStatBar = ({ label, pct, tone }) => (
  <div className="bsa-statbar">
    <div className="bsa-statbar-track"><div className={`bsa-statbar-fill ${tone}`} style={{ height: pct + '%' }}><span className="bsa-statbar-pct">%{pct}</span></div></div>
    <div className="bsa-statbar-label">{label}</div>
  </div>
);

// Clean line icons for the redesigned Details header / KPI tiles / cost cards.
const BSA_DICONS = {
  sender: (<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.81.36 1.6.7 2.34a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.7a2 2 0 0 1 2.11-.45c.74.34 1.53.57 2.34.7A2 2 0 0 1 22 16.92z" /></svg>),
  recipients: (<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>),
  group: (<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" /></svg>),
  type: (<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M20.59 13.41 13.42 20.6a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" /><line x1="7" y1="7" x2="7.01" y2="7" /></svg>),
  read: (<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7z" /><circle cx="12" cy="12" r="3" /></svg>),
  reply: (<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 17 4 12 9 7" /><path d="M20 18v-2a4 4 0 0 0-4-4H4" /></svg>),
  cal: (<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" /><line x1="16" y1="2" x2="16" y2="6" /><line x1="8" y1="2" x2="8" y2="6" /><line x1="3" y1="10" x2="21" y2="10" /></svg>),
  tagSm: (<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><path d="M20.59 13.41 13.42 20.6a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" /><line x1="7" y1="7" x2="7.01" y2="7" /></svg>),
  coins: (<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><circle cx="8" cy="8" r="6" /><path d="M18.09 10.37A6 6 0 1 1 10.34 18" /><path d="M7 6h1v4" /><path d="m16.71 13.88.7.71-2.82 2.82" /></svg>),
  receipt: (<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1z" /><line x1="8" y1="7" x2="16" y2="7" /><line x1="8" y1="11" x2="16" y2="11" /></svg>),
  priority: (<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><line x1="6" y1="20" x2="6" y2="13" /><line x1="12" y1="20" x2="12" y2="8" /><line x1="18" y1="20" x2="18" y2="4" /></svg>),
  play: (<svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M7 4.5v15l13-7.5z" /></svg>),
  clock: (<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9" /><polyline points="12 7 12 12 16 14" /></svg>),
};
const BsaSparkle = ({ size }) => <svg width={size || 16} height={size || 16} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.5l1.7 4.6 4.6 1.7-4.6 1.7L12 15.1l-1.7-4.6L5.7 8.8l4.6-1.7L12 2.5z" /><path d="M18.5 13.5l.9 2.4 2.4.9-2.4.9-.9 2.4-.9-2.4-2.4-.9 2.4-.9.9-2.4z" /></svg>;

// ===== Ask AI assistant (prototype) — answers questions about a transaction's results =====
const BsaAskAI = ({ txn, summary, suggestions, t, onClose }) => {
  const [msgs, setMsgs] = useStateBsa(() => [{ role: 'ai', text: summary }]);
  const [draft, setDraft] = useStateBsa('');
  const bodyRef = useRefBsa(null);
  useEffectBsa(() => { const b = bodyRef.current; if (b) b.scrollTop = b.scrollHeight; }, [msgs.length]);
  useEffectBsa(() => { const f = (e) => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', f); return () => document.removeEventListener('keydown', f); }, []);
  const answer = (q) => {
    const l = q.toLowerCase();
    if (/schedul|not sent|pending/.test(l) && bsaIsScheduled(txn)) return 'This transaction is scheduled and hasn’t been sent yet — there are no results to analyze. I’ll have insights ready once it’s processed.';
    if (/fail/.test(l)) return 'Most failures here are technical — unreachable numbers or provider errors. Turning on retry logic for “Failed” and “No Answer” with a short wait typically recovers a meaningful share of them.';
    if (/read|answer|engage|improve|better/.test(l)) return 'Engagement tracks with send time and message clarity. Sending during working hours and keeping the first line short tends to lift the rate; an A/B test on the call-to-action is a good next step.';
    if (/pending/.test(l)) return 'Pending means the message is queued in Falcon and not yet submitted to the provider. These usually clear within a few minutes; if they persist, check the channel balance.';
    if (/cost|spend|cheap|expensive|reduce/.test(l)) return 'Cost is concentrated on the first attempt. Targeting verified numbers and trimming unnecessary retries keeps the cost-per-delivered down.';
    return summary;
  };
  const send = (q) => { const text = (q || draft).trim(); if (!text) return; setMsgs((m) => [...m, { role: 'user', text }, { role: 'ai', text: answer(text) }]); setDraft(''); };
  return (
    <React.Fragment>
      <div className="drawer-overlay bsa-ai-scrim" onMouseDown={onClose}></div>
      <aside className="drawer bsa-ai-drawer" role="dialog" aria-modal="true">
        <div className="bsa-modal-head bsa-ai-head">
          <div className="bsa-modal-titlewrap"><div className="bsa-modal-title bsa-ai-title"><span className="bsa-ai-spark"><BsaSparkle size={17} /></span> {t.bsaAskAI || 'Ask AI'}</div><div className="bsa-modal-sub">{txn.templateName} · {txn.id}</div></div>
          <button className="bsa-modal-close" onClick={onClose} aria-label={t.close || 'Close'}><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg></button>
        </div>
        <div className="bsa-ai-body" ref={bodyRef}>
          {msgs.map((m, i) => (
            <div key={i} className={`bsa-ai-msg ${m.role}`}>
              <div className="bsa-ai-bubble">
                {m.role === 'ai' ? <div className="bsa-ai-name"><span className="bsa-ai-ava"><BsaSparkle size={13} /></span> {t.bsaFalconAi || 'Falcon AI'}</div> : null}
                {m.text}
              </div>
            </div>
          ))}
        </div>
        <div className="bsa-ai-suggest">
          <div className="bsa-ai-suggest-h">{t.bsaSuggestedQ || 'Suggested questions'}</div>
          {suggestions.map((s) => <button key={s} type="button" className="bsa-ai-chip" onClick={() => send(s)}>{s}</button>)}
        </div>
        <div className="bsa-ai-input">
          <input value={draft} onChange={(e) => setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') send(); }} placeholder={t.bsaAskPlaceholder || 'Ask anything about this transaction…'} />
          <button type="button" className="bsa-ai-send" onClick={() => send()} aria-label={t.bsaSendMsg || 'Send'}><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><line x1="22" y1="2" x2="11" y2="13" /><polygon points="22 2 15 22 11 13 2 9 22 2" /></svg></button>
        </div>
      </aside>
    </React.Fragment>
  );
};

const BsaDetails = ({ txn, channel, t, onBack, onConversation, pushToast }) => {
  const st = bsaStatsFor(txn);
  const [recips] = useStateBsa(() => bsaRecipientsFor(txn));
  const [chartIn, setChartIn] = useStateBsa(false); // drives the funnel bars rising from 0 on mount
  useEffectBsa(() => { const id = setTimeout(() => setChartIn(true), 40); return () => clearTimeout(id); }, []);
  const [openMenuId, setOpenMenuId] = useStateBsa(null);
  const [sel, setSel] = useStateBsa(0);
  const [recPage, setRecPage] = useStateBsa(1);
  const [recPageSize, setRecPageSize] = useStateBsa(10);
  const [askAI, setAskAI] = useStateBsa(false);
  // The global header "Ask AI" button opens THIS transaction's side pop-up while a details page is open.
  useEffectBsa(() => {
    if (!window.falconAskAI) return;
    window.falconAskAI.handler = () => { if (txn.status !== 'scheduled' && txn.status !== 'deleted') setAskAI(true); };
    return () => { if (window.falconAskAI) window.falconAskAI.handler = null; };
  }, []);
  const scheduled = bsaIsScheduled(txn);
  const sv = bsaStatusView(txn);
  const headRecipients = bsaHeadRecipients(txn), headCost = bsaHeadCost(txn);
  const plannedTotal = txn.targetCount || txn.plannedCount || 0;
  const showProgressOf = (txn.status === 'in_progress' || txn.status === 'partial' || txn.status === 'canceled') && plannedTotal > headRecipients;
  // For runs that didn't reach everyone (in progress / partial / canceled) the breakdown bars fill to
  // how much of the PLANNED total was actually processed — so they never read a misleading 100%.
  const costDenomN = txn.targetCount || txn.plannedCount || 0; // the planned total this run was meant to reach
  const costProg = (costDenomN && (txn.status === 'in_progress' || txn.status === 'partial' || txn.status === 'canceled')) ? Math.min(1, (txn.recipientsCount || 0) / costDenomN) : 1;
  const noData = !sv.stats; // scheduled / deleted / failed → render the stats sections but with no values (placeholders)
  // WhatsApp Overview → engagement rates (delivered / read / played / seen / failed / reply).
  const bsaPctCount = (pct) => Math.round((pct / 100) * st.total);
  const rateBars = [
    { k: 's1', label: t.bsaDeliveredRate || 'Delivered', pct: st.deliveredRate, count: st.delivered, tip: t.bsaDeliveredRateTip || 'Percentage of messages successfully delivered to recipients.' },
    { k: 's2', label: t.bsaReadRate || 'Read', pct: st.readRatePct, count: st.read, tip: t.bsaReadRateTip || 'Percentage of delivered messages that have been read by recipients.' },
    { k: 's3', label: t.bsaPlayedRate || 'Played', pct: st.playedRate, count: bsaPctCount(st.playedRate), tip: t.bsaPlayedRateTip || 'Percentage of voice notes that have been played by recipients.' },
    { k: 's4', label: t.bsaSeenRate || 'Seen', pct: st.seenRate, count: bsaPctCount(st.seenRate), tip: t.bsaSeenRateTip || 'Percentage of media messages that have been seen by recipients.' },
    { k: 's5', label: t.bsaFailedRate || 'Failed', pct: st.failedRate, count: bsaPctCount(st.failedRate), tip: t.bsaFailedRateTip || 'Percentage of messages that failed to send or deliver.' },
    { k: 's6', label: t.bsaReplyRate || 'Reply', pct: st.replyRate, count: st.replies, tip: t.bsaReplyRateTip || 'Percentage of recipients who replied to the message.' },
  ];
  const aiSummary = !sv.stats
    ? (txn.status === 'failed'
      ? `“${txn.templateName}” failed before processing — the system aborted due to insufficient balance, so no records were sent and nothing was charged. The figures shown are an estimate for ${(txn.plannedCount || 0).toLocaleString()} planned recipients.`
      : `“${txn.templateName}” is scheduled for ${txn.scheduledAt || 'a later time'} and hasn’t been sent yet, so there are no results to analyze. Ask me to estimate cost or check the setup.`)
    : `Here’s a quick read on “${txn.templateName}”: of ${st.total.toLocaleString()} recipients, ${st.deliveredRate}% were delivered and ${st.readRatePct}% read it, with a ${st.replyRate}% reply rate. Ask me anything about these results.`;
  const body = (window.bsaWaTemplateBodies || {})[txn.templateId];
  const failReason = txn.failReason;
  // Preview the SELECTED recipient's own message (variables filled with that recipient's values).
  const dvars = (window.bsaTemplateVars || {})[txn.templateId] || [];
  const selRecip = recips[sel] || recips[0];
  const selVals = {}; dvars.forEach((v) => { selVals[v] = bsaRecipVal(v, sel); });

  return (
    <div className="bsa-takeover bsa-details">
      <div className="bsa-det-top">
        <div className="bsa-det-head">
          <button className="bsa-back-ic" onClick={onBack} aria-label={t.back || 'Back'}><IcArrowLeft size={18} stroke={2} /></button>
          <div className="bsa-det-titlewrap">
            <div className="bsa-det-titlerow">
              <h2 className="bsa-det-title">{txn.templateName}</h2>
              <BsaStatusPill status={txn.status} t={t} />
            </div>
            <div className="bsa-det-sub">
              <span className="bsa-det-subitem">{BSA_DICONS.cal} {txn.createdAt}</span>
              <span className="bsa-det-subdot" />
              <span className="bsa-det-subitem">{BSA_DICONS.tagSm} {txn.type}</span>
              <span className="bsa-det-subdot" />
              <span className="bsa-det-subitem bsa-det-id">{txn.id}</span>
            </div>
          </div>
        </div>
        <div className="bsa-det-exports">
          <button className="btn btn-secondary" onClick={() => pushToast(t.bsaExportedDetails || 'Exporting transaction details ✓')}><IcDownload size={14} stroke={1.8} /> {t.bsaExportDetails || 'Export Details'}</button>
          <button className="btn btn-primary" onClick={() => pushToast(t.bsaExportedStats || 'Exporting statistics ✓')}><IcDownload size={14} stroke={1.8} /> {t.bsaExportStats || 'Export Statistics'}</button>
        </div>
      </div>

      {failReason && <div className={`bsa-fail-banner st-${txn.status}`}><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /><line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" /></svg> {failReason}</div>}
      {txn.status === 'deleted' && <div className="bsa-del-banner"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polyline points="3 6 5 6 21 6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg> <span><strong>{t.bsaDelBannerH || 'Deleted'}</strong> — {t.bsaDelBanner || 'this scheduled transaction was deleted and will not be sent. No statistics or costs will be generated.'}</span></div>}
      {txn.status === 'scheduled' && <div className="bsa-sched-banner"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9" /><polyline points="12 7 12 12 15 14" /></svg> <span><strong>{t.bsaSchedBannerH || 'Scheduled'}</strong> — {(t.bsaSchedBanner || 'this transaction is set for')} {txn.scheduledAt || '—'}. {t.bsaSchedBanner2 || 'It hasn’t been sent yet, so delivery statistics, rates, and costs will appear here once it’s processed.'}</span></div>}
      {sv.processing && (<div className="bsa-progress-banner"><span className="bsa-progress-txt"><span className="bsa-progress-dot" /><strong>{t.bsaProcessingH || 'In Progress'}</strong> — {headRecipients.toLocaleString()} {t.bsaProcOf || 'of'} {plannedTotal.toLocaleString()} {t.bsaProcRecipients || 'recipients processed so far. The recipient count and cost update live as the transaction runs.'}</span><span className="bsa-progress-bar"><i style={{ width: (plannedTotal ? Math.round(headRecipients / plannedTotal * 100) : 0) + '%' }} /></span></div>)}

      <div className="bsa-kpis">
        <div className="bsa-kpi"><span className="bsa-kpi-ic">{BSA_DICONS.sender}</span><span className="bsa-kpi-txt"><span className="bsa-kpi-lbl">{t.bsaSenderId || 'Sender ID'}</span><strong className="bsa-kpi-val">{txn.sender}</strong></span></div>
        <div className="bsa-kpi"><span className="bsa-kpi-ic">{BSA_DICONS.recipients}</span><span className="bsa-kpi-txt"><span className="bsa-kpi-lbl">{t.bsaTotalRecipients || 'Total Recipients'}</span><strong className="bsa-kpi-val">{headRecipients.toLocaleString()}{showProgressOf ? <em className="bsa-kpi-sub">{t.bsaProcOf || 'of'} {plannedTotal.toLocaleString()}</em> : null}</strong></span></div>
        <div className="bsa-kpi"><span className="bsa-kpi-ic">{BSA_DICONS.group}</span><span className="bsa-kpi-txt"><span className="bsa-kpi-lbl">{t.bsaRecipientsWord || 'Recipients'}</span><strong className="bsa-kpi-val"><BsaRecipients txn={txn} /></strong></span></div>
      </div>

      {(<React.Fragment>
      <div className="bsa-det-mid">
        <div className="bsa-card bsa-overview">
          <div className="bsa-ov-head">
            <div>
              <div className="bsa-card-h">{t.bsaOverviewStats || 'Overview Stats'}</div>
              <div className="bsa-ov-sub">{t.bsaRatesSub || 'Engagement rates — delivery, read, played, seen, failed & reply'}</div>
            </div>
            <div className="bsa-ov-legend">
              {rateBars.map((f) => <span key={f.k} className="bsa-ov-chip" title={f.tip}><i className={`d ${f.k}`} />{f.label}</span>)}
            </div>
          </div>
          <div className="bsa-funnel bsa-funnel-rates">
            <div className="bsa-funnel-bars">
              {[100, 75, 50, 25, 0].map((v) => <span key={v} className="bsa-gl" style={{ bottom: v + '%' }}><em>{v}</em></span>)}
              {rateBars.map((f) => (
                <div key={f.k} className="bsa-fcol" title={f.tip}>
                  <div className={`bsa-fbar ${f.k} ${noData ? 'is-empty' : ''}`} style={{ height: (chartIn && !noData ? f.pct : 0) + '%' }}><span className="bsa-fpct">{noData ? '—' : f.pct + '%'}</span></div>
                </div>
              ))}
            </div>
            <div className="bsa-funnel-x">
              {rateBars.map((f) => (
                <div key={f.k} className="bsa-fxcol" title={f.tip}><span className="bsa-flabel">{f.label}</span><span className="bsa-fcount">{noData ? '—' : f.count.toLocaleString()}</span></div>
              ))}
            </div>
          </div>
        </div>
        <div className="bsa-card bsa-cost-summary">
          <div className="bsa-card-h">{t.bsaCostBreakdown || 'Cost Breakdown'}</div>
          <div className="bsa-cost-grid">
            <div className="bsa-cost-item">
              <span className="bsa-cost-ic ic-sent">{BSA_DICONS.coins}</span>
              <div className="bsa-cost-meta"><div className="bsa-cost-val">{noData ? '—' : <React.Fragment><IcRiyal size={15} /> {st.cost.toLocaleString()}</React.Fragment>}</div><div className="bsa-cost-lbl">{t.bsaCostSent || 'Cost of messages sent'}</div></div>
            </div>
            <div className="bsa-cost-item">
              <span className="bsa-cost-ic ic-avg">{BSA_DICONS.receipt}</span>
              <div className="bsa-cost-meta"><div className="bsa-cost-val">{noData ? '—' : <React.Fragment><IcRiyal size={15} /> {st.avg}</React.Fragment>}</div><div className="bsa-cost-lbl">{t.bsaAvgCost || 'Average cost of a message'}</div></div>
            </div>
          </div>
          <div className="bsa-cbreak">
            <div className="bsa-cbreak-group bsa-dest-group">
              <div className="bsa-cbreak-h">{t.bsaByDest || 'By destination'}</div>
              {noData ? <div className="bsa-cbreak-empty">—</div> : (() => {
                const segs = st.costByDest.filter((d) => d.v > 0).map((d, i) => ({ ...d, color: BSA_DEST_COLORS[i % BSA_DEST_COLORS.length] }));
                const dtot = segs.reduce((s, d) => s + d.v, 0) || 1;
                return (
                  <div className="bsa-dest-chart">
                    <BsaDonut data={segs} total={dtot} />
                    <ul className="bsa-dest-list">
                      {segs.map((d) => <li key={d.k} className="bsa-dest-row"><span className="bsa-dest-dot" style={{ background: d.color }} /><span className="bsa-dest-name">{d.k}</span><span className="bsa-dest-val"><IcRiyal size={11} /> {d.v.toLocaleString()}</span><span className="bsa-dest-pct">{Math.round(d.v / dtot * 100)}%</span></li>)}
                    </ul>
                  </div>
                );
              })()}
            </div>
          </div>
        </div>
      </div>
      </React.Fragment>)}

      <div className="bsa-det-bottom">
        <div className="bsa-card bsa-recip-card">
          <div className="bsa-card-h bsa-recip-h">{t.bsaRecipientsDetails || 'Recipients Details'}</div>
          <div className="table-scroll">
            <table className="bsa-table bsa-recip-table">
              <thead><tr><th>{t.bsaColRecipientNum || 'Recipient Number'}</th><th>{t.bsaColStatus || 'Status'}</th><th>{t.bsaColSendDate || 'Send Date'}</th><th>{t.bsaColDeliveryDate || 'Delivery Date'}</th><th>{t.bsaColStatusDate || 'Status Date'}</th><th>{t.bsaColReply || 'Reply'}</th><th>{t.bsaColMsgCost || 'Message Cost'}</th><th className="bsa-col-action">{t.bsaColActions || 'Actions'}</th></tr></thead>
              <tbody>
                {recips.slice((recPage - 1) * recPageSize, recPage * recPageSize).map((r, li) => {
                  const i = (recPage - 1) * recPageSize + li;
                  const rs = BSA_RSTATUS[r.status] || BSA_RSTATUS.pending;
                  const isOpen = openMenuId === r.id;
                  return (
                    <tr key={r.id} className={sel === i ? 'bsa-recip-sel' : ''} onClick={() => setSel(i)}>
                      <td className="bsa-sender">{r.number}</td>
                      <td><span className={`bsa-recip-status ${rs.cls}`}>{rs.label}</span></td>
                      <td><BsaDateCell stamp={r.sendDate} /></td>
                      <td>{r.deliveryDate ? <BsaDateCell stamp={r.deliveryDate} /> : <span className="bsa-muted">---</span>}</td>
                      <td>{r.statusDate ? <BsaDateCell stamp={r.statusDate} /> : <span className="bsa-muted">---</span>}</td>
                      <td className="bsa-reply-cell">{r.replied ? <span className="bsa-replied" title="Replied">↩</span> : ''}</td>
                      <td className="bsa-cost"><IcRiyal size={12} /> {r.cost}</td>
                      <td className="bsa-col-action" style={{ position: 'relative' }}>
                        <button className="row-action-btn bsa-row-action-btn" onClick={(e) => { e.stopPropagation(); setOpenMenuId(isOpen ? null : r.id); }}><IcMore size={16} /></button>
                        {isOpen && <BsaRowMenu items={[{ id: 'conv', label: t.bsaConversation || 'Conversation', icon: <IcInfo size={14} stroke={1.8} />, disabled: (txn.status === 'scheduled' || txn.status === 'deleted'), disabledHint: t.bsaNoConvYet || 'No conversation for scheduled or deleted messages', onClick: () => { setOpenMenuId(null); onConversation(txn, r); } }]} onClose={() => setOpenMenuId(null)} />}
                      </td>
                    </tr>
                  );
                })}
                {recips.length === 0 && <tr><td colSpan={8} className="bsa-empty">{txn.status === 'failed' ? (t.bsaFailedNoRecords || 'No records were processed — the transaction was aborted before any messages were sent.') : (t.bsaNoRecipients || 'No recipients to show.')}</td></tr>}
              </tbody>
            </table>
          </div>
          {window.TablePagination && <window.TablePagination total={recips.length} page={recPage} pageSize={recPageSize} onPageChange={setRecPage} onPageSizeChange={(n) => { setRecPageSize(n); setRecPage(1); }} t={t} />}
        </div>
        <div className="bsa-card bsa-preview-card bsa-det-preview">
          <div className="bsa-card-h bsa-det-preview-h">{t.bsaPreview || 'Preview'}{selRecip ? <span className="bsa-det-preview-rn">{selRecip.number}</span> : null}</div>
          <BsaPhonePreview body={body} vars={selVals} empty={!body} />
        </div>
      </div>

      {askAI && <BsaAskAI txn={txn} summary={aiSummary} suggestions={[t.bsaAiQ1 || 'Summarize this transaction', t.bsaAiQ2 || 'Why are some messages still pending?', t.bsaAiQ3 || 'How can I improve the read rate?']} t={t} onClose={() => setAskAI(false)} />}
    </div>
  );
};

// ===== Voice (IVR) detailed view =====
// Full SIP-mapped voice call statuses.
const BSA_VSTATUS = {
  pending:     { label: 'Pending', cls: 'pd' },
  sent:        { label: 'Sent', cls: 'st' },
  ringing:     { label: 'Ringing', cls: 'rg' },
  live:        { label: 'Live', cls: 'lv' },
  answered:    { label: 'Answered', cls: 'an' },
  no_answer:   { label: 'No Answer', cls: 'na' },
  busy:        { label: 'Busy', cls: 'bs' },
  unreachable: { label: 'Unreachable', cls: 'ur' },
  dropped:     { label: 'Initiator Dropped', cls: 'dr' },
  canceled:    { label: 'Canceled', cls: 'cn' },
  failed:      { label: 'Failed', cls: 'fl' },
};
const BSA_VNUMS = ['+966 57 283 8628', '+966 55 991 2030', '+966 53 384 4111', '+966 56 174 2284', '+966 50 998 2200', '+962 79 655 0500', '+971 50 123 4567', '+965 9 887 6543', '+973 3 311 2200', '+20 10 2233 4455', '+966 54 220 7781', '+974 5 512 3344'];
// Recipient dial-code → destination country, so the cost-by-destination breakdown scales to many countries.
const BSA_DIAL_COUNTRY = [
  { code: '+966', name: 'Saudi Arabia' }, { code: '+971', name: 'UAE' }, { code: '+965', name: 'Kuwait' },
  { code: '+973', name: 'Bahrain' }, { code: '+974', name: 'Qatar' }, { code: '+968', name: 'Oman' },
  { code: '+962', name: 'Jordan' }, { code: '+961', name: 'Lebanon' }, { code: '+20', name: 'Egypt' },
];
const bsaCountryOf = (number) => { const s = (number || '').replace(/\s/g, ''); const m = BSA_DIAL_COUNTRY.find((c) => s.startsWith(c.code)); return m ? m.name : 'Other'; };
const BSA_DEST_COLORS = ['#0d3f44', '#1f7a6d', '#3fa796', '#7bc4b4', '#e0a458', '#c0613f', '#9a4d6e', '#5a6470', '#b0b8bd'];
// Per-recipient attempt plan (last entry = final result; earlier entries were retried).
const BSA_VPLANS = [
  ['answered'], ['no_answer', 'answered'], ['busy', 'answered'], ['no_answer', 'no_answer', 'no_answer'],
  ['unreachable', 'unreachable', 'failed'], ['answered'], ['busy', 'busy', 'answered'], ['failed'],
  ['no_answer', 'answered'], ['canceled'], ['answered'], ['busy', 'no_answer', 'answered'],
  ['dropped'], ['unreachable', 'dropped'],
];
const BSA_RETRY_WAITS = [0, 5, 10]; // configured wait (min) before attempt 1 / 2 / 3
// Full call lifecycle — one example of every status, used to lead the in-progress run's recipients grid (queued → live → terminal).
const BSA_VLIFECYCLE = ['pending', 'sent', 'ringing', 'live', 'answered', 'busy', 'no_answer', 'unreachable', 'dropped', 'canceled', 'failed'];
const BSA_VINFLIGHT = { pending: true, sent: true, ringing: true, live: true }; // in-flight — no terminal result/status-date yet
const bsaVAttemptCost = (st, rate) => st === 'answered' ? rate * 3 : st === 'live' ? rate * 2 : (st === 'busy' || st === 'no_answer' || st === 'unreachable' || st === 'dropped') ? rate : 0;
const bsaShiftTime = (timeStr, addMins) => {
  const mt = /(\d{1,2}):(\d{2})\s*(am|pm)/i.exec(timeStr || '02:40 pm');
  let h = mt ? parseInt(mt[1], 10) % 12 : 2; if (mt && /pm/i.test(mt[3])) h += 12;
  const total = (((mt ? h * 60 + parseInt(mt[2], 10) : 160) + addMins) % 1440 + 1440) % 1440;
  const hh = Math.floor(total / 60), mm = total % 60; let h12 = hh % 12; if (h12 === 0) h12 = 12;
  const ss = (total * 17 + 29) % 60; // deterministic seconds so send/status times show HH:MM:SS
  return `${String(h12).padStart(2, '0')}:${String(mm).padStart(2, '0')}:${String(ss).padStart(2, '0')} ${hh >= 12 ? 'pm' : 'am'}`;
};
const bsaVoiceRecipientsFor = (txn) => {
  const n = Math.min(txn.recipientsCount || 0, 24);
  const rate = txn.recipientsCount ? Math.max(2, Math.round((txn.totalCost || 0) / txn.recipientsCount / 2)) : 4;
  const bp = (txn.createdAt || '27-Mar-2025 · 02:40 pm').split('·');
  const bDate = (bp[0] || '27-Mar-2025').trim(), bTime = (bp[1] || '02:40 pm').trim();
  const stamp = (m) => `${bDate} · ${bsaShiftTime(bTime, m)}`;
  const scheduled = bsaIsScheduled(txn);
  return Array.from({ length: n }, (_, i) => {
    if (scheduled) return { id: 'vr' + i, number: BSA_VNUMS[i % BSA_VNUMS.length], status: 'pending', attempts: [{ n: 1, status: 'pending', wait: 0, at: null, cost: 0 }], sendDate: null, statusDate: null, cost: 0, duration: '00:00 sec', durSec: 0, completed: false, options: [] };
    // In-progress runs lead with one example of every status (queued → live → terminal); the rest follow the realistic retry mix.
    const plan = txn.status === 'failed' ? ['failed']
      : (txn.status === 'in_progress' && i < BSA_VLIFECYCLE.length) ? [BSA_VLIFECYCLE[i]]
      : BSA_VPLANS[i % BSA_VPLANS.length];
    const final = plan[plan.length - 1];
    const inflight = !!BSA_VINFLIGHT[final];
    let mins = 0;
    const attempts = plan.map((st, ai) => {
      const wait = BSA_RETRY_WAITS[ai] || 0; mins += wait;
      // A queued (pending) call has not been submitted to the provider yet, so it carries no timestamp.
      return { n: ai + 1, status: st, wait, at: st === 'pending' ? null : stamp(mins), cost: bsaVAttemptCost(st, rate) };
    });
    const answered = final === 'answered';
    const live = final === 'live'; // answered & IVR active — shows a running duration
    return {
      id: 'vr' + i, number: BSA_VNUMS[i % BSA_VNUMS.length], status: final, attempts,
      sendDate: final === 'pending' ? null : attempts[0].at,
      statusDate: inflight ? null : attempts[attempts.length - 1].at,
      cost: attempts.reduce((s, a) => s + a.cost, 0),
      duration: (answered || live) ? `00:${String((live ? 9 : 18) + (i % 7) * 3).padStart(2, '0')} sec` : '00:00 sec',
      durSec: (answered || live) ? (live ? 9 : 18) + (i % 7) * 3 : 0,
      completed: answered && i % 4 !== 0,
      options: [
        { level: 1, keys: ['1', '2'], selected: (i % 2) ? '1' : '2' },
        { level: 2, keys: ['1', '2', '3', '4', '5', '6', '*', '#'], selected: ['3', '4', '1'][i % 3] },
      ],
    };
  });
};
// Aggregate call statistics computed from the recipient set.
const bsaVoiceStats = (recips, txn) => {
  const total = recips.length || 1;
  const cnt = (s) => recips.filter((r) => r.status === s).length;
  const pct = (x) => Math.round((x / total) * 1000) / 10;
  const answered = cnt('answered');
  const answeredRecips = recips.filter((r) => r.status === 'answered');
  const completed = answeredRecips.filter((r) => r.completed).length;
  const durs = answeredRecips.map((r) => r.durSec || 0);
  const avg = durs.length ? Math.round(durs.reduce((a, b) => a + b, 0) / durs.length) : 0;
  const totalAttempts = recips.reduce((s, r) => s + r.attempts.length, 0);
  // The recipients table is a bounded sample (≤24 rows). Scale its cost/time figures up to the full
  // campaign so Total Cost / Total Seconds agree with the headline KPI and the outbox "Transaction Cost"
  // — otherwise the breakdown shows a smaller sample total than the campaign (two conflicting numbers).
  const sampleCost = recips.reduce((s, r) => s + r.cost, 0);
  const scale = (txn.recipientsCount && total) ? Math.max(1, txn.recipientsCount / total) : 1;
  const totalCost = txn.totalCost || Math.round(sampleCost * scale);
  const costScale = sampleCost ? totalCost / sampleCost : 1;
  const fullAnswered = Math.max(1, Math.round(answered * scale));
  const avgCost = Math.round((totalCost / fullAnswered) * 100) / 100;
  const totalSec = Math.round(avg * fullAnswered);
  const byAttempt = [1, 2, 3].map((k) => Math.round(recips.reduce((s, r) => s + (r.attempts[k - 1] ? r.attempts[k - 1].cost : 0), 0) * costScale));
  // Cost grouped by destination country (sorted high→low) — scales to any number of countries.
  const destMap = {}; recips.forEach((r) => { if (r.cost > 0) { const c = bsaCountryOf(r.number); destMap[c] = (destMap[c] || 0) + r.cost; } });
  const byDest = Object.keys(destMap).map((k) => ({ k, v: Math.round(destMap[k] * costScale) })).sort((a, b) => b.v - a.v);
  // Absorb per-segment rounding drift into the largest destination so the donut total matches Total Cost exactly.
  const byDestSum = byDest.reduce((s, d) => s + d.v, 0);
  if (byDest.length && byDestSum !== totalCost) byDest[0].v = Math.max(0, byDest[0].v + (totalCost - byDestSum));
  return {
    answeredRate: pct(answered), busyRate: pct(cnt('busy')), noAnswerRate: pct(cnt('no_answer')),
    failedRate: pct(cnt('failed') + cnt('unreachable') + cnt('canceled') + cnt('dropped')),
    completionRate: answered ? Math.round((completed / answered) * 100) : 0, avgDuration: avg,
    totalSec, avgCost,
    totalCost, costPerCall: totalAttempts ? Math.round((totalCost / totalAttempts) * 100) / 100 : 0,
    totalAttempts, byAttempt, byDest: byDest.length ? byDest : [{ k: 'Saudi Arabia', v: totalCost }],
  };
};
// IVR navigation insights (illustrative).
const BSA_VOICE_STATS = {
  paths: [{ p: '1 → 3 → 2', pct: 34 }, { p: '1 → 4 → 5', pct: 14 }, { p: '2 → 3 → 1', pct: 10 }, { p: 'Timeout', pct: 1 }],
  behavior: { avg: '3.2 sec', dropOff: '9%', fastest: '1' },
  perf: [{ o: 'Option 1', l: 'High Engagement', v: 46 }, { o: 'Option 3', l: 'Stable Performance', v: 27 }, { o: 'Option 2', l: 'Needs Optimization', v: 19 }, { o: '0 / *', l: 'Low Usage', v: 8 }],
};
// Donut chart for the cost-by-destination breakdown — segments + a centred total.
const BsaDonut = ({ data, total, size = 122, stroke = 16 }) => {
  const r = (size - stroke) / 2, c = 2 * Math.PI * r, cx = size / 2;
  let acc = 0;
  return (
    <svg className="bsa-donut" width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
      <circle cx={cx} cy={cx} r={r} fill="none" stroke="#eef1f0" strokeWidth={stroke} />
      <g transform={`rotate(-90 ${cx} ${cx})`}>
        {data.map((d, i) => { const frac = total ? d.v / total : 0; const dash = frac * c; const el = <circle key={i} cx={cx} cy={cx} r={r} fill="none" stroke={d.color} strokeWidth={stroke} strokeDasharray={`${dash} ${c - dash}`} strokeDashoffset={-acc} />; acc += dash; return el; })}
      </g>
      <text x={cx} y={cx - 1} textAnchor="middle" className="bsa-donut-total">{Math.round(total).toLocaleString()}</text>
      <text x={cx} y={cx + 14} textAnchor="middle" className="bsa-donut-sub">SAR</text>
    </svg>
  );
};
const BsaPlay = () => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7"><circle cx="12" cy="12" r="10" /><polygon points="10 8 16 12 10 16 10 8" fill="currentColor" stroke="none" /></svg>;

// Recorded-call preview modal — plays the IVR as delivered to the recipient (variable replacement + hang-up).
const BsaVoicePreview = ({ recip, ivrSeed, t, pushToast, onClose, onConversation }) => {
  const rs = BSA_VSTATUS[recip.status] || BSA_VSTATUS.pending;
  const answered = recip.status === 'answered';
  const nodes = (ivrSeed && ivrSeed.ivr && ivrSeed.ivr.nodes) || [];
  const seed = parseInt(String(recip.id || '0').replace(/\D/g, ''), 10) || 0;
  const steps = useMemoBsa(() => (answered ? bsaIvrWalk(nodes, seed) : []), [ivrSeed && ivrSeed.id, seed, answered]);
  const last = steps.length ? steps[steps.length - 1].node : null;
  const term = last && last.terminal && last.terminal.type;
  const outcome = term === 'transfer' ? (t.bsaIvrOutTransfer || 'Transferred to agent') : (t.bsaIvrOutHangup || 'Recipient hung up');
  useEffectBsa(() => {
    const f = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', f);
    return () => document.removeEventListener('keydown', f);
  }, []);
  return (
    <div className="bsa-modal-overlay" onMouseDown={onClose}>
      <div className="bsa-modal bsa-vprev2" onMouseDown={(e) => e.stopPropagation()}>
        <div className="bsa-vprev2-head">
          <span className="bsa-vprev2-ic"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.81.36 1.6.7 2.34a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.74-1.74a2 2 0 0 1 2.11-.45c.74.34 1.53.57 2.34.7A2 2 0 0 1 22 16.92z" /></svg></span>
          <div className="bsa-vprev2-htxt">
            <div className="bsa-vprev2-title">{t.bsaVoicePreview || 'Voice Call Preview'}</div>
            <div className="bsa-vprev2-sub">{recip.number} · {recip.duration || '00:00 sec'}</div>
          </div>
          <span className={`bsa-vstatus ${rs.cls}`}>{rs.label}</span>
          <button className="bsa-modal-close" onClick={onClose} aria-label={t.close || 'Close'}><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg></button>
        </div>
        <div className="bsa-vprev2-body">
          <div className={`bsa-vprev2-player ${!answered ? 'is-off' : ''}`}>
            <button type="button" className="bsa-vprev2-play" onClick={() => pushToast(answered ? (t.bsaPlayingRec || 'Playing recorded call…') : (t.bsaNoAudio || 'No connected audio for this call result.'))} disabled={!answered}><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M7 4.5v15l13-7.5z" /></svg></button>
            <div className="bsa-vprev2-pmeta">
              <BsaVConvWave />
              <div className="bsa-vprev2-plabel"><span>{answered ? (t.bsaRecordedCall || 'Recorded call') : (t.bsaNoRecording || 'No recording for this call')}</span><span className="bsa-vprev2-pdur">{recip.duration || '00:00 sec'}</span></div>
            </div>
          </div>
          {answered ? (
            <React.Fragment>
              <div className="bsa-vprev2-tiles">
                <div className="bsa-vprev2-tile"><span className="bsa-vprev2-tval">{recip.duration || '—'}</span><span className="bsa-vprev2-tlbl">{t.bsaDuration || 'Duration'}</span></div>
                <div className="bsa-vprev2-tile"><span className="bsa-vprev2-tval bsa-vprev2-tout">{outcome}</span><span className="bsa-vprev2-tlbl">{t.bsaOutcome || 'Outcome'}</span></div>
              </div>
              <div className="bsa-vprev2-canvasbox">
                <div className="bsa-vprev2-phead">{t.bsaIvrFlowPlay || 'IVR flow — tap any node to play its prompt'}</div>
                {window.TplIvrStep2 && ivrSeed
                  ? <div className="ivr-edit-canvas is-readonly td-ivr-flow bsa-ivr-flow bsa-vprev2-canvas"><window.TplIvrStep2 key={ivrSeed.id} data={ivrSeed} setData={() => {}} t={t} readOnly={true} inspect={true} /></div>
                  : <div className="bsa-vprev2-empty">{t.bsaNoIvrFlow || 'No IVR flow available for this template.'}</div>}
              </div>
              {onConversation ? <button type="button" className="bsa-vprev2-conv" onClick={() => { onClose(); onConversation(); }}><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /></svg> {t.bsaViewConversation || 'View full conversation'}</button> : null}
            </React.Fragment>
          ) : (
            <div className="bsa-vprev2-empty"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><line x1="12" y1="16" x2="12" y2="12" /><line x1="12" y1="8" x2="12.01" y2="8" /></svg> {t.bsaRecNoteNo || 'This call did not connect, so there is no recorded audio or IVR navigation. The attempt history is in the recipients grid.'}</div>
          )}
        </div>
      </div>
    </div>
  );
};

// ===== Voice "Conversation" — the recipient's IVR call rendered as a WhatsApp-style transcript =====
const bsaIvrKids = (nodes, id) => nodes.filter((n) => (n.parent || null) === (id || null));
const bsaNodeDur = (node) => Math.max(2, (node.segments || []).reduce((s, seg) => s + (seg.kind === 'recording' ? (seg.dur || 3) : 2), 0));
// Walk the IVR tree from the root, choosing the child the recipient "pressed" at each menu (deterministic per recipient).
const bsaIvrWalk = (nodes, seed) => {
  if (!nodes || !nodes.length) return [];
  const out = []; const root = nodes.find((n) => !n.parent) || nodes[0];
  let cur = root, depth = 0;
  while (cur && depth < 6) {
    const kids = bsaIvrKids(nodes, cur.id);
    const pick = kids.length ? kids[((seed || 0) + depth) % kids.length] : null;
    out.push({ node: cur, kids, pressed: pick });
    if (!pick) break; cur = pick; depth++;
  }
  return out;
};
const BsaVConvWave = () => { const hs = [6, 11, 16, 9, 14, 7, 12, 17, 8, 13, 6, 15, 9, 12, 7, 16, 10, 13, 8, 11, 15, 6]; return <span className="bsa-vconv-wave">{hs.map((h, i) => <i key={i} style={{ height: h + 'px' }} />)}</span>; };
// Playable IVR voice note: the waveform fills + the timer counts up while it plays (play/pause, auto-stops at the end).
const BsaVoiceNote = ({ dur }) => {
  const total = Math.max(1, Math.min(dur || 3, 59));
  const [playing, setPlaying] = useStateBsa(false);
  const [elapsed, setElapsed] = useStateBsa(0);
  useEffectBsa(() => { if (!playing) return; const id = setInterval(() => setElapsed((e) => e + 0.12), 120); return () => clearInterval(id); }, [playing]);
  useEffectBsa(() => { if (elapsed >= total) { setPlaying(false); setElapsed(0); } }, [elapsed, total]);
  const frac = total ? Math.min(1, elapsed / total) : 0;
  const showSec = (playing || elapsed > 0) ? elapsed : total;
  const hs = [6, 11, 16, 9, 14, 7, 12, 17, 8, 13, 6, 15, 9, 12, 7, 16, 10, 13, 8, 11, 15, 6];
  return (
    <div className="bsa-vconv-voice">
      <button type="button" className={`bsa-vconv-play ${playing ? 'is-playing' : ''}`} onClick={() => setPlaying((p) => !p)} aria-label={playing ? 'Pause' : 'Play'}>
        {playing
          ? <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="5" width="4" height="14" rx="1" /><rect x="14" y="5" width="4" height="14" rx="1" /></svg>
          : <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><path d="M7 4.5v15l13-7.5z" /></svg>}
      </button>
      <span className="bsa-vconv-wave">{hs.map((h, i) => <i key={i} className={(i + 0.5) / hs.length <= frac ? 'on' : ''} style={{ height: h + 'px' }} />)}</span>
      <span className="bsa-vconv-dur">0:{String(Math.min(59, Math.round(showSec))).padStart(2, '0')}</span>
    </div>
  );
};

// Spoken transcript of a node: concatenates each recording's text and inlines the dynamic variable values.
const bsaNodeTranscript = (node, vars) => {
  const segs = (node && node.segments) || [];
  const T = window.bsaIvrTranscripts || {};
  const pieces = segs.map((s) => s.kind === 'recording' ? (T[s.name] || '') : (vars && vars[s.key] != null ? String(vars[s.key]) : '')).filter(Boolean);
  const txt = pieces.join(' ').replace(/\s+([.,])/g, '$1').replace(/\s{2,}/g, ' ').trim();
  return txt || (node ? node.label : '');
};
// Plain-language description of a recipient's call outcome (each call is a record, so this summarises it).
const bsaRecipDesc = (r, walk) => {
  const n = r.attempts.length, att = n > 1 ? ` after ${n} attempts` : '';
  if (r.status === 'answered') {
    const last = walk.length ? walk[walk.length - 1] : null;
    const end = last && last.node.terminal && last.node.terminal.type;
    const endTxt = end === 'transfer' ? 'and was transferred to an agent' : end === 'return_root' ? 'and returned to the main menu before hanging up' : end === 'return_parent' ? 'and returned to the previous menu' : 'before the call ended';
    const path = walk.map((s) => s.node.label).join(' → ');
    return `Answered${att} and navigated the IVR — ${path} — ${endTxt}. Call duration ${r.duration}.`;
  }
  if (r.status === 'live') return `Call is live — the recipient is in the IVR now.${walk.length ? ' Path so far: ' + walk.map((s) => s.node.label).join(' → ') + '.' : ''}`;
  if (r.status === 'busy') return `The line was busy${att}; the IVR was never reached.`;
  if (r.status === 'no_answer') return `The call rang but was not picked up${att}; the IVR was never reached.`;
  if (r.status === 'unreachable') return `The number could not be reached${att} (out of service or invalid).`;
  if (r.status === 'dropped') return `The call was dropped by the system or provider before it was answered${att}.`;
  if (r.status === 'canceled') return 'The call was canceled before completion.';
  if (r.status === 'failed') return `A technical error prevented the call from completing${att}.`;
  if (r.status === 'ringing') return "The call is currently ringing on the recipient's device.";
  if (r.status === 'sent') return 'The call request has been submitted to the voice provider and is awaiting connection.';
  if (r.status === 'pending') return 'The call is queued and has not yet been submitted to the voice provider.';
  return 'No additional details for this call.';
};
// Derive the AI-handoff intent (which scripted continuation to play) from the template/IVR.
const bsaAiIntent = (txn, ivrSeed) => {
  const x = (((txn && txn.templateName) || '') + ' ' + ((ivrSeed && ivrSeed.name) || '') + ' ' + ((ivrSeed && ivrSeed.id) || '')).toLowerCase();
  if (/stc|bill|invoice|pay/.test(x)) return 'billing';
  if (/saudia|flight|travel|book|airline/.test(x)) return 'travel';
  if (/absher|appoint|reminder|schedul/.test(x)) return 'appointment';
  if (/rajhi|bank|card|account|riyad/.test(x)) return 'banking';
  return 'support';
};
// Which messaging channel a given recipient continued on — split deterministically so the demo shows both.
// Uses bit 1 of the seed (not bit 0, which usually drives the IVR menu choice) so both channels appear among transfers.
const bsaChannelFor = (seed) => ((Math.floor((seed || 0) / 2) % 2) === 0 ? 'whatsapp' : 'instagram');
// Add minutes to a "hh:mm am/pm" label (so the chat timestamps progress after the call).
const bsaAddMin = (timeStr, mins) => {
  const mt = /(\d{1,2}):(\d{2})\s*(am|pm)?/i.exec(timeStr || '');
  if (!mt) return timeStr || '';
  let h = parseInt(mt[1], 10) % 12; if ((mt[3] || '').toLowerCase() === 'pm') h += 12;
  const total = ((h * 60 + parseInt(mt[2], 10) + mins) % 1440 + 1440) % 1440;
  const hh = Math.floor(total / 60), mm = total % 60; let h12 = hh % 12; if (h12 === 0) h12 = 12;
  return String(h12).padStart(2, '0') + ':' + String(mm).padStart(2, '0') + ' ' + (hh >= 12 ? 'pm' : 'am');
};
const BsaChanIcon = ({ channel }) => channel === 'instagram'
  ? <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="2" width="20" height="20" rx="5" /><circle cx="12" cy="12" r="4" /><line x1="17.5" y1="6.5" x2="17.51" y2="6.5" /></svg>
  : <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M12.04 2C6.58 2 2.13 6.45 2.13 11.91c0 1.75.46 3.46 1.32 4.96L2 22l5.25-1.38a9.9 9.9 0 0 0 4.79 1.22h.01c5.46 0 9.91-4.45 9.91-9.91 0-2.65-1.03-5.14-2.9-7.01A9.82 9.82 0 0 0 12.04 2Zm5.8 14.13c-.25.69-1.45 1.32-1.99 1.37-.53.05-1.02.24-3.45-.72-2.91-1.15-4.76-4.12-4.9-4.31-.14-.19-1.17-1.55-1.17-2.96 0-1.41.74-2.1 1-2.39.25-.29.55-.36.73-.36.18 0 .37 0 .53.01.17.01.4-.06.62.48.25.6.84 2.06.91 2.21.07.14.12.31.02.5-.09.19-.14.31-.28.48-.14.16-.29.37-.42.49-.14.14-.28.29-.12.57.16.27.71 1.17 1.53 1.9 1.05.94 1.94 1.23 2.21 1.37.27.14.43.12.59-.07.16-.19.68-.79.86-1.07.18-.27.36-.22.61-.13.25.09 1.6.75 1.87.89.27.14.45.21.52.32.07.12.07.66-.18 1.35Z" /></svg>;

// AI-agent continuation: the IVR "transfer" hands the customer to the Falcon AI Assistant on a text channel.
// This part IS a live WhatsApp/Instagram chat, so it carries the WhatsApp message actions (info · reply · react) + a composer.
// Shared state for the WhatsApp/Instagram AI-handoff live chat. The chat bubbles render in the scrolling
// thread body while the composer renders in the pinned footer — the same split as the WhatsApp conversation,
// so they must share one state source (this hook).
const useBsaHandoff = (intent, channel, number, time, t) => {
  const script = (window.bsaAiHandoff || {})[intent] || (window.bsaAiHandoff || {}).support || { agent: 'Falcon AI Assistant', msgs: [] };
  const chanLabel = channel === 'instagram' ? 'Instagram' : 'WhatsApp';
  const idRef = useRefBsa(0);
  const [msgs, setMsgs] = useStateBsa(() => script.msgs.map((m, i) => ({ ...m, id: 'ai' + i, time: bsaAddMin(time, 1 + i) })));
  const [reactFor, setReactFor] = useStateBsa(null);
  const [replyTo, setReplyTo] = useStateBsa(null);
  const [draft, setDraft] = useStateBsa('');
  useEffectBsa(() => { const f = (e) => { if (!e.target.closest || !e.target.closest('.bsa-cv-pop, .bsa-cv-actbtn')) setReactFor(null); }; document.addEventListener('mousedown', f); return () => document.removeEventListener('mousedown', f); }, []);
  const who = (m) => m.from === 'ai' ? script.agent : (m.from === 'me' ? ((t && t.bsaYou) || 'You') : number);
  const setReaction = (id, emo) => { setMsgs((arr) => arr.map((m) => m.id === id ? { ...m, reaction: m.reaction === emo ? null : emo } : m)); setReactFor(null); };
  const sendReply = () => { if (!draft.trim()) return; setMsgs((arr) => [...arr, { id: 'r' + (++idRef.current), from: 'me', text: draft.trim(), time: bsaAddMin(time, 8 + arr.length), replyTo: replyTo ? { from: who(replyTo), text: replyTo.text } : null }]); setDraft(''); setReplyTo(null); };
  return { script, chanLabel, msgs, reactFor, setReactFor, replyTo, setReplyTo, draft, setDraft, who, setReaction, sendReply };
};
// part="chat" → channel divider + AI/recipient bubbles (rendered inside the scrolling thread body).
// part="foot" → reply bar + composer (rendered inside the pinned .bsa-cv-foot, exactly like the WhatsApp conversation).
const BsaAiHandoff = ({ part, ho, channel, number, t, pushToast }) => {
  const { script, chanLabel, msgs, reactFor, setReactFor, replyTo, setReplyTo, draft, setDraft, who, setReaction, sendReply } = ho;
  const actions = (m) => (
    <div className={`bsa-cv-acts ${reactFor === m.id ? 'open' : ''}`}>
      <button className="bsa-cv-actbtn" title={t.bsaMessageInfo || 'Message info'} onClick={() => pushToast(who(m) + ' · ' + chanLabel + ' · ' + m.time)}>{BsaCvIc.info}</button>
      <button className="bsa-cv-actbtn primary" title={t.reply || 'Reply'} onClick={() => setReplyTo(m)}>{BsaCvIc.reply}</button>
      <div className="bsa-cv-reactwrap">
        <button className="bsa-cv-actbtn" title={t.bsaReact || 'React'} onClick={() => setReactFor((x) => x === m.id ? null : m.id)}>{BsaCvIc.smile}</button>
        {reactFor === m.id && <div className={`bsa-cv-pop bsa-cv-reactpop ${(m.from === 'ai' || m.from === 'me') ? 'left' : 'right'}`}>{BSA_REACTIONS.map((e) => <button key={e} type="button" className={m.reaction === e ? 'on' : ''} onClick={() => setReaction(m.id, e)}>{e}</button>)}</div>}
      </div>
    </div>
  );
  if (part === 'foot') {
    return (
      <React.Fragment>
        {replyTo ? (
          <div className="bsa-cv-replybar">
            <span className="bsa-cv-replybar-ic">{BsaCvIc.reply}</span>
            <div className="bsa-cv-replybar-q"><span>{who(replyTo)}</span><em>{replyTo.text}</em></div>
            <button type="button" onClick={() => setReplyTo(null)} title={t.cancel || 'Cancel'}>{BsaCvIc.x}</button>
          </div>
        ) : null}
        <div className="bsa-cv-composer">
          <input className="bsa-cv-input" placeholder={(t.bsaReplyOnChan || 'Reply on') + ' ' + chanLabel + ' …'} value={draft} onChange={(e) => setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') sendReply(); }} />
          <button className="btn btn-primary bsa-cv-send" onClick={sendReply}>{BsaCvIc.send} {t.bsaSendMsg || 'Send'}</button>
        </div>
      </React.Fragment>
    );
  }
  return (
    <div className={`bsa-aichat chan-${channel}`}>
      <div className="bsa-aichat-divider">
        <span className="bsa-aichat-line" />
        <span className={`bsa-aichat-chan chan-${channel}`}><BsaChanIcon channel={channel} /> {chanLabel}</span>
        <span className="bsa-aichat-line" />
      </div>
      <div className="bsa-aichat-handnote">{(t.bsaAiContinued || 'Call ended — conversation continued with')} <b>{script.agent}</b> {(t.bsaOn || 'on')} {chanLabel}</div>
      {msgs.map((m) => {
        const isAi = m.from === 'ai';
        const isMe = m.from === 'me';
        return (isAi || isMe) ? (
          // Business side — the AI assistant or the operator's own reply — on the RIGHT.
          <div key={m.id} className="bsa-cv-msg out">
            <div className="bsa-cv-stack">
              <div className={`bsa-cv-sender ${isAi ? 'bsa-ai-sender' : ''}`}>{isAi ? <React.Fragment><span className="bsa-ai-tag">AI</span> <strong>{script.agent}</strong></React.Fragment> : <strong>{t.bsaYou || 'You'}</strong>}</div>
              <div className="bsa-cv-line">
                {actions(m)}
                <div className={`bsa-cv-bubble ${isAi ? 'is-ai' : ''} ${m.reaction ? 'has-react' : ''}`}>
                  {m.replyTo ? <div className="bsa-cv-quote"><span>{m.replyTo.from}</span><em>{m.replyTo.text}</em></div> : null}
                  <div className="bsa-cv-text">{m.text}</div>
                  {m.reaction ? <span className="bsa-cv-reaction">{m.reaction}</span> : null}
                </div>
              </div>
              <div className="bsa-cv-time">{m.time}<BsaTicks status={isAi ? 'read' : 'sent'} /></div>
            </div>
            {isAi ? <span className="bsa-cv-ava bsa-ai-ava">AI</span> : <BsaAvatar tone="agent" size={36} />}
          </div>
        ) : (
          // Recipient reply — on the LEFT.
          <div key={m.id} className="bsa-cv-msg in">
            <BsaAvatar tone="cust" size={36} />
            <div className="bsa-cv-stack">
              <div className="bsa-cv-sender"><strong>{number}</strong></div>
              <div className="bsa-cv-line">
                <div className={`bsa-cv-bubble ${m.reaction ? 'has-react' : ''}`}>
                  {m.replyTo ? <div className="bsa-cv-quote"><span>{m.replyTo.from}</span><em>{m.replyTo.text}</em></div> : null}
                  <div className="bsa-cv-text">{m.text}</div>
                  {m.reaction ? <span className="bsa-cv-reaction">{m.reaction}</span> : null}
                </div>
                {actions(m)}
              </div>
              <div className="bsa-cv-time">{m.time}</div>
            </div>
          </div>
        );
      })}
      <div className="bsa-aichat-resolved"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5" /></svg> {(t.bsaAiResolved || 'Resolved by the AI assistant — no human agent required.')}</div>
    </div>
  );
};

const BsaVoiceConversation = ({ txn, recipient, t, onBack, pushToast, onSendWhatsapp, onSendVoice }) => {
  const recip = recipient || {};
  const rs = BSA_VSTATUS[recip.status] || BSA_VSTATUS.pending;
  const answered = recip.status === 'answered';
  const number = recip.number || txn.sender;
  const callTime = ((recip.sendDate || txn.createdAt || '').split('·')[1] || '01:20 pm').trim();
  const callDay = ((recip.sendDate || txn.createdAt || 'Today').split('·')[0] || 'Today').trim();
  const ivrSeeds = (window.seedTemplates || []).filter((x) => x.ivr && (x.ivr.nodes || []).length);
  const ivrSeed = ivrSeeds.find((x) => x.ivr.type === String(txn.type || '').toLowerCase()) || ivrSeeds[0];
  const nodes = (ivrSeed && ivrSeed.ivr && ivrSeed.ivr.nodes) || [];
  const ivrVars = {}; ((ivrSeed && ivrSeed.ivr && ivrSeed.ivr.variables) || []).forEach((v) => { ivrVars[v.key] = v.sample; });
  const seed = parseInt(String(recip.id || '0').replace(/\D/g, ''), 10) || 0;
  useEffectBsa(() => { const f = (e) => { if (e.key === 'Escape') onBack(); }; document.addEventListener('keydown', f); return () => document.removeEventListener('keydown', f); }, []);

  // Walk the IVR tree from the root, picking the child the recipient "pressed" at each menu (deterministic per recipient).
  const steps = useMemoBsa(() => (answered ? bsaIvrWalk(nodes, seed) : []), [ivrSeed && ivrSeed.id, seed, answered]);

  const last = steps.length ? steps[steps.length - 1].node : null;
  const term = last && last.terminal && last.terminal.type;
  const endText = term === 'transfer' ? (t.bsaIvrEndTransfer || 'Transferred to a customer-service agent — call ended.')
    : term === 'return_root' ? (t.bsaIvrEndRoot || 'Returned to the main menu, then the recipient hung up.')
    : term === 'return_parent' ? (t.bsaIvrEndParent || 'Returned to the previous menu, then the recipient hung up.')
    : (t.bsaIvrEndHangup || 'The recipient hung up — call ended.');
  // A "transfer" ending hands the customer off to the Falcon AI Assistant on their preferred text channel.
  const hasHandoff = answered && term === 'transfer';
  const intent = bsaAiIntent(txn, ivrSeed);
  const channel = bsaChannelFor(seed);
  const orgLabel = 'Aramco-Marketing-Office Management';
  const ho = useBsaHandoff(intent, channel, number, callTime, t);

  return (
    <div className="bsa-takeover bsa-conv2 bsa-vconv2">
      <div className="bsa-cv-topbar">
        <button className="bsa-cv-back" onClick={onBack}><span className="bsa-cv-backcircle">{BsaCvIc.back}</span> {t.back || 'Back'}</button>
        <div className="bsa-cv-topmeta">
          <div><span>{t.bsaMessageName || 'Message Name'}:</span> <strong>{txn.templateName}</strong></div>
          <div><span>{t.bsaCreatedDate || 'Created Date'}:</span> <strong>{txn.createdAt}</strong></div>
        </div>
        <div className="bsa-cv-topbar-r"><span className={`bsa-vstatus ${rs.cls}`}>{rs.label}</span></div>
      </div>

      <div className="bsa-cv-grid">
        <div className="bsa-cv-info">
          <div className="bsa-cv-info-h">{t.bsaCallInfo || 'Call Info'}</div>
          <div className="bsa-cv-info-row"><span>{t.bsaSenderId || 'Sender ID'}</span><strong>{txn.sender}</strong></div>
          <div className="bsa-cv-info-row"><span>{t.bsaRecipientNumber || 'Recipient Number'}</span><strong>{number}</strong></div>
          <div className="bsa-cv-info-row"><span>{t.bsaType || 'Type'}</span><strong>{txn.type}</strong></div>
          <div className="bsa-cv-info-row"><span>{t.bsaColStatus || 'Status'}</span><strong><span className={`bsa-vstatus ${rs.cls}`}>{rs.label}</span></strong></div>
          <div className="bsa-cv-info-sep" />
          <div className="bsa-cv-info-block">
            <div className="bsa-cv-info-drow"><div className="bsa-cv-info-dcol"><span className="bsa-cv-info-dlabel">{t.bsaSendDate || 'Send Date'}</span><strong className="bsa-cv-info-dval">{recip.sendDate || '—'}</strong></div></div>
            <div className="bsa-cv-info-drow"><div className="bsa-cv-info-dcol"><span className="bsa-cv-info-dlabel">{t.bsaColStatusDate || 'Status Date'}</span><strong className="bsa-cv-info-dval">{recip.statusDate || '—'}</strong></div></div>
            <div className="bsa-cv-info-drow"><div className="bsa-cv-info-dcol"><span className="bsa-cv-info-dlabel">{t.bsaDuration || 'Duration'}</span><strong className="bsa-cv-info-dval">{recip.duration || '—'}</strong></div></div>
            <div className="bsa-cv-info-drow"><div className="bsa-cv-info-dcol"><span className="bsa-cv-info-dlabel">{t.bsaOutcome || 'Outcome'}</span><strong className="bsa-cv-info-dval">{answered ? (hasHandoff ? (t.bsaIvrOutTransfer || 'Transferred to agent') : (t.bsaIvrCall || 'IVR call')) : rs.label}</strong></div></div>
          </div>
        </div>

        <div className="bsa-cv-thread">
          <div className="bsa-cv-head">
            <span className="bsa-cv-title">{t.bsaConversation || 'Conversation'}</span>
            <BsaAvatar tone="cust" size={40} />
            <span className="bsa-cv-recip">{t.bsaRecipientNumber || 'Recipient Number'}: <strong>{number}</strong></span>
            <span className="bsa-vconv2-headsub">{answered ? (t.bsaIvrCall || 'IVR call') + (recip.duration ? ' · ' + recip.duration : '') : (t.bsaIvrNoConnect || 'call did not connect')}</span>
          </div>

          <div className="bsa-cv-body bsa-vconv-cvbody">
        {!answered ? (
          <div className="bsa-vconv-empty">
            <span className="bsa-vconv-empty-ic"><svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.29.62 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2A19.79 19.79 0 0 1 3.07 4.18 2 2 0 0 1 5 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .62 2.29 2 2 0 0 1-.45 2.11L8.9 9.39" /><line x1="23" y1="1" x2="1" y2="23" /></svg></span>
            <div className="bsa-vconv-empty-h">{t.bsaIvrNoConvH || 'No conversation to show'}</div>
            <div className="bsa-vconv-empty-p">{(t.bsaIvrNoConvP || 'This call was')} {rs.label}{t.bsaIvrNoConvP2 || ' — the recipient never reached the IVR menu, so there are no prompts or key presses to display.'}</div>
          </div>
        ) : (
          <React.Fragment>
            <div className="bsa-cv-day"><span>{callDay}</span></div>
            <div className="bsa-vconv-note">{t.bsaIvrStart || 'Call connected — the recipient navigated the IVR menu below.'}</div>
            {steps.map((s, i) => {
              const dur = bsaNodeDur(s.node);
              const transcript = bsaNodeTranscript(s.node, ivrVars);
              return (
                <React.Fragment key={i}>
                  {/* IVR prompt — the sender/business side, on the right */}
                  <div className="bsa-cv-msg out">
                    <div className="bsa-cv-stack">
                      <div className="bsa-cv-sender">{orgLabel}- <strong>{t.bsaIvrSystem2 || 'IVR System'}</strong></div>
                      <div className="bsa-cv-line">
                        <div className="bsa-cv-bubble bsa-vconv-ivrbubble">
                          <div className="bsa-vconv-nodelabel"><span className="bsa-vconv-sys">{t.bsaIvrSystem || 'IVR'}</span> {s.node.label}</div>
                          <BsaVoiceNote dur={dur} />
                          {transcript ? <div className="bsa-vconv-transcript"><span className="bsa-vconv-tlabel">{t.bsaTranscript || 'Transcript'}</span>{transcript}</div> : null}
                          {s.kids.length ? (
                            <div className="bsa-vconv-menu">
                              {s.kids.map((c) => <div key={c.id} className={`bsa-vconv-opt ${s.pressed && s.pressed.id === c.id ? 'picked' : ''}`}><span className="bsa-vconv-optkey">{c.key}</span><span className="bsa-vconv-optlbl">{c.label}</span></div>)}
                            </div>
                          ) : null}
                        </div>
                      </div>
                      <div className="bsa-cv-time">{callTime}<BsaTicks status="read" /></div>
                    </div>
                    <BsaAvatar tone="agent" size={36} />
                  </div>
                  {/* Key the recipient pressed — recipient side, on the left */}
                  {s.pressed ? (
                    <div className="bsa-cv-msg in">
                      <BsaAvatar tone="cust" size={36} />
                      <div className="bsa-cv-stack">
                        <div className="bsa-cv-sender"><strong>{number}</strong></div>
                        <div className="bsa-cv-line">
                          <div className="bsa-cv-bubble bsa-vconv-pressbubble">
                            <span className="bsa-vconv-dtmf">{s.pressed.key}</span>
                            <span className="bsa-vconv-presslbl">{(t.bsaIvrPressed || 'Pressed')} <b>{s.pressed.key}</b> — {s.pressed.label}</span>
                          </div>
                        </div>
                        <div className="bsa-cv-time">{callTime}</div>
                      </div>
                    </div>
                  ) : null}
                </React.Fragment>
              );
            })}
            {hasHandoff
              ? <BsaAiHandoff part="chat" ho={ho} channel={channel} number={number} t={t} pushToast={pushToast} />
              : <div className="bsa-vconv-end"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.29.62 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2A19.79 19.79 0 0 1 3.07 4.18 2 2 0 0 1 5 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .62 2.29 2 2 0 0 1-.45 2.11L8.9 9.39" /><line x1="23" y1="1" x2="1" y2="23" /></svg> {endText}</div>}
          </React.Fragment>
        )}
          </div>
          <div className="bsa-cv-foot bsa-vconv2-foot">
            <div className="bsa-vconv2-actions">
              <button type="button" className="btn btn-secondary bsa-vconv2-act" onClick={() => onSendWhatsapp && onSendWhatsapp()}>
                <svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M17.5 14.4c-.3-.15-1.77-.87-2.04-.97-.27-.1-.47-.15-.67.15-.2.3-.77.97-.94 1.17-.17.2-.35.22-.65.07-.3-.15-1.26-.46-2.4-1.48-.89-.79-1.49-1.77-1.66-2.07-.17-.3-.02-.46.13-.61.13-.13.3-.35.45-.52.15-.17.2-.3.3-.5.1-.2.05-.37-.02-.52-.07-.15-.67-1.62-.92-2.22-.24-.58-.49-.5-.67-.51l-.57-.01c-.2 0-.52.07-.8.37-.27.3-1.04 1.02-1.04 2.49s1.07 2.89 1.22 3.09c.15.2 2.1 3.2 5.08 4.49.71.31 1.26.49 1.69.63.71.22 1.36.19 1.87.12.57-.09 1.77-.72 2.02-1.42.25-.7.25-1.3.17-1.42-.07-.13-.27-.2-.57-.35zM12.05 21.5h-.01a9.4 9.4 0 0 1-4.8-1.32l-.34-.2-3.57.94.95-3.48-.22-.36A9.42 9.42 0 0 1 21.5 12a9.44 9.44 0 0 1-9.45 9.5zM20.52 3.48A11.8 11.8 0 0 0 12.05.5 11.86 11.86 0 0 0 1.86 18.3L.5 23.5l5.32-1.4a11.83 11.83 0 0 0 5.66 1.44h.01A11.86 11.86 0 0 0 20.52 3.48z" /></svg>
                {t.bsaSendWa || 'Send Whatsapp Message'}
              </button>
              <button type="button" className="btn btn-primary bsa-vconv2-act" onClick={() => onSendVoice && onSendVoice()}>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.81.36 1.6.7 2.34a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.7a2 2 0 0 1 2.11-.45c.74.34 1.53.57 2.34.7A2 2 0 0 1 22 16.92z" /></svg>
                {t.bsaSendVoice || 'Send Voice IVR Message'}
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

const BsaVoiceDetails = ({ txn, t, onBack, pushToast, onConversation }) => {
  const [recips] = useStateBsa(() => bsaVoiceRecipientsFor(txn));
  const [openMenuId, setOpenMenuId] = useStateBsa(null);
  const [expandId, setExpandId] = useStateBsa(null);
  const [selRecId, setSelRecId] = useStateBsa(null);
  const [previewId, setPreviewId] = useStateBsa(null);
  const [recPage, setRecPage] = useStateBsa(1);
  const [recPageSize, setRecPageSize] = useStateBsa(10);
  const [askAI, setAskAI] = useStateBsa(false);
  // The global header "Ask AI" button opens THIS transaction's side pop-up while a details page is open.
  useEffectBsa(() => {
    if (!window.falconAskAI) return;
    window.falconAskAI.handler = () => { if (txn.status !== 'scheduled' && txn.status !== 'deleted') setAskAI(true); };
    return () => { if (window.falconAskAI) window.falconAskAI.handler = null; };
  }, []);
  const scheduled = bsaIsScheduled(txn);
  const sv = bsaStatusView(txn);
  const headRecipients = bsaHeadRecipients(txn), headCost = bsaHeadCost(txn);
  const plannedTotal = txn.targetCount || txn.plannedCount || 0;
  const showProgressOf = (txn.status === 'in_progress' || txn.status === 'partial' || txn.status === 'canceled') && plannedTotal > headRecipients;
  const costDenomN = txn.targetCount || txn.plannedCount || 0; // the planned total this run was meant to reach
  const costProg = (costDenomN && (txn.status === 'in_progress' || txn.status === 'partial' || txn.status === 'canceled')) ? Math.min(1, (txn.recipientsCount || 0) / costDenomN) : 1;
  const noData = !sv.stats; // scheduled / deleted / failed → render the stats sections but with no values (placeholders)
  const stats = useMemoBsa(() => bsaVoiceStats(recips, txn), [recips]);
  const vs = BSA_VOICE_STATS;
  const previewRecip = recips.find((r) => r.id === previewId);
  const ivrSeeds = (window.seedTemplates || []).filter((x) => x.ivr && (x.ivr.nodes || []).length);
  const ivrSeed = ivrSeeds.find((x) => x.ivr.type === String(txn.type || '').toLowerCase()) || ivrSeeds[0];
  const ivrNodes = (ivrSeed && ivrSeed.ivr && ivrSeed.ivr.nodes) || [];
  const ivrVars = {}; ((ivrSeed && ivrSeed.ivr && ivrSeed.ivr.variables) || []).forEach((v) => { ivrVars[v.key] = v.sample; });
  // Recipient selected in the grid → its IVR canvas + path show in the side preview (mirrors the WhatsApp tab).
  const selRec = recips.find((r) => r.id === selRecId) || recips[0];
  const selWalk = (selRec && (selRec.status === 'answered' || selRec.status === 'live')) ? bsaIvrWalk(ivrNodes, parseInt(String(selRec.id).replace(/\D/g, ''), 10) || 0) : [];
  const retryEnabled = recips.some((r) => r.attempts.length > 1);
  const aiSummary = !sv.stats
    ? (txn.status === 'failed'
      ? `“${txn.templateName}” failed before processing — the system aborted due to insufficient balance, so no calls were placed and nothing was charged. The figures shown are an estimate for ${(txn.plannedCount || 0).toLocaleString()} planned recipients.`
      : `“${txn.templateName}” is scheduled for ${txn.scheduledAt || 'a later time'} and hasn’t been dialed yet, so there are no call results to analyze. Ask me to estimate cost or review the IVR setup.`)
    : `Here’s a quick read on “${txn.templateName}”: ${stats.answeredRate}% of ${(txn.recipientsCount || 0).toLocaleString()} calls were answered and ${stats.failedRate}% failed, with ${stats.completionRate}% completing the IVR. The average answered call lasted ${stats.avgDuration}s. Ask me anything about these results.`;
  const bars = [
    { label: t.bsaAnswered || 'Answered', pct: stats.answeredRate, tone: 'va', tip: t.bsaAnsweredTip || 'Percentage of calls successfully answered by recipients.' },
    { label: t.bsaBusy || 'Busy', pct: stats.busyRate, tone: 'vp', tip: t.bsaBusyTip || 'Percentage of calls that encountered a busy signal.' },
    { label: t.bsaNoAnswer || 'No Answer', pct: stats.noAnswerRate, tone: 'vn', tip: t.bsaNoAnswerTip || 'Percentage of calls that rang but were not answered.' },
    { label: t.bsaFailed || 'Failed', pct: stats.failedRate, tone: 'vf', tip: t.bsaVFailedTip || 'Percentage of calls that failed to initiate due to technical errors.' },
  ];

  return (
    <div className="bsa-takeover bsa-voice-details">
      <div className="bsa-det-top">
        <div className="bsa-det-head">
          <button className="bsa-back-ic" onClick={onBack} aria-label={t.back || 'Back'}><IcArrowLeft size={18} stroke={2} /></button>
          <div className="bsa-det-titlewrap">
            <div className="bsa-det-titlerow">
              <h2 className="bsa-det-title">{txn.templateName}</h2>
              <BsaStatusPill status={txn.status} t={t} />
            </div>
            <div className="bsa-det-sub">
              <span className="bsa-det-subitem">{BSA_DICONS.cal} {txn.createdAt}</span>
              <span className="bsa-det-subdot" />
              <span className="bsa-det-subitem">{BSA_DICONS.tagSm} {txn.type}</span>
              <span className="bsa-det-subdot" />
              <span className="bsa-det-subitem bsa-det-id">{txn.id}</span>
            </div>
          </div>
        </div>
        <div className="bsa-det-exports">
          <button className="btn btn-secondary" onClick={() => pushToast(t.bsaExportedDetails || 'Exporting transaction details ✓')}><IcDownload size={14} stroke={1.8} /> {t.bsaExportDetails || 'Export Details'}</button>
          <button className="btn btn-primary" onClick={() => pushToast(t.bsaExportedStats || 'Exporting statistics ✓')}><IcDownload size={14} stroke={1.8} /> {t.bsaExportStats || 'Export Statistics'}</button>
        </div>
      </div>

      {txn.failReason && <div className={`bsa-fail-banner st-${txn.status}`}><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /><line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" /></svg> {txn.failReason}</div>}
      {txn.status === 'deleted' && <div className="bsa-del-banner"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polyline points="3 6 5 6 21 6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg> <span><strong>{t.bsaDelBannerH || 'Deleted'}</strong> — {t.bsaDelBanner || 'this scheduled transaction was deleted and will not be sent. No statistics or costs will be generated.'}</span></div>}
      {txn.status === 'scheduled' && <div className="bsa-sched-banner"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9" /><polyline points="12 7 12 12 15 14" /></svg> <span><strong>{t.bsaSchedBannerH || 'Scheduled'}</strong> — {(t.bsaSchedBanner || 'this transaction is set for')} {txn.scheduledAt || '—'}. {t.bsaSchedBannerV || 'No calls have been placed yet, so statistics, IVR analytics, and costs will appear here once it’s processed.'}</span></div>}
      {sv.processing && (<div className="bsa-progress-banner"><span className="bsa-progress-txt"><span className="bsa-progress-dot" /><strong>{t.bsaProcessingH || 'In Progress'}</strong> — {headRecipients.toLocaleString()} {t.bsaProcOf || 'of'} {plannedTotal.toLocaleString()} {t.bsaProcCalls || 'recipients dialed so far. The recipient count and cost update live as the transaction runs.'}</span><span className="bsa-progress-bar"><i style={{ width: (plannedTotal ? Math.round(headRecipients / plannedTotal * 100) : 0) + '%' }} /></span></div>)}

      <div className="bsa-kpis">
        <div className="bsa-kpi"><span className="bsa-kpi-ic">{BSA_DICONS.sender}</span><span className="bsa-kpi-txt"><span className="bsa-kpi-lbl">{t.bsaSenderId || 'Sender ID'}</span><strong className="bsa-kpi-val">{txn.sender}</strong></span></div>
        <div className="bsa-kpi"><span className="bsa-kpi-ic">{BSA_DICONS.recipients}</span><span className="bsa-kpi-txt"><span className="bsa-kpi-lbl">{t.bsaTotalRecipients || 'Total Recipients'}</span><strong className="bsa-kpi-val">{headRecipients.toLocaleString()}{showProgressOf ? <em className="bsa-kpi-sub">{t.bsaProcOf || 'of'} {plannedTotal.toLocaleString()}</em> : null}</strong></span></div>
        <div className="bsa-kpi"><span className="bsa-kpi-ic">{BSA_DICONS.group}</span><span className="bsa-kpi-txt"><span className="bsa-kpi-lbl">{t.bsaRecipientsWord || 'Recipients'}</span><strong className="bsa-kpi-val"><BsaRecipients txn={txn} /></strong></span></div>
      </div>

      {(<React.Fragment>
      <div className="bsa-det-mid">
        <div className="bsa-card bsa-overview">
          <div className="bsa-ov-head">
            <div>
              <div className="bsa-card-h">{t.bsaCallStats || 'Call Statistics'}</div>
              <div className="bsa-ov-sub">{t.bsaCallOutcomes || 'Outcome distribution across all recipients'}</div>
            </div>
            <div className="bsa-vstat-tiles">
              <div className="bsa-vstat-tile" title={t.bsaIvrCompletionTip || 'Percentage of answered calls where the recipient navigated through the entire IVR tree.'}><span className="bsa-vstat-val">{noData ? '—' : stats.completionRate + '%'}</span><span className="bsa-vstat-lbl">{t.bsaIvrCompletion || 'IVR Completion'}</span></div>
              <div className="bsa-vstat-tile" title={t.bsaAvgDurationTip || 'The average length of answered calls.'}><span className="bsa-vstat-val">{noData ? '—' : stats.avgDuration + 's'}</span><span className="bsa-vstat-lbl">{t.bsaAvgDuration || 'Avg. Duration'}</span></div>
            </div>
          </div>
          <div className="bsa-funnel bsa-voice-funnel">
            <div className="bsa-funnel-bars">
              {[100, 75, 50, 25, 0].map((v) => <span key={v} className="bsa-gl" style={{ bottom: v + '%' }}><em>{v}</em></span>)}
              {bars.map((b) => (
                <div key={b.label} className="bsa-fcol" title={b.tip}>
                  <div className={`bsa-fbar ${b.tone} ${noData ? 'is-empty' : ''}`} style={{ height: (noData ? 0 : Math.max(b.pct, 1.5)) + '%' }}><span className="bsa-fpct">{noData ? '—' : b.pct + '%'}</span></div>
                </div>
              ))}
            </div>
            <div className="bsa-funnel-x">
              {bars.map((b) => <div key={b.label} className="bsa-fxcol" title={b.tip}><span className="bsa-flabel">{b.label}</span></div>)}
            </div>
          </div>
        </div>
        <div className="bsa-card bsa-cost-summary bsa-voice-cost">
          <div className="bsa-card-h">{t.bsaCostBreakdown || 'Cost Breakdown'}</div>
          <div className="bsa-cost-grid bsa-cost-grid-4">
            <div className="bsa-cost-item"><span className="bsa-cost-ic ic-sent">{BSA_DICONS.coins}</span><div className="bsa-cost-meta"><div className="bsa-cost-val">{noData ? '—' : <React.Fragment><IcRiyal size={14} /> {stats.totalCost.toLocaleString()}</React.Fragment>}</div><div className="bsa-cost-lbl">{t.bsaTotalCallCost || 'Total Cost'}</div></div><BsaCostInfo tip={t.bsaTotalCostDesc || 'Total amount billed for this campaign across all answered calls.'} /></div>
            <div className="bsa-cost-item"><span className="bsa-cost-ic ic-avg">{BSA_DICONS.receipt}</span><div className="bsa-cost-meta"><div className="bsa-cost-val">{noData ? '—' : <React.Fragment><IcRiyal size={14} /> {stats.avgCost}</React.Fragment>}</div><div className="bsa-cost-lbl">{t.bsaAvgCost || 'Average Cost'}</div></div><BsaCostInfo tip={t.bsaAvgCostDesc || 'Total cost divided by the number of answered calls.'} /></div>
            <div className="bsa-cost-item"><span className="bsa-cost-ic ic-dur"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9" /><polyline points="12 7 12 12 15 14" /></svg></span><div className="bsa-cost-meta"><div className="bsa-cost-val">{noData ? '—' : stats.totalSec.toLocaleString() + 's'}</div><div className="bsa-cost-lbl">{t.bsaTotalSec || 'Total Seconds'}</div></div><BsaCostInfo tip={t.bsaTotalSecDesc || 'Combined talk time across all answered calls.'} /></div>
            <div className="bsa-cost-item"><span className="bsa-cost-ic ic-dur"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9" /><polyline points="12 7 12 12 15 14" /></svg></span><div className="bsa-cost-meta"><div className="bsa-cost-val">{noData ? '—' : stats.avgDuration + 's'}</div><div className="bsa-cost-lbl">{t.bsaAvgSec || 'Average Seconds'}</div></div><BsaCostInfo tip={t.bsaAvgSecDesc || 'Average duration of an answered call.'} /></div>
          </div>
          <div className="bsa-cbreak">
            <div className="bsa-cbreak-group bsa-dest-group">
              <div className="bsa-cbreak-h">{t.bsaByDest || 'By destination'}</div>
              {noData ? <div className="bsa-cbreak-empty">—</div> : (() => {
                const segs = stats.byDest.map((d, i) => ({ ...d, color: BSA_DEST_COLORS[i % BSA_DEST_COLORS.length] }));
                const dtot = segs.reduce((s, d) => s + d.v, 0) || 1;
                return (
                  <div className="bsa-dest-chart">
                    <BsaDonut data={segs} total={dtot} />
                    <ul className="bsa-dest-list">
                      {segs.map((d) => <li key={d.k} className="bsa-dest-row"><span className="bsa-dest-dot" style={{ background: d.color }} /><span className="bsa-dest-name">{d.k}</span><span className="bsa-dest-val"><IcRiyal size={11} /> {d.v.toLocaleString()}</span><span className="bsa-dest-pct">{Math.round(d.v / dtot * 100)}%</span></li>)}
                    </ul>
                  </div>
                );
              })()}
            </div>
            <div className="bsa-cbreak-group">
              <div className="bsa-cbreak-h">{t.bsaByAttempt || 'By retry attempt'}</div>
              {stats.byAttempt.map((v, ix) => { const p = stats.totalCost ? Math.round((v / stats.totalCost) * 100 * costProg) : 0; return (
                <div key={ix} className="bsa-cbreak-item">
                  <div className="bsa-cbreak-item-top"><span className="bsa-cbreak-label">{(t.bsaAttempt || 'Attempt')} {ix + 1}</span><strong className="bsa-cbreak-val">{noData ? '—' : v.toLocaleString()}</strong></div>
                  <div className="bsa-cbreak-bar-row"><span className="bsa-cbreak-bar"><i style={{ width: (noData ? 0 : p) + '%' }} /></span><span className="bsa-cbreak-pct">{noData ? '—' : p + '%'}</span></div>
                </div>
              ); })}
            </div>
          </div>
        </div>
      </div>

      </React.Fragment>)}

      <div className="bsa-det-bottom bsa-vdet-bottom">
        <div className="bsa-card bsa-recip-card bsa-vrecip-card">
          <div className="bsa-card-h bsa-recip-h">{t.bsaRecipientsDetails || 'Recipients Details'}<button className="bsa-filter-chip" onClick={() => pushToast(t.bsaFilterOpened || 'Filter')}><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" /></svg> {t.filter || 'Filter'}</button></div>
          <div className="table-scroll">
            <table className="bsa-table bsa-recip-table bsa-vrecip-table bsa-vrec-selectable">
              <thead><tr>
                <th className="bsa-exp-th" aria-hidden="true"></th>
                <th>{t.bsaColRecipientNum || 'Recipient Number'}</th>
                <th>{t.bsaColStatus || 'Status'}</th>
                <th>{t.bsaColAttempts || 'Attempts'}</th>
                <th>{t.bsaColStatusDate || 'Status Date'}</th>
                <th>{t.bsaColDuration || 'Duration'}</th>
                <th className="bsa-col-action">{t.bsaColActions || 'Actions'}</th>
              </tr></thead>
              <tbody>
                {recips.slice((recPage - 1) * recPageSize, recPage * recPageSize).map((r) => {
                  const rs = BSA_VSTATUS[r.status] || BSA_VSTATUS.pending;
                  const menuOpen = openMenuId === r.id;
                  const multi = r.attempts.length > 1;
                  const isExp = expandId === r.id;
                  return (
                    <React.Fragment key={r.id}>
                    <tr className={`bsa-vrec-row has-attempts ${isExp ? 'is-expanded' : ''} ${selRec && selRec.id === r.id ? 'bsa-recip-sel' : ''}`} onClick={() => setSelRecId(r.id)}>
                      <td className="bsa-exp-cell"><button type="button" className={`bsa-exp-caret ${isExp ? 'on' : ''}`} onClick={(e) => { e.stopPropagation(); setExpandId(isExp ? null : r.id); }} aria-label={isExp ? 'Collapse attempts' : 'Expand attempts'} aria-expanded={isExp}><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6" /></svg></button></td>
                      <td className="bsa-sender">{r.number}</td>
                      <td><span className={`bsa-vstatus ${rs.cls}`}>{rs.label}</span></td>
                      <td><span className={`bsa-attempt-badge ${multi ? 'multi' : ''}`}>{r.attempts.length}</span></td>
                      <td><BsaDateCell stamp={r.statusDate} /></td>
                      <td className="bsa-muted">{r.durSec ? r.duration : '—'}</td>
                      <td className="bsa-col-action" style={{ position: 'relative' }} onClick={(e) => e.stopPropagation()}>
                        <button className="row-action-btn bsa-row-action-btn" onClick={(e) => { e.stopPropagation(); setOpenMenuId(menuOpen ? null : r.id); }}><IcMore size={16} /></button>
                        {menuOpen && <BsaRowMenu items={[
                          { id: 'conv', label: t.bsaConversation || 'Conversation', icon: <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /></svg>, disabled: (txn.status === 'scheduled' || txn.status === 'deleted'), disabledHint: t.bsaNoConvYet || 'No conversation for scheduled or deleted messages', onClick: () => { setOpenMenuId(null); onConversation(txn, r); } },
                        ]} onClose={() => setOpenMenuId(null)} />}
                      </td>
                    </tr>
                    {isExp ? (
                      <tr className="bsa-attempt-row">
                        <td colSpan={7}>
                          <div className="bsa-attempt-wrap">
                            <div className="bsa-attempt-h">{t.bsaAttemptsBreakdown || 'Delivery attempts'}</div>
                            <table className="bsa-attempt-table">
                              <thead><tr>
                                <th>{t.bsaColAttempt || 'Attempt'}</th>
                                <th>{t.bsaColStatus || 'Status'}</th>
                                <th>{t.bsaColTime || 'Time'}</th>
                                <th>{t.bsaColWait || 'Wait'}</th>
                                <th>{t.bsaColCost || 'Cost'}</th>
                              </tr></thead>
                              <tbody>
                                {r.attempts.map((a) => { const as = BSA_VSTATUS[a.status] || BSA_VSTATUS.pending; return (
                                  <tr key={a.n}>
                                    <td><span className="bsa-attempt-num">{a.n}</span></td>
                                    <td><span className={`bsa-vstatus ${as.cls}`}>{as.label}</span></td>
                                    <td>{a.at ? <BsaDateCell stamp={a.at} /> : <span className="bsa-muted">—</span>}</td>
                                    <td className="bsa-muted">{a.wait ? a.wait + ' min' : '—'}</td>
                                    <td>{a.cost ? <span className="bsa-cost"><IcRiyal size={12} /> {a.cost.toLocaleString()}</span> : <span className="bsa-muted">—</span>}</td>
                                  </tr>
                                ); })}
                              </tbody>
                            </table>
                          </div>
                        </td>
                      </tr>
                    ) : null}
                    </React.Fragment>
                  );
                })}
                {recips.length === 0 && <tr><td colSpan={7} className="bsa-empty">{txn.status === 'failed' ? (t.bsaFailedNoCalls || 'No calls were placed — the transaction was aborted before any records were processed.') : (t.bsaNoRecipients || 'No recipients to show.')}</td></tr>}
              </tbody>
            </table>
          </div>
          {window.TablePagination && <window.TablePagination total={recips.length} page={recPage} pageSize={recPageSize} onPageChange={setRecPage} onPageSizeChange={(n) => { setRecPageSize(n); setRecPage(1); }} t={t} />}
        </div>

        <div className="bsa-card bsa-preview-card bsa-vcanvas-card">
          <div className="bsa-card-h bsa-det-preview-h">{t.bsaIvrCanvasPreview || 'IVR Canvas Preview'}{selRec ? <span className="bsa-det-preview-rn">{selRec.number}</span> : null}</div>
          <div className="bsa-vcanvas-hint">{t.bsaCanvasPlayHint || 'Tap any node to play its prompt'}</div>
          {window.TplIvrStep2 && ivrSeed
            ? <div className="ivr-edit-canvas is-readonly td-ivr-flow bsa-ivr-flow bsa-vcanvas-flow"><window.TplIvrStep2 key={ivrSeed.id} data={ivrSeed} setData={() => {}} t={t} readOnly={true} inspect={true} /></div>
            : <div className="bsa-var-empty">{t.bsaNoIvrFlow || 'No IVR flow available for this template.'}</div>}
          {selRec ? (
            <div className="bsa-vcanvas-detail">
              <div className="bsa-vrec-block">
                <div className="bsa-vrec-bh">{t.bsaCallDescription || 'Call description'}</div>
                <p className="bsa-vrec-desc">{bsaRecipDesc(selRec, selWalk)}</p>
              </div>
              {selWalk.length ? (
                <div className="bsa-vrec-block">
                  <div className="bsa-vrec-bh">{t.bsaTranscript || 'Transcript'}</div>
                  <div className="bsa-vrec-tscript">
                    {selWalk.map((s, i) => (
                      <div key={i} className="bsa-vrec-tline">
                        <div className="bsa-vrec-tnode"><span className="bsa-vconv-sys">{t.bsaIvrSystem || 'IVR'}</span> {s.node.label}</div>
                        <div className="bsa-vrec-ttext">{bsaNodeTranscript(s.node, ivrVars)}</div>
                        {s.pressed ? <div className="bsa-vrec-tkey"><span className="bsa-vrec-tkeycap">{s.pressed.key}</span> {(t.bsaIvrPressed || 'Pressed')} — {s.pressed.label}</div> : null}
                      </div>
                    ))}
                  </div>
                </div>
              ) : null}
            </div>
          ) : null}
        </div>
      </div>

      {previewRecip && <BsaVoicePreview recip={previewRecip} ivrSeed={ivrSeed} t={t} pushToast={pushToast} onClose={() => setPreviewId(null)} onConversation={() => onConversation(txn, previewRecip)} />}
      {askAI && <BsaAskAI txn={txn} summary={aiSummary} suggestions={[t.bsaAiQ1 || 'Summarize this transaction', t.bsaAiVQ2 || 'Why did some calls fail?', t.bsaAiVQ3 || 'How can I improve the answered rate?']} t={t} onClose={() => setAskAI(false)} />}
    </div>
  );
};

// ===== Conversation page (WhatsApp-style) =====
// Delivery ticks: single (sent) / double grey (delivered) / double blue (read).
const BsaTicks = ({ status }) => {
  if (!status || status === 'pending' || status === 'sending') return null;
  const dbl = status === 'read' || status === 'delivered';
  const color = status === 'read' ? '#34b7f1' : 'rgba(0,0,0,.4)';
  return (
    <svg className="bsa-ticks" width="16" height="11" viewBox="0 0 17 11" fill="none" stroke={color} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M1.5 6 L4.4 8.9 L9.3 3" />
      {dbl && <path d="M8.4 6 L11.3 8.9 L16.2 3" />}
    </svg>
  );
};
// Neutral silhouette avatar in a tinted circle.
const BsaAvatar = ({ tone, size }) => (
  <span className={`bsa-cv-ava ${tone || ''}`} style={size ? { width: size, height: size } : null}>
    <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="8.2" r="3.8" /><path d="M5 20c0-3.9 3.1-6.3 7-6.3s7 2.4 7 6.3z" /></svg>
  </span>
);
// Conversation icon set.
const BsaCvIc = {
  back: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="19" y1="12" x2="5" y2="12" /><polyline points="12 19 5 12 12 5" /></svg>,
  chevron: <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6" /></svg>,
  search: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7" /><line x1="21" y1="21" x2="16.65" y2="16.65" /></svg>,
  info: <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><line x1="12" y1="16" x2="12" y2="12" /><line x1="12" y1="8" x2="12.01" y2="8" /></svg>,
  reply: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 17 4 12 9 7" /><path d="M20 18v-2a4 4 0 0 0-4-4H4" /></svg>,
  forward: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 17 20 12 15 7" /><path d="M4 18v-2a4 4 0 0 1 4-4h12" /></svg>,
  smile: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" y1="9" x2="9.01" y2="9" /><line x1="15" y1="9" x2="15.01" y2="9" /></svg>,
  clip: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" /></svg>,
  mic: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" /><path d="M19 10v2a7 7 0 0 1-14 0v-2" /><line x1="12" y1="19" x2="12" y2="23" /><line x1="8" y1="23" x2="16" y2="23" /></svg>,
  photo: <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.7" /><path d="m21 15-5-5L5 21" /></svg>,
  doc: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /></svg>,
  play: <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><path d="M7 4.5v15l13-7.5z" /></svg>,
  stop: <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2.5" /></svg>,
  send: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><line x1="22" y1="2" x2="11" y2="13" /><polygon points="22 2 15 22 11 13 2 9 22 2" /></svg>,
  x: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>,
  trash: <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><polyline points="3 6 5 6 21 6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg>,
  tpl: <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /></svg>,
  tplAdd: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /><line x1="12" y1="8.5" x2="12" y2="13.5" /><line x1="9.5" y1="11" x2="14.5" y2="11" /></svg>,
  warn: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /><line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" /></svg>,
};
const BSA_REACTIONS = ['👍', '❤️', '😂', '😮', '🙏', '🔥'];
const BSA_EMOJIS = ['😀', '😁', '😂', '😍', '🤝', '👍', '🙏', '🎉', '🚀', '❤️', '🔥', '✅', '📄', '📎', '⭐', '💬'];
const BSA_WAVE = [5, 9, 14, 8, 17, 11, 6, 13, 18, 10, 7, 15, 9, 12, 6, 16, 8, 11, 14, 7];
// Playable WhatsApp voice note: the waveform fills + the timer counts up while it plays (play/pause, auto-stops at the end).
const bsaDurToSec = (s) => { const p = String(s == null ? '' : s).split(':').map((x) => parseInt(x, 10) || 0); return p.length >= 2 ? p[0] * 60 + p[1] : (p[0] || 0); };
const BsaCvVoiceNote = ({ dur, tone }) => {
  const total = Math.max(1, bsaDurToSec(dur));
  const [playing, setPlaying] = useStateBsa(false);
  const [elapsed, setElapsed] = useStateBsa(0);
  // Wall-clock playback held in a ref: stays real-time, survives parent re-renders, and keeps
  // advancing correctly even when the browser throttles background-tab timers.
  const timerRef = useRefBsa(null);
  const startRef = useRefBsa(0);
  const totalRef = useRefBsa(total); totalRef.current = total;
  const stop = () => { if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } };
  const toggle = () => {
    if (timerRef.current) { stop(); setPlaying(false); return; }
    setPlaying(true);
    startRef.current = performance.now() - elapsed * 1000; // resume from where it was paused
    timerRef.current = setInterval(() => {
      const e = (performance.now() - startRef.current) / 1000;
      if (e >= totalRef.current) { stop(); setPlaying(false); setElapsed(0); return; }
      setElapsed(e);
    }, 90);
  };
  useEffectBsa(() => stop, []); // clean up only on unmount
  const frac = total ? Math.min(1, elapsed / total) : 0;
  const showSec = Math.round((playing || elapsed > 0) ? elapsed : total);
  const fmt = (s) => Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0');
  return (
    <div className="bsa-cv-voice">
      <button type="button" className={`bsa-cv-vplay ${playing ? 'is-playing' : ''}`} onClick={toggle} aria-label={playing ? 'Pause' : 'Play'}>
        {playing
          ? <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="5" width="4" height="14" rx="1" /><rect x="14" y="5" width="4" height="14" rx="1" /></svg>
          : BsaCvIc.play}
      </button>
      <span className="bsa-cv-wave">{BSA_WAVE.map((h, i) => <i key={i} className={(i + 0.5) / BSA_WAVE.length <= frac ? 'on' : ''} style={{ height: h + 'px' }} />)}</span>
      <span className="bsa-cv-vdur">{fmt(showSec)}</span>
      <BsaAvatar tone={tone} size={22} />
    </div>
  );
};

// ===== Send-a-new-template pop-up (opened from inside a conversation) =====
// Shows the same fields as the Send WhatsApp form, but locked to this transaction's audience:
// only the Template Name is editable, and — once a template with variables is chosen — its
// variable-mapping fields (per the already-selected contact groups / recipients) become editable.
const BsaSendTplModal = ({ txn, recipient, t, pushToast, onClose }) => {
  const templates = window.bsaWaTemplates || [];
  const groups = window.bsaContactGroups || [];
  const [tplId, setTplId] = useStateBsa('');
  const [groupCfg, setGroupCfg] = useStateBsa({}); // { [gid]: { mobileCol, varMap: { [var]: column } } }
  const [manualVars, setManualVars] = useStateBsa({}); // { [idx]: { [var]: value } }

  const tpl = templates.find((x) => x.id === tplId);
  const vars = (window.bsaTemplateVars || {})[tplId] || [];
  const cat = tpl ? tpl.type : (txn.type || '');
  const lang = tpl ? (tpl.language || '') : (txn.language || '');
  // Audience is locked to whatever this transaction already targeted.
  const selGroups = [...new Set(txn.recipientsList || [])].map((name) => groups.find((g) => g.name === name)).filter(Boolean);
  const manualNums = (txn.manualRecipients && txn.manualRecipients.length) ? txn.manualRecipients
    : (!selGroups.length && recipient && recipient.number ? [recipient.number] : []);

  useEffectBsa(() => {
    const next = {};
    selGroups.forEach((g) => { const cols = g.columns || []; const varMap = {}; vars.forEach((v) => { varMap[v] = bsaAutoCol(cols, v); }); next[g.id] = { mobileCol: bsaAutoMobile(cols), varMap }; });
    setGroupCfg(next); setManualVars({});
  }, [tplId]);
  useEffectBsa(() => { const f = (e) => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', f); return () => document.removeEventListener('keydown', f); }, []);

  const setGroupVar = (gid, v, col) => setGroupCfg((c) => ({ ...c, [gid]: { ...(c[gid] || {}), varMap: { ...((c[gid] || {}).varMap || {}), [v]: col } } }));
  const setManualVar = (i, v, val) => setManualVars((mv) => ({ ...mv, [i]: { ...(mv[i] || {}), [v]: val } }));
  const varsMapped = selGroups.every((g) => vars.every((v) => !!((groupCfg[g.id] || {}).varMap || {})[v]))
    && manualNums.every((_, i) => vars.every((v) => !!String((manualVars[i] || {})[v] || '').trim()));
  const canSend = !!tplId && varsMapped;
  // Live phone preview of the chosen template (values pulled from the mapping, else sample data).
  const previewBody = (window.bsaWaTemplateBodies || {})[tplId];
  const previewVals = (() => {
    const o = {}; if (!vars.length) return o;
    if (selGroups.length) { const g = selGroups[0]; const cfg = groupCfg[g.id] || {}; const row0 = bsaGroupRows(g)[0] || {}; vars.forEach((v) => { const col = (cfg.varMap || {})[v]; o[v] = (col && row0[col]) || bsaSampleVal(v); }); return o; }
    vars.forEach((v) => { o[v] = String((manualVars[0] || {})[v] || '').trim() || bsaSampleVal(v); }); return o;
  })();

  const lock = <svg className="bsa-lock-ic" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></svg>;
  const lockedSel = (val) => <BsaSelect value={val || ''} disabled placeholder="—" options={val ? [{ id: val, label: val }] : []} onChange={() => {}} />;

  return ReactDOM.createPortal(
    <div className="bsa-modal-overlay" onMouseDown={onClose}>
      <div className="bsa-modal bsa-sendtpl-modal" onMouseDown={(e) => e.stopPropagation()}>
        <div className="bsa-modal-head">
          <div className="bsa-modal-titlewrap"><div className="bsa-modal-title">{t.bsaSendNewTpl || 'Send new message template'}</div><div className="bsa-modal-sub">{recipient && recipient.number ? recipient.number : txn.templateName}</div></div>
          <button className="bsa-modal-close" onClick={onClose} aria-label={t.close || 'Close'}><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg></button>
        </div>
        <div className="bsa-modal-body">
          {/* Message — everything locked except Template Name */}
          <div className="bsa-card">
            <div className="bsa-card-h">{t.bsaMessageStep || 'Message'}</div>
            <div className="bsa-tpl-grid">
              <label className="bsa-field"><span>{t.bsaSenderId || 'Sender ID'}</span>{lockedSel(txn.sender)}</label>
              <label className="bsa-field"><span>{t.bsaCategory || 'Category'}</span>{lockedSel(cat)}</label>
              <label className="bsa-field"><span>{t.bsaLanguage || 'Language'}</span>{lockedSel(lang)}</label>
              <label className="bsa-field"><span>{t.bsaTemplateName || 'Template Name'}</span><BsaSelect value={tplId} placeholder={t.bsaChooseTemplate || 'Choose Template'} options={templates.map((x) => ({ id: x.id, label: x.name }))} onChange={setTplId} /></label>
            </div>
          </div>
          {/* Recipients (variable mapping) + live phone Preview side by side */}
          <div className="bsa-sendtpl-lower">
          {/* Recipients — locked audience; variable mapping is the only editable part */}
          <div className="bsa-card">
            <div className="bsa-card-h">{t.bsaRecipientsHead || 'Recipients'}</div>
            {!tplId ? <div className="bsa-rec-locked">{lock} {t.bsaPickTplForVars || 'Choose a template name first — its variables appear here for mapping.'}</div> : (
              <React.Fragment>
                {selGroups.map((g) => {
                  const cols = g.columns || []; const cfg = groupCfg[g.id] || {}; const colOpts = cols.map((c) => ({ id: c, label: c }));
                  return (
                    <div key={g.id} className="bsa-group-block bsa-group-locked">
                      <div className="bsa-group-block-h"><span className="bsa-gb-name"><IcBuildingS size={15} /> {g.name} <em>{(g.count || 0).toLocaleString()} {t.bsaContactsWord || 'contacts'}</em></span><span className="bsa-gb-lockchip">{lock} {t.bsaLockedWord || 'Locked'}</span></div>
                      {vars.length ? (
                        <div className="bsa-gb-section">
                          <div className="bsa-gb-label">{t.bsaMatchColumns || 'Match your columns to the template'}</div>
                          <div className="bsa-map2-box">
                            <table className="bsa-map2-table">
                              <thead><tr><th>{t.bsaField || 'Field'}</th><th>{t.bsaColumnInFile || 'Column in your file'}</th></tr></thead>
                              <tbody>
                                <tr><td><span className="bsa-map2-field">{t.bsaDestination || 'Destination'}</span></td><td className="bsa-map2-cell">{lockedSel(cfg.mobileCol)}</td></tr>
                                {vars.map((v) => <tr key={v}><td><span className="bsa-map2-var">{`{{${v}}}`}</span> <em className="bsa-map2-req" aria-hidden="true">*</em></td><td className="bsa-map2-cell"><BsaSelect value={(cfg.varMap || {})[v] || ''} placeholder={t.bsaSelect || 'Select'} options={colOpts} onChange={(c) => setGroupVar(g.id, v, c)} /></td></tr>)}
                              </tbody>
                            </table>
                          </div>
                        </div>
                      ) : <div className="bsa-gb-novars">{t.bsaTplNoVars || 'This template has no variables to map.'}</div>}
                    </div>
                  );
                })}
                {manualNums.length > 0 && (
                  <div className="bsa-rec-sub">
                    <div className="bsa-rec-sub-h">{t.bsaManualHead || 'Manual Recipients'}</div>
                    {manualNums.map((num, i) => (
                      <div key={i} className="bsa-manual-card bsa-manual-locked">
                        <div className="bsa-manual-row">
                          <span className="bsa-manual-num">{lock} {num}</span>
                          {vars.map((v) => <input key={v} className="bsa-manual-var" value={(manualVars[i] || {})[v] || ''} placeholder={bsaPretty(v)} onChange={(e) => setManualVar(i, v, e.target.value)} />)}
                        </div>
                      </div>
                    ))}
                    {!vars.length && <div className="bsa-gb-novars">{t.bsaTplNoVars || 'This template has no variables to map.'}</div>}
                  </div>
                )}
              </React.Fragment>
            )}
          </div>
          {/* Live phone preview of the selected template */}
          <div className="bsa-card bsa-preview-card bsa-sendtpl-preview">
            <div className="bsa-card-h">{t.bsaPreview || 'Preview'}</div>
            <BsaPhonePreview body={previewBody} vars={previewVals} empty={!previewBody} />
          </div>
          </div>
        </div>
        <div className="bsa-modal-foot">
          <button className="btn btn-secondary" onClick={onClose}>{t.cancel || 'Cancel'}</button>
          <button className="btn btn-primary" disabled={!canSend} onClick={() => { pushToast((t.bsaTplSentToast || 'Template queued') + (tpl ? ' — ' + tpl.name : '')); onClose(); }}>{t.bsaSend || 'Send'}</button>
        </div>
      </div>
    </div>,
    document.body
  );
};

const BsaConversation = ({ txn, recipient, t, onBack, pushToast, onSendTemplate, stagedTemplate, onStagedConsumed }) => {
  const [tplModal, setTplModal] = useStateBsa(false);
  const [expired, setExpired] = useStateBsa(false);
  const [draft, setDraft] = useStateBsa('');
  const [msgs, setMsgs] = useStateBsa(() => {
    const base = (window.bsaSampleConversation || []).map((m) => ({ ...m }));
    // Seed the thread with the ACTUAL sent template message (as shown in the Preview) so the conversation is realistic.
    const tb = (window.bsaWaTemplateBodies || {})[txn.templateId];
    if (!tb) return base;
    const tplMsg = { id: 'tpl0', dir: 'out', org: 'Aramco-Marketing-Office Management', name: 'Jawad Lababneh', kind: 'template', title: bsaSubst(tb.title, {}), text: bsaSubst(tb.body, {}), footer: tb.footer, button: tb.button, time: '04:29 PM', status: 'read' };
    return [tplMsg, ...base];
  });
  const [replyTo, setReplyTo] = useStateBsa(null);
  const [reactFor, setReactFor] = useStateBsa(null);
  const [emojiOpen, setEmojiOpen] = useStateBsa(false);
  const [attachOpen, setAttachOpen] = useStateBsa(false);
  const [recording, setRecording] = useStateBsa(false);
  const [recStopped, setRecStopped] = useStateBsa(false); // recording stopped → preview (play + send/delete)
  const [recSec, setRecSec] = useStateBsa(0);
  const [infoMsg, setInfoMsg] = useStateBsa(null); // message whose info is mirrored in the left panel
  const [search, setSearch] = useStateBsa('');      // in-conversation search
  const [searchIdx, setSearchIdx] = useStateBsa(0); // index of the current match
  const bodyRef = useRefBsa(null);
  const idRef = useRefBsa(100);
  const number = (recipient && recipient.number) || txn.sender;
  const orgLabel = 'Aramco-Marketing-Office Management';
  // 24-hour support-window countdown (demo). Compact while there's plenty of time left; it enlarges only in the final hour (hours === 0).
  const winHours = 22, winMins = 30, winSecs = 15;

  useEffectBsa(() => {
    if (!recording) { setRecSec(0); setRecStopped(false); return; }
    if (recStopped) return; // paused at the recorded length
    const id = setInterval(() => setRecSec((s) => s + 1), 1000);
    return () => clearInterval(id);
  }, [recording, recStopped]);

  useEffectBsa(() => {
    const f = (e) => { if (!e.target.closest || !e.target.closest('.bsa-cv-pop, .bsa-cv-actbtn, .bsa-cv-tool')) { setReactFor(null); setEmojiOpen(false); setAttachOpen(false); } };
    document.addEventListener('mousedown', f);
    return () => document.removeEventListener('mousedown', f);
  }, []);

  useEffectBsa(() => { const b = bodyRef.current; if (b) b.scrollTop = b.scrollHeight; }, [msgs.length]);

  const mmss = (s) => Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0');
  const nowTime = () => { try { return new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } catch (e) { return ''; } };
  const summary = (m) => !m ? '' : m.kind === 'image' ? '📷 Photo' : m.kind === 'voice' ? '🎤 Voice message' : m.kind === 'doc' ? '📄 ' + (m.fileName || 'Document') : m.kind === 'template' ? (m.title || m.text || '') : ((m.emoji ? m.emoji + ' ' : '') + (m.text || ''));

  const pushOut = (extra) => {
    const rq = replyTo ? { from: replyTo.dir === 'out' ? (t.bsaYou || 'You') : number, text: summary(replyTo) } : null;
    setMsgs((arr) => [...arr, { id: 'sent-' + (++idRef.current), dir: 'out', org: orgLabel, name: 'Jawad Lababneh', time: nowTime(), status: 'sent', replyTo: rq, ...extra }]);
    setReplyTo(null);
  };
  const sendText = () => { if (!draft.trim()) return; pushOut({ text: draft.trim() }); setDraft(''); };
  // Send the template that was composed via "Send new message template" and staged for this recipient.
  const stagedBody = stagedTemplate ? (window.bsaWaTemplateBodies || {})[stagedTemplate.templateId] : null;
  const sendStagedTpl = () => {
    if (stagedBody) { const sv = stagedTemplate.vars || {}; pushOut({ kind: 'template', title: bsaSubst(stagedBody.title, sv), text: bsaSubst(stagedBody.body, sv), footer: stagedBody.footer, button: stagedBody.button }); }
    onStagedConsumed && onStagedConsumed();
    pushToast(t.bsaTplMsgSent || 'Template message sent ✓');
  };
  const sendVoice = () => { pushOut({ kind: 'voice', dur: mmss(Math.max(recSec, 1)) }); setRecording(false); pushToast(t.bsaVoiceSent || 'Voice message sent ✓'); };
  const attachPhoto = () => { pushOut({ kind: 'image', caption: '' }); setAttachOpen(false); pushToast(t.bsaPhotoSent || 'Photo sent ✓'); };
  const attachDoc = () => { pushOut({ kind: 'doc', fileName: 'Document.pdf', fileMeta: '· PDF · 1.2 MB' }); setAttachOpen(false); pushToast(t.bsaDocSent || 'Document sent ✓'); };
  const setReaction = (id, emo) => { setMsgs((arr) => arr.map((m) => m.id === id ? { ...m, reaction: m.reaction === emo ? null : emo } : m)); setReactFor(null); };

  const renderActions = (m, out) => {
    const infoBtn = (
      <button key="info" className={`bsa-cv-actbtn ${infoMsg && infoMsg.id === m.id ? 'on' : ''}`} title={t.bsaMessageInfo || 'Message info'} onClick={() => setInfoMsg((cur) => (cur && cur.id === m.id ? null : m))}>{BsaCvIc.info}</button>
    );
    const replyBtn = (
      <button key="reply" className="bsa-cv-actbtn primary" disabled={expired} title={out ? (t.forward || 'Forward') : (t.reply || 'Reply')} onClick={expired ? undefined : () => setReplyTo(m)}>{out ? BsaCvIc.forward : BsaCvIc.reply}</button>
    );
    const reactBtn = (
      <div key="react" className="bsa-cv-reactwrap">
        <button className="bsa-cv-actbtn" disabled={expired} title={t.bsaReact || 'React'} onClick={expired ? undefined : () => setReactFor((x) => x === m.id ? null : m.id)}>{BsaCvIc.smile}</button>
        {!expired && reactFor === m.id && (
          <div className={`bsa-cv-pop bsa-cv-reactpop ${out ? 'left' : 'right'}`}>
            {BSA_REACTIONS.map((e) => <button key={e} type="button" className={m.reaction === e ? 'on' : ''} onClick={() => setReaction(m.id, e)}>{e}</button>)}
          </div>
        )}
      </div>
    );
    // Mirror per side: info always nearest the bubble, react (emoji) always farthest.
    return (
      <div className={`bsa-cv-acts ${reactFor === m.id ? 'open' : ''}`}>
        {out ? [reactBtn, replyBtn, infoBtn] : [infoBtn, replyBtn, reactBtn]}
      </div>
    );
  };

  // ---- In-conversation search: match, highlight, and jump between hits ----
  const searchableText = (m) => [m.text, m.title, m.caption, m.fileName, m.footer, m.button].filter(Boolean).join(' ');
  const q = search.trim().toLowerCase();
  const matchIds = q ? msgs.filter((m) => searchableText(m).toLowerCase().includes(q)).map((m) => m.id) : [];
  const currentMatchId = matchIds.length ? matchIds[Math.min(searchIdx, matchIds.length - 1)] : null;
  const scrollToMsg = (id) => { const b = bodyRef.current; if (!b || !id) return; const el = [...b.querySelectorAll('.bsa-cv-msg')].find((x) => x.dataset.msgId === String(id)); if (el) el.scrollIntoView({ block: 'center', behavior: 'smooth' }); };
  const gotoMatch = (dir) => { if (!matchIds.length) return; const n = (searchIdx + dir + matchIds.length) % matchIds.length; setSearchIdx(n); scrollToMsg(matchIds[n]); };
  useEffectBsa(() => { setSearchIdx(0); if (q && matchIds.length) requestAnimationFrame(() => scrollToMsg(matchIds[0])); }, [search]);
  // Highlight query hits inside a text string (preserving line breaks + *bold* markers).
  const hlSeg = (str, kb) => {
    if (!q) return str;
    const lower = String(str).toLowerCase(); const out = []; let i = 0, idx;
    while ((idx = lower.indexOf(q, i)) !== -1) { if (idx > i) out.push(str.slice(i, idx)); out.push(<mark key={kb + '-' + idx} className="bsa-cv-hl">{str.slice(idx, idx + q.length)}</mark>); i = idx + q.length; }
    if (i < str.length) out.push(str.slice(i));
    return out;
  };
  const hl = (text) => String(text || '').split('\n').map((line, li) => (
    <React.Fragment key={li}>{li > 0 && <br />}{line.split(/(\*[^*]+\*)/g).map((seg, si) => seg.startsWith('*') && seg.endsWith('*') ? <strong key={si}>{hlSeg(seg.slice(1, -1), li + '-' + si)}</strong> : hlSeg(seg, li + '-' + si))}</React.Fragment>
  ));

  const renderBody = (m) => {
    if (m.kind === 'image') return (
      <div className="bsa-cv-media">
        <div className="bsa-cv-photo">{BsaCvIc.photo}</div>
        {m.caption ? <div className="bsa-cv-cap">{hl(m.caption)}</div> : null}
      </div>
    );
    if (m.kind === 'voice') return <BsaCvVoiceNote dur={m.dur} tone={m.dir === 'out' ? 'agent' : 'cust'} />;
    if (m.kind === 'doc') return (
      <div className="bsa-cv-doc">
        <div className="bsa-cv-doc-thumb"><span className="bsa-cv-doc-brand"><b>T2</b><i>Falcon Admin</i></span></div>
        <div className="bsa-cv-doc-file">
          <span className="bsa-cv-doc-pdf">PDF</span>
          <span className="bsa-cv-doc-meta"><strong>{hlSeg(m.fileName, 'fn')}</strong><em>{m.fileMeta}</em></span>
        </div>
      </div>
    );
    if (m.kind === 'template') return (
      <div className="bsa-cv-tplcard">
        {m.title ? <div className="bsa-cv-tpl-title">{hl(m.title)}</div> : null}
        {m.text ? <div className="bsa-cv-text">{hl(m.text)}</div> : null}
        {m.footer ? <div className="bsa-cv-tpl-footer">{hl(m.footer)}</div> : null}
        {m.button ? <div className="bsa-cv-tpl-btn">{m.button}</div> : null}
      </div>
    );
    return (
      <React.Fragment>
        {m.emoji ? <div className="bsa-cv-bigemoji">{m.emoji}</div> : null}
        {m.text ? <div className="bsa-cv-text">{hl(m.text)}</div> : null}
      </React.Fragment>
    );
  };

  const cdParts = (txn.createdAt || '').split('·');
  const cdDate = (cdParts[0] || txn.createdAt || '').trim();
  const cdTime = (cdParts[1] || '').trim();
  // Add minutes to a "HH:MM AM/PM" string, returning HH:MM:SS AM/PM (seconds shown throughout).
  const addMin = (timeStr, mins) => {
    const mt = /(\d{1,2}):(\d{2})\s*(AM|PM)/i.exec(timeStr || '');
    if (!mt) return timeStr || '';
    let h = parseInt(mt[1], 10) % 12; if ((mt[3] || '').toUpperCase() === 'PM') h += 12;
    const total = ((h * 60 + parseInt(mt[2], 10) + mins) % 1440 + 1440) % 1440;
    const hh = Math.floor(total / 60), mm = total % 60;
    let h12 = hh % 12; if (h12 === 0) h12 = 12;
    const ss = (total * 17 + 29) % 60;
    return `${String(h12).padStart(2, '0')}:${String(mm).padStart(2, '0')}:${String(ss).padStart(2, '0')} ${hh >= 12 ? 'PM' : 'AM'}`;
  };
  // Append deterministic seconds to a plain "HH:MM am/pm" stamp.
  const withSec = (timeStr) => { const mt = /(\d{1,2}):(\d{2})(:\d{2})?\s*(AM|PM)?/i.exec(timeStr || ''); if (!mt || mt[3]) return timeStr || ''; const ss = ((parseInt(mt[1], 10) * 60 + parseInt(mt[2], 10)) * 17 + 29) % 60; return `${mt[1]}:${mt[2]}:${String(ss).padStart(2, '0')}${mt[4] ? ' ' + mt[4] : ''}`; };
  // Dates shown in the left green box: the selected message's lifecycle, else the transaction.
  const infoView = infoMsg
    ? { date: 'Oct 6, 2025', created: withSec(infoMsg.time), send: withSec(infoMsg.time), delivery: addMin(infoMsg.time, 1), read: addMin(infoMsg.time, 2) }
    : { date: cdDate, created: withSec(cdTime), send: withSec(cdTime), delivery: withSec(cdTime), read: withSec(cdTime) };
  const infoDate = (label, time, withTick) => (
    <div className="bsa-cv-info-drow">
      <div className="bsa-cv-info-dcol"><span className="bsa-cv-info-dlabel">{label}</span><strong className="bsa-cv-info-dval">{infoView.date}{time ? <em>{time}</em> : null}</strong></div>
      {withTick ? <BsaTicks status="read" /> : null}
    </div>
  );

  return (
    <div className="bsa-takeover bsa-conv2">
      {/* Top bar */}
      <div className="bsa-cv-topbar">
        <button className="bsa-cv-back" onClick={onBack}><span className="bsa-cv-backcircle">{BsaCvIc.back}</span> {t.back || 'Back'}</button>
        <div className="bsa-cv-topmeta">
          <div><span>{t.bsaMessageName || 'Message Name'}:</span> <strong>{txn.templateName}</strong></div>
          <div><span>{t.bsaCreatedDate || 'Created Date'}:</span> <strong>{txn.createdAt}</strong></div>
        </div>
        <div className="bsa-cv-topbar-r">
          <button className="bsa-cv-demo" onClick={() => setExpired((v) => !v)} title={t.bsaWindowToggleHint || 'Demo: toggle the 24-hour support window'}>{expired ? (t.bsaWindowOpen || 'Reopen window') : (t.bsaWindowExpire || 'Simulate expiry')}</button>
        </div>
      </div>

      {/* Two columns: Message Info + Conversation */}
      <div className="bsa-cv-grid">
        {/* Left — Message Info */}
        <div className="bsa-cv-info">
          <div className="bsa-cv-info-h">{t.bsaMessageInfo || 'Message Info'}</div>
          <div className="bsa-cv-info-row"><span>{t.bsaSender || 'Sender'}</span><strong>{infoMsg ? (infoMsg.dir === 'out' ? (infoMsg.name || (t.bsaYou || 'You')) : number) : txn.sender}</strong></div>
          <div className="bsa-cv-info-row"><span>{t.bsaType || 'Type'}</span><strong>{txn.type}</strong></div>
          <div className="bsa-cv-info-sep" />
          <div className="bsa-cv-info-block">
            {infoMsg ? <div className="bsa-cv-info-msg"><span className="bsa-cv-info-msg-from">{infoMsg.dir === 'out' ? (t.bsaYou || 'You') : number}</span><em>{bsaFmt(summary(infoMsg))}</em><button type="button" className="bsa-cv-info-clear" onClick={() => setInfoMsg(null)}>{t.bsaShowTxn || 'Show transaction'}</button></div> : null}
            {infoDate(t.bsaCreatedDate || 'Created Date', infoView.created)}
            {infoDate(t.bsaSendDate || 'Send Date', infoView.send)}
            {infoDate(t.bsaDeliveryDate || 'Delivery Date', infoView.delivery)}
            {infoDate(t.bsaReadDate || 'Read Date', infoView.read, true)}
          </div>
        </div>

        {/* Right — Conversation thread */}
        <div className="bsa-cv-thread">
          <div className="bsa-cv-head">
            <span className="bsa-cv-title">{t.bsaConversation || 'Conversation'}</span>
            <span className="bsa-cv-chev">{BsaCvIc.chevron}</span>
            <BsaAvatar tone="cust" size={40} />
            <span className="bsa-cv-recip">{t.bsaRecipientNumber || 'Recipient Number'}: <strong>{number}</strong></span>
            <div className={`bsa-cv-search ${search ? 'has-q' : ''} ${search && !matchIds.length ? 'no-match' : ''}`}>
              {BsaCvIc.search}
              <input value={search} onChange={(e) => setSearch(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); gotoMatch(e.shiftKey ? -1 : 1); } else if (e.key === 'Escape') { setSearch(''); } }} placeholder={t.bsaSearchHere || 'Search Here ....'} />
              {search && (
                <div className="bsa-cv-search-tools">
                  <span className="bsa-cv-search-count">{matchIds.length ? `${searchIdx + 1}/${matchIds.length}` : (t.bsaNoResults || 'No results')}</span>
                  <button type="button" className="bsa-cv-search-nav" disabled={!matchIds.length} onClick={() => gotoMatch(-1)} title={t.bsaPrevMatch || 'Previous'} aria-label={t.bsaPrevMatch || 'Previous match'}><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 15 12 9 18 15" /></svg></button>
                  <button type="button" className="bsa-cv-search-nav" disabled={!matchIds.length} onClick={() => gotoMatch(1)} title={t.bsaNextMatch || 'Next'} aria-label={t.bsaNextMatch || 'Next match'}><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 9 12 15 18 9" /></svg></button>
                  <button type="button" className="bsa-cv-search-x" onClick={() => setSearch('')} title={t.clear || 'Clear'} aria-label={t.clear || 'Clear'}><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg></button>
                </div>
              )}
            </div>
          </div>

          {/* Thread */}
          <div className="bsa-cv-body" ref={bodyRef}>
        <div className="bsa-cv-day"><span>Oct 6, 2025</span></div>
        {msgs.map((m) => {
          const out = m.dir === 'out';
          const isHit = q && matchIds.indexOf(m.id) !== -1;
          const isCurrent = isHit && m.id === currentMatchId;
          return (
            <div key={m.id} data-msg-id={m.id} className={`bsa-cv-msg ${m.dir} ${isHit ? 'is-hit' : ''} ${isCurrent ? 'is-current' : ''}`}>
              {!out && <BsaAvatar tone="cust" size={38} />}
              <div className="bsa-cv-stack">
                <div className="bsa-cv-sender">{out ? <React.Fragment>{m.org}- <strong>{m.name}</strong></React.Fragment> : <strong>{number}</strong>}</div>
                <div className="bsa-cv-line">
                  {out && renderActions(m, out)}
                  <div className={`bsa-cv-bubble k-${m.kind || 'text'}${m.emoji ? ' has-emoji' : ''}${m.reaction ? ' has-react' : ''}`}>
                    {m.replyTo ? <div className="bsa-cv-quote"><span>{m.replyTo.from}</span><em>{bsaFmt(m.replyTo.text)}</em></div> : null}
                    {renderBody(m)}
                    {m.reaction ? <span className="bsa-cv-reaction">{m.reaction}</span> : null}
                  </div>
                  {!out && renderActions(m, out)}
                </div>
                <div className="bsa-cv-time">{m.time}{out && <BsaTicks status={m.status} />}</div>
              </div>
              {out && <BsaAvatar tone="agent" size={38} />}
            </div>
          );
        })}
      </div>

      {/* Footer: support window + composer */}
      <div className="bsa-cv-foot">
        {!expired ? (
          <React.Fragment>
            <div className={`bsa-cv-window ${winHours < 1 ? 'is-final' : ''}`}>
              <div className="bsa-cv-clock">
                <span className="bsa-cv-unit"><b>{String(winHours).padStart(2, '0')}</b><em>{t.bsaHours || 'Hours'}</em></span>
                <span className="bsa-cv-colon">:</span>
                <span className="bsa-cv-unit"><b>{String(winMins).padStart(2, '0')}</b><em>{t.bsaMinutes || 'Minutes'}</em></span>
                <span className="bsa-cv-colon">:</span>
                <span className="bsa-cv-unit"><b>{String(winSecs).padStart(2, '0')}</b><em>{t.bsaSeconds || 'Seconds'}</em></span>
              </div>
              <span className="bsa-cv-window-lbl">{t.bsaWindowLeft || 'Time remaining for the Customer Support Window'}</span>
            </div>
            {replyTo ? (
              <div className="bsa-cv-replybar">
                <span className="bsa-cv-replybar-ic">{BsaCvIc.reply}</span>
                <div className="bsa-cv-replybar-q"><span>{replyTo.dir === 'out' ? (t.bsaYou || 'You') : number}</span><em>{bsaFmt(summary(replyTo))}</em></div>
                <button type="button" onClick={() => setReplyTo(null)} title={t.cancel || 'Cancel'}>{BsaCvIc.x}</button>
              </div>
            ) : null}
            {stagedTemplate && stagedBody ? (
              <div className="bsa-cv-staged">
                <div className="bsa-cv-staged-card">
                  <div className="bsa-cv-staged-lbl">{t.bsaTemplateReady || 'Template ready to send'}</div>
                  {stagedBody.title ? <div className="bsa-cv-tpl-title">{bsaFmt(bsaSubst(stagedBody.title, stagedTemplate.vars || {}))}</div> : null}
                  <div className="bsa-cv-text">{bsaFmt(bsaSubst(stagedBody.body, stagedTemplate.vars || {}))}</div>
                  {stagedBody.footer ? <div className="bsa-cv-tpl-footer">{stagedBody.footer}</div> : null}
                  {stagedBody.button ? <div className="bsa-cv-tpl-btn">{stagedBody.button}</div> : null}
                </div>
                <div className="bsa-cv-staged-acts">
                  <button type="button" className="bsa-cv-tool danger" title={t.bsaDiscard || 'Discard'} onClick={() => onStagedConsumed && onStagedConsumed()}>{BsaCvIc.x}</button>
                  <button type="button" className="btn btn-primary bsa-cv-send" onClick={sendStagedTpl}>{BsaCvIc.send} {t.bsaSendMsg || 'Send'}</button>
                </div>
              </div>
            ) : recording ? (
              <div className="bsa-cv-composer recording">
                <button className="bsa-cv-tool danger" onClick={() => { setRecording(false); setRecStopped(false); }} title={t.delete || 'Delete'}>{BsaCvIc.trash}</button>
                {recStopped ? (
                  <div className="bsa-cv-recprev">
                    <button type="button" className="bsa-cv-recplay" title={t.bsaPlay || 'Play'}>{BsaCvIc.play}</button>
                    <span className="bsa-cv-recwave">{BSA_WAVE.map((h, i) => <i key={i} style={{ height: h + 'px' }} />)}</span>
                    <span className="bsa-cv-rectime">{mmss(recSec)}</span>
                  </div>
                ) : (
                  <div className="bsa-cv-reclive"><span className="bsa-cv-recdot" /><span className="bsa-cv-rectime">{mmss(recSec)}</span><span className="bsa-cv-recwave">{BSA_WAVE.map((h, i) => <i key={i} style={{ height: h + 'px' }} />)}</span></div>
                )}
                {recStopped
                  ? <button className="btn btn-primary bsa-cv-send" onClick={sendVoice}>{BsaCvIc.send} {t.bsaSendMsg || 'Send'}</button>
                  : <button type="button" className="bsa-cv-recstop" onClick={() => setRecStopped(true)} title={t.bsaStopRecording || 'Stop recording'}>{BsaCvIc.stop}</button>}
              </div>
            ) : (
              <div className="bsa-cv-composer">
                <div className="bsa-cv-toolwrap">
                  <button className="bsa-cv-tool" title={t.bsaEmoji || 'Emoji'} onClick={() => { setEmojiOpen((o) => !o); setAttachOpen(false); }}>{BsaCvIc.smile}</button>
                  {emojiOpen ? <div className="bsa-cv-pop bsa-cv-emojipop">{BSA_EMOJIS.map((e) => <button key={e} type="button" onClick={() => setDraft((d) => d + e)}>{e}</button>)}</div> : null}
                </div>
                <div className="bsa-cv-toolwrap">
                  <button className="bsa-cv-tool" title={t.bsaAttach || 'Attach'} onClick={() => { setAttachOpen((o) => !o); setEmojiOpen(false); }}>{BsaCvIc.clip}</button>
                  {attachOpen ? <div className="bsa-cv-pop bsa-cv-attachpop"><button type="button" onClick={attachPhoto}>{BsaCvIc.photo} {t.bsaPhoto || 'Photo'}</button><button type="button" onClick={attachDoc}>{BsaCvIc.doc} {t.bsaDocument || 'Document'}</button></div> : null}
                </div>
                <input className="bsa-cv-input" value={draft} onChange={(e) => setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') sendText(); }} placeholder={t.bsaTypeMsg || 'Type message ...'} />
                <button className="bsa-cv-tool" title={t.bsaRecord || 'Record voice'} onClick={() => { setRecording(true); setEmojiOpen(false); setAttachOpen(false); }}>{BsaCvIc.mic}</button>
                <button className="bsa-cv-tool" title={t.bsaSendNewTpl || 'Send new message template'} onClick={() => onSendTemplate && onSendTemplate()}>{BsaCvIc.tplAdd}</button>
                <button className="btn btn-primary bsa-cv-send" onClick={sendText}>{BsaCvIc.send} {t.bsaSendMsg || 'Send'}</button>
              </div>
            )}
          </React.Fragment>
        ) : (
          <div className="bsa-cv-expired-row">
            <div className="bsa-cv-expired">
              <span className="bsa-cv-expired-ic">{BsaCvIc.warn}</span>
              <span>{t.bsaExpiredMsg || 'The 24-hour support window has expired — send a Template Message to continue.'}</span>
            </div>
            <button type="button" className="bsa-cv-expired-btn" onClick={() => onSendTemplate && onSendTemplate()}>{BsaCvIc.tplAdd} {t.bsaSendNewTpl || 'Send New Message Template'}</button>
          </div>
        )}
          </div>
        </div>
      </div>
      {tplModal && <BsaSendTplModal txn={txn} recipient={recipient} t={t} pushToast={pushToast} onClose={() => setTplModal(false)} />}
    </div>
  );
};

// ===== Falcon / Client perspective picker =====
const BsaViewPicker = ({ t, onPick }) => (
  <div className="tpl-picker-page">
    <div className="tpl-picker-card">
      <div className="tpl-picker-head">
        <div>
          <h2>{t.bsaPickerTitle || 'Whose channels & services are you reviewing?'}</h2>
          <p>{t.bsaPickerSub || 'Falcon admins manage across clients; client admins manage a single organization.'}</p>
        </div>
      </div>
      <div className="tpl-picker-grid">
        <button className="tpl-picker-tile falcon" onClick={() => onPick('falcon')}>
          <span className="ic-wrap"><T2Mark size={28} color="white" /></span>
          <span className="ttl">{t.bsaViewFalcon || 'View as Falcon'}</span>
          <span className="dsc">{t.bsaViewFalconDesc || 'Browse clients and manage their channels & services.'}</span>
          <span className="cta">{t.bsaViewFalcon || 'View as Falcon'} <IcChevronRight size={14} stroke={2} /></span>
        </button>
        <button className="tpl-picker-tile client" onClick={() => onPick('client')}>
          <span className="ic-wrap"><IcBuildingS size={26} stroke={1.7} /></span>
          <span className="ttl">{t.bsaViewClient || 'View as Client'}</span>
          <span className="dsc">{t.bsaViewClientDesc || 'Manage channels and services for a single organization.'}</span>
          <span className="cta">{t.bsaViewClient || 'View as Client'} <IcChevronRight size={14} stroke={2} /></span>
        </button>
      </div>
    </div>
  </div>
);

// ===== Falcon clients rail (clients only) =====
const BsaClientsRail = ({ tree, selectedId, onSelect, t }) => {
  const clients = (tree && tree.children) || [];
  return (
    <div className="clients-panel vs-clients-rail bsa-clients-rail">
      <div className="vs-rail-head">{t.falconClients || 'Clients'}</div>
      <div className="vs-rail-list">
        {clients.map((c) => (
          <button key={c.id} type="button" className={`vs-rail-item ${selectedId === c.id ? 'on' : ''}`} onClick={() => onSelect(c.id)}>
            <BrandLogo brand={c.brand} size={24} />
            <span className="vs-rail-name">{c.name}</span>
          </button>
        ))}
      </div>
    </div>
  );
};

// ===== "Viewing as" role selector (mirrors the Contact Groups / Wallet header chip) =====
const BsaViewingAs = ({ role, setRole, t }) => (
  <div className="wb-tb-viewing bsa-viewing-as">
    <span className="wb-tb-viewing-label">{(t.wbViewingAs || 'VIEWING AS').toUpperCase()}</span>
    <div className="wb-role-chip wb-tb-viewing-chip">
      <select className="wb-role-chip-select" value={role} onChange={(e) => setRole(e.target.value)}>
        <option value="account-owner">{t.wbRoleOwner || 'Account Owner'}</option>
        <option value="node-admin">{t.wbRoleNodeAdmin || 'Node Admin'}</option>
        <option value="normal-user">{t.wbRoleNormalUser || 'Normal User'}</option>
      </select>
    </div>
  </div>
);

// ===== Page =====
const BasicApplicationPage = ({ tree, selected, selectNode = () => {}, expanded, toggleExpand = () => {}, t = {}, pushToast = () => {} }) => {
  const clients = (tree && tree.children) || [];
  const [viewAs, setViewAs] = useStateBsa(null);          // null (picker) | 'falcon' | 'client'
  const [role, setRole] = useStateBsa('account-owner');   // Client view "Viewing as": account-owner | node-admin | normal-user
  const [channel, setChannel] = useStateBsa('whatsapp'); // whatsapp | voice
  const [mode, setMode] = useStateBsa('outbox');          // outbox | scheduled
  const [view, setView] = useStateBsa('list');            // list | compose | details | conversation
  const [activeTxn, setActiveTxn] = useStateBsa(null);
  const [editRow, setEditRow] = useStateBsa(null);
  const [prefill, setPrefill] = useStateBsa(null);         // recipient pre-filled when composing from a conversation
  const [convRecipient, setConvRecipient] = useStateBsa(null);
  const [composeFromConv, setComposeFromConv] = useStateBsa(false); // compose opened from a conversation → hide groups, "Back to conversation"
  const [stagedTpl, setStagedTpl] = useStateBsa(null);              // template composed from a conversation, staged in the thread to send
  const [openMenuId, setOpenMenuId] = useStateBsa(null);
  const [page, setPage] = useStateBsa(1);
  const [pageSize, setPageSize] = useStateBsa(10);
  const [confirm, setConfirm] = useStateBsa(null);        // { kind:'cancel'|'delete', row }

  // Filters
  const [search, setSearch] = useStateBsa('');
  const [typeFilter, setTypeFilter] = useStateBsa(null);

  // Transaction data (mutable so Cancel/Delete/Send update the grid live).
  const [waOutbox, setWaOutbox] = useStateBsa(() => (window.seedBsaWaOutbox || []).map((r) => ({ ...r })));
  const [waScheduled, setWaScheduled] = useStateBsa(() => (window.seedBsaWaScheduled || []).map((r) => ({ ...r })));
  const [voiceOutbox, setVoiceOutbox] = useStateBsa(() => (window.seedBsaVoiceOutbox || []).map((r) => ({ ...r })));
  const [voiceScheduled, setVoiceScheduled] = useStateBsa(() => (window.seedBsaVoiceScheduled || []).map((r) => ({ ...r })));

  useEffectBsa(() => { setPage(1); setOpenMenuId(null); setSearch(''); setTypeFilter(null); }, [channel, mode]);

  // Live "In Progress" ticker — every few seconds an in-progress transaction processes another
  // batch: its recipient count + cost climb toward target. It holds just below target so the
  // row stays "In Progress" in the list (it never auto-completes during the demo).
  useEffectBsa(() => {
    const advance = (list) => {
      let changed = false;
      const next = list.map((r) => {
        if (r.status !== 'in_progress') return r;
        const target = r.targetCount || r.recipientsCount || 0;
        const targetCost = r.targetCost || r.totalCost || 0;
        const cap = Math.max(1, Math.round(target * 0.7)); // hold around 70% → clearly still "In Progress"
        const cur = r.recipientsCount || 0;
        if (cur >= cap) return r;                   // reached the holding point → leave it processing
        const step = Math.max(1, Math.round(target * 0.06));
        const nc = Math.min(cap, cur + step);
        changed = true;
        return { ...r, recipientsCount: nc, totalCost: Math.round(targetCost * (nc / target)) };
      });
      return changed ? next : list; // same reference when nothing advanced → no re-render
    };
    const id = setInterval(() => { setWaOutbox(advance); setVoiceOutbox(advance); }, 4500);
    return () => clearInterval(id);
  }, []);

  const allRows = channel === 'whatsapp'
    ? (mode === 'outbox' ? waOutbox : waScheduled)
    : (mode === 'outbox' ? voiceOutbox : voiceScheduled);

  const rows = useMemoBsa(() => allRows.filter((r) =>
    (!typeFilter || typeFilter === 'all' || r.type === typeFilter) &&
    (!search.trim() || [r.id, r.sender, r.templateName, r.recipients, r.type].join(' ').toLowerCase().includes(search.trim().toLowerCase()))
  ), [allRows, typeFilter, search]);

  const sendLabel = channel === 'whatsapp' ? (t.bsaSendWa || 'Send Whatsapp Message') : (t.bsaSendVoice || 'Send Voice IVR Message');

  // --- actions ---
  const openDetails = (r) => { setActiveTxn(r); setView('details'); };
  const openCompose = () => { setEditRow(null); setPrefill(null); setComposeFromConv(false); setView('compose'); };
  const openEdit = (r) => { setEditRow(r); setPrefill(null); setComposeFromConv(false); setView('compose'); };
  // "Send new template" from a conversation → open the compose screen with that recipient pre-filled, no groups, "Back to conversation".
  const openComposeFor = (recipient) => { setEditRow(null); setPrefill(recipient ? { number: recipient.number } : null); setComposeFromConv(true); setView('compose'); };
  // From a (voice) conversation → jump straight to the send page for a chosen channel, recipient pre-filled.
  const openComposeChannel = (ch, recipient) => { setChannel(ch); setEditRow(null); setPrefill(recipient ? { number: recipient.number } : null); setComposeFromConv(false); setView('compose'); };
  const backToConversation = (tplData) => { setStagedTpl(tplData || null); setComposeFromConv(false); setView('conversation'); };
  const openConversation = (txn, recipient) => { setActiveTxn(txn); setConvRecipient(recipient); setView('conversation'); };
  const backToList = () => { setView('list'); setActiveTxn(null); setEditRow(null); setConvRecipient(null); };

  const onSent = (data) => {
    const tpl = [...(window.bsaWaTemplates || []), ...(window.bsaVoiceTemplates || [])].find((x) => x.id === data.templateId) || {};
    const stamp = '27-Mar-2025 · 03:' + String(10 + Math.floor((data.recipientsCount % 50))).slice(0, 2) + ' pm';
    // Record the real recipients: the selected contact group names + any manual numbers.
    const allGroups = window.bsaContactGroups || [];
    const groupNames = (data.groups || []).map((gid) => (allGroups.find((x) => x.id === gid) || {}).name).filter(Boolean);
    const manualNums = (data.manual || []).filter((mm) => mm.phone && String(mm.phone).trim()).map((mm) => `${bsaDial(mm.phoneCountry)} ${String(mm.phone).trim()}`);
    const txn = {
      id: 'TXN-' + (100600 + Math.floor(data.recipientsCount)), sender: data.sender, templateId: data.templateId,
      templateName: tpl.name, language: tpl.language || null, type: tpl.type, createdAt: stamp,
      recipientsCount: data.recipientsCount, totalCost: data.totalCost,
      recipients: groupNames[0] || manualNums[0] || '', recipientsList: groupNames, manualRecipients: manualNums,
      status: data.scheduled ? 'scheduled' : 'in_progress', ...(data.scheduled ? { scheduledAt: '20-Jul-2026 · 09:00 am' } : {}),
    };
    const setter = channel === 'whatsapp' ? (data.scheduled ? setWaScheduled : setWaOutbox) : (data.scheduled ? setVoiceScheduled : setVoiceOutbox);
    setter((list) => [txn, ...list]);
    setMode(data.scheduled ? 'scheduled' : 'outbox');
    backToList();
    pushToast(data.scheduled ? (t.bsaScheduledToast || 'Transaction scheduled ✓') : (t.bsaSentToast || 'Transaction submitted — now processing ✓'));
  };

  const applyCancel = (row) => {
    const isWa = channel === 'whatsapp';
    const setter = isWa ? setWaOutbox : setVoiceOutbox;
    const ratePerMsg = isWa ? 2.5 : 4;
    setConfirm(null);
    // Look up the LIVE row — the engine keeps running while the dialog is open, so it may have
    // already finished every recipient by the time the user confirms the cancellation.
    const list = isWa ? waOutbox : voiceOutbox;
    const live = list.find((x) => x.id === row.id) || row;
    const target = live.targetCount || live.recipientsCount || 0;
    const processed = live.recipientsCount || 0;
    if (live.status !== 'in_progress' || processed >= target) {
      // Race lost: the whole transaction was already processed before the cancel took effect.
      setter((l) => l.map((x) => x.id === row.id ? { ...x, status: 'completed', recipientsCount: target, totalCost: live.targetCost || x.totalCost } : x));
      pushToast(t.bsaCancelTooLate || `Too late to cancel — the transaction had already finished. All ${target.toLocaleString()} recipients were processed and it is marked Completed.`);
      return;
    }
    // Stopped mid-flight at the next batch edge: keep + charge only what was already sent.
    const sent = processed;
    setter((l) => l.map((x) => x.id === row.id ? {
      ...x, status: 'canceled', recipientsCount: sent, totalCost: live.totalCost || Math.round(sent * ratePerMsg),
      failReason: `Canceled by user — processing stopped at the next batch. ${sent} of ${target} recipients were sent before cancellation; the remaining ${target - sent} were not processed and were not charged.`,
    } : x));
    pushToast(t.bsaCanceledToast || `Transaction canceled while in progress — stopped after ${sent.toLocaleString()} of ${target.toLocaleString()} recipients; the remaining ${(target - sent).toLocaleString()} were not charged.`);
  };
  const applyDelete = (row) => {
    const setter = channel === 'whatsapp' ? setWaScheduled : setVoiceScheduled;
    setter((list) => list.map((x) => x.id === row.id ? { ...x, status: 'deleted' } : x));
    setConfirm(null);
    pushToast(t.bsaDeletedToast || 'Scheduled transaction deleted.');
  };

  const isFalcon = viewAs === 'falcon';
  // A Normal User has no hierarchy — the tree is hidden and they only see their own org's messages.
  const isNormalUser = viewAs === 'client' && role === 'normal-user';
  // Falcon → full hierarchy (all clients + departments); Client → this org's own subtree (its node as the root).
  const aramco = clients.find((c) => c.id === 'aramco') || clients[0] || { id: 'aramco', name: 'Aramco', type: 'client', brand: 'aramco', children: [] };
  const treeForView = isFalcon ? tree : ((window.findNode && window.findNode(tree, 'aramco')) || aramco);
  const selNode = (window.findNode && tree) ? window.findNode(tree, selected) : null;
  const headerNode = (selNode && selNode.type !== 'root') ? selNode : aramco;
  const clientName = (headerNode && headerNode.name) || 'Aramco';
  const switchPerspective = () => { setViewAs(null); setRole('account-owner'); setView('list'); setMode('outbox'); setChannel('whatsapp'); setOpenMenuId(null); };
  const changeRole = (r) => { setRole(r); setView('list'); setMode('outbox'); setOpenMenuId(null); };

  // ---- Perspective picker (landing) ----
  if (!viewAs) return <BsaViewPicker t={t} onPick={(v) => { setViewAs(v); if (v === 'client') selectNode('aramco'); }} />;

  // Resolve the open transaction against the live lists so an In-Progress detail page ticks too.
  const liveTxn = activeTxn ? (waOutbox.concat(waScheduled, voiceOutbox, voiceScheduled).find((x) => x.id === activeTxn.id) || activeTxn) : activeTxn;

  // ---- Full-page takeovers (Compose / Details / Conversation) ----
  if (view === 'compose') return <BsaCompose channel={channel} t={t} pushToast={pushToast} onCancel={composeFromConv ? () => backToConversation(null) : backToList} onSent={onSent} editRow={editRow} prefill={prefill} fromConversation={composeFromConv} onBackToConversation={backToConversation} />;
  if (view === 'details') return channel === 'voice'
    ? <BsaVoiceDetails txn={liveTxn} t={t} pushToast={pushToast} onBack={backToList} onConversation={openConversation} />
    : <BsaDetails txn={liveTxn} channel={channel} t={t} pushToast={pushToast} onBack={backToList} onConversation={openConversation} />;
  if (view === 'conversation') return channel === 'voice'
    ? <BsaVoiceConversation txn={activeTxn} recipient={convRecipient} t={t} pushToast={pushToast} onBack={() => { setView(activeTxn ? 'details' : 'list'); }} onSendWhatsapp={() => openComposeChannel('whatsapp', convRecipient)} onSendVoice={() => openComposeChannel('voice', convRecipient)} />
    : <BsaConversation txn={activeTxn} recipient={convRecipient} t={t} pushToast={pushToast} onBack={() => { setView(activeTxn ? 'details' : 'list'); }} onSendTemplate={() => openComposeFor(convRecipient)} stagedTemplate={stagedTpl} onStagedConsumed={() => setStagedTpl(null)} />;

  return (
    <div className={`templates-page basic-app-page ${isNormalUser ? 'bsa-no-tree' : ''}`}>
      {!isNormalUser && <window.TplOrgTree tree={treeForView} selected={selected} selectNode={selectNode} expanded={expanded} toggleExpand={toggleExpand} t={t} rootClickable={!isFalcon} hideSectionLabel={!isFalcon} hideMenus={true} />}
      <div className="content-panel bsa-main">
        {/* Channel tabs (WhatsApp | IVR Voice) + Switch perspective */}
        <div className="bsa-topbar">
          <div className="bsa-channel-tabs">
            <button className={`bsa-channel-tab ${channel === 'whatsapp' ? 'on' : ''}`} onClick={() => setChannel('whatsapp')}>{t.bsaWhatsapp || 'WhatsApp'}</button>
            <button className={`bsa-channel-tab ${channel === 'voice' ? 'on' : ''}`} onClick={() => setChannel('voice')}>{t.bsaVoice || 'IVR Voice'}</button>
          </div>
          <button className="btn btn-secondary bsa-switch-btn" onClick={switchPerspective}><IcArrowLeft size={12} stroke={1.8} /> {t.bsaSwitchPerspective || 'Switch perspective'}</button>
        </div>

        <div className="content-body bsa-body">
          {/* Header row: client (left) · Send (right) */}
          <div className="bsa-header-row">
            <div className="bsa-ch-left">
              {headerNode.type === 'client' ? <BrandLogo brand={headerNode.brand} size={28} /> : <span className="bsa-ch-avatar">{(clientName || 'A').slice(0, 1)}</span>}
              <span className="bsa-ch-name">{clientName}</span>
            </div>
            {!isFalcon && (
            <div className="bsa-ch-right">
              <BsaViewingAs role={role} setRole={changeRole} t={t} />
              {/* Only a Normal User sends messages; Account Owner / Node Admin oversee & review the hierarchy. */}
              {isNormalUser && (
              <button className="btn btn-primary bsa-send-btn" onClick={openCompose}>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="m22 2-7 20-4-9-9-4Z" /><path d="M22 2 11 13" /></svg>
                {sendLabel}
              </button>
              )}
            </div>
            )}
          </div>

          <BsaMessageGrid
            rows={rows} channel={channel} mode={mode} setMode={setMode} t={t}
            onDetails={openDetails} onEdit={openEdit}
            onCancel={(r) => setConfirm({ kind: 'cancel', row: r })}
            onDelete={(r) => setConfirm({ kind: 'delete', row: r })}
            openMenuId={openMenuId} setOpenMenuId={setOpenMenuId}
            page={page} pageSize={pageSize} setPage={setPage} setPageSize={setPageSize}
            search={search} setSearch={setSearch} typeFilter={typeFilter} setTypeFilter={setTypeFilter}
          />
        </div>
      </div>

      {confirm && confirm.kind === 'cancel' && (
        <BsaConfirm
          title={t.bsaCancelTitle || 'Cancel this transaction?'}
          body={`Transaction ${confirm.row.id} is currently In Progress (${(confirm.row.recipientsCount || 0).toLocaleString()} of ${(confirm.row.targetCount || confirm.row.recipientsCount || 0).toLocaleString()} recipients processed so far). Cancelling stops the engine at the next batch — recipients already sent are kept and charged; those not yet processed are excluded from the count and not charged. Note: if the engine finishes every recipient before your cancellation takes effect, the transaction completes normally — you'll be told which outcome occurred.`}
          confirmLabel={t.bsaCancelConfirm || 'Cancel Transaction'} danger
          onCancel={() => setConfirm(null)} onConfirm={() => applyCancel(confirm.row)}
        />
      )}
      {confirm && confirm.kind === 'delete' && (
        <BsaConfirm
          title={t.bsaDeleteTitle || 'Delete this scheduled transaction?'}
          body={(t.bsaDeleteBody || 'This scheduled transaction will be ignored and listed with status “Deleted”. This cannot be undone.')}
          confirmLabel={t.delete || 'Delete'} danger
          onCancel={() => setConfirm(null)} onConfirm={() => applyDelete(confirm.row)}
        />
      )}
    </div>
  );
};

window.BasicApplicationPage = BasicApplicationPage;
