document.addEventListener('DOMContentLoaded', () => {
      if (window.AOS && typeof window.AOS.init === 'function') {
        try { window.AOS.init(); } catch (err) { console.warn('AOS init failed', err); }
      }

      try {
        const senderForm = document.getElementById('senderForm');
        const recipientsInput = document.getElementById('recipients');
        const subjectInput = document.getElementById('subject');
        const bodyInput = document.getElementById('body');
        const variableListEl = document.getElementById('variableList');
        const variableNoteEl = document.getElementById('variableNote');
        const sendBtn = document.getElementById('sendBtn');
        const clearLogsBtn = document.getElementById('clearLogsBtn');
        const exportLogsBtn = document.getElementById('exportLogsBtn');
        const logArea = document.getElementById('logArea');
        const queuedCountEl = document.getElementById('queuedCount');
        const sentCountEl = document.getElementById('sentCount');
        const failureCountEl = document.getElementById('failureCount');
        const accountNotice = document.getElementById('accountNotice');
        const importRecipientsBtn = document.getElementById('importRecipientsBtn');
        const recipientFileInput = document.getElementById('recipientFileInput');
        const runtime = (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.sendMessage === 'function' && chrome.runtime.id) ? chrome.runtime : null;
        const extensionId = runtime?.id || null;
        const EMAIL_BRIDGE = Object.freeze({
          REQUEST: 'EXT_EMAIL_SENDER_REQUEST',
          RESPONSE: 'EXT_EMAIL_SENDER_RESPONSE',
          EVENT: 'EXT_EMAIL_SENDER_EVENT'
        });
        const EMAIL_CELL_PATTERN = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
        const SHEET_FILE_EXTENSIONS = new Set(['xlsx', 'xls', 'csv']);
        const POPUP_CLOSED_PATTERN = /Closed Gmail popup window/i;

        let currentJobId = null;
        let sending = false;
        let activeJobMeta = null;
        let keepAlivePort = null;
        let keepAliveTimer = null;
        let jobCompletionAnnounced = false;
        let lastKnownStats = { total: 0, sent: 0, failed: 0 };
        const recipientRecords = new Map();
        const variableRegistry = new Map();
        const NAME_ALIAS_KEYS = Object.freeze(['first-name', 'name', 'full-name', 'contact-name']);
        const KEEP_ALIVE_PING_MS = 20000;
        const defaultSendLabel = '<i class="fa-solid fa-paper-plane me-2"></i>Send Emails';

        const scheduleKeepAlivePing = () => {
          if (!keepAlivePort) return;
          window.clearTimeout(keepAliveTimer);
          keepAliveTimer = window.setTimeout(() => {
            try {
              keepAlivePort.postMessage({ type: 'ping', source: 'emailSenderUI', time: Date.now() });
            } catch (err) {
              console.warn('[EmailSender UI] keep-alive ping failed:', err?.message || err);
            }
            scheduleKeepAlivePing();
          }, KEEP_ALIVE_PING_MS);
        };

        const ensureKeepAlivePort = () => {
          if (!runtime || typeof runtime.connect !== 'function') return;
          if (keepAlivePort) return;
          try {
            const port = runtime.connect({ name: 'emailSenderKeepAlive' });
            keepAlivePort = port;
            scheduleKeepAlivePing();
            port.onDisconnect.addListener(() => {
              window.clearTimeout(keepAliveTimer);
              keepAliveTimer = null;
              keepAlivePort = null;
              setTimeout(ensureKeepAlivePort, 750);
            });
          } catch (err) {
            console.warn('[EmailSender UI] keep-alive connection failed:', err?.message || err);
          }
        };
        ensureKeepAlivePort();
        window.addEventListener('beforeunload', () => {
          try { keepAlivePort?.disconnect(); } catch (_) {}
          keepAlivePort = null;
          window.clearTimeout(keepAliveTimer);
          keepAliveTimer = null;
        });

        const handleConnectionError = (err) => {
          if (!err) return;
          if (/Receiving end does not exist/i.test(String(err))) {
            console.warn('[EmailSender UI] runtime connection missing, retrying keep-alive');
            ensureKeepAlivePort();
          }
        };

        function finalizeSendFlow({ resetForm = false } = {}) {
          currentJobId = null;
          setSendingState(false);
          if (resetForm) {
            senderForm?.reset?.();
            resetCounts();
          }
        }

        const handlePopupClosure = (stats, { fallback = false } = {}) => {
          if (!sending) return;
          const completedFromStats = (Number(stats?.sent) || 0) + (Number(stats?.failed) || 0);
          const totalFromStats = Number(stats?.total);

          if (!activeJobMeta) {
            const inferredTotal = !Number.isNaN(totalFromStats) && totalFromStats > 0
              ? totalFromStats
              : Number(queuedCountEl?.textContent) || 0;
            activeJobMeta = {
              total: inferredTotal,
              completed: 0
            };
          }

          if (!Number.isNaN(totalFromStats) && totalFromStats > 0) {
            activeJobMeta.total = totalFromStats;
          }

          if (!Number.isNaN(completedFromStats) && completedFromStats > 0) {
            activeJobMeta.completed = Math.max(activeJobMeta.completed || 0, completedFromStats);
          } else if (fallback) {
            activeJobMeta.completed = (activeJobMeta.completed || 0) + 1;
          }

          if (stats) {
            updateCounts(stats);
          }

          const total = activeJobMeta.total || 0;
          const completed = activeJobMeta.completed || 0;

          if (total > 0 && completed >= total) {
            maybeAnnounceJobCompletion(stats);
            finalizeSendFlow({ resetForm: true });
          } else {
            updateSendButtonLabel();
          }
        };

        function handleEmailRuntimeMessage(message) {
          if (!message || message.tool !== 'emailSender') return;
          console.log('[EmailSender UI] runtime message:', message);

          const jobMatch = !currentJobId || message.jobId === currentJobId;

          if (message.type === 'popupClosed' && jobMatch) {
            handlePopupClosure(message.stats);
            return;
          }

          if (message.type === 'progress' && jobMatch) {
            if (message.note) {
              appendLog(message.note);
              if (POPUP_CLOSED_PATTERN.test(message.note)) {
                handlePopupClosure(message.stats, { fallback: true });
              }
            }
            return;
          }

          if (message.type === 'complete' && jobMatch) {
            if (message.stats) updateCounts(message.stats);
            maybeAnnounceJobCompletion(message.stats);
            if (message.outcome) appendLog(message.outcome);
            finalizeSendFlow({ resetForm: true });
            return;
          }

          if (message.type === 'error' && jobMatch) {
            if (message.error) appendLog(`Error: ${message.error}`);
            if (message.stats) updateCounts(message.stats);
            finalizeSendFlow();
            return;
          }

          if (message.type === 'terminated' && jobMatch) {
            if (message.stats) updateCounts(message.stats);
            if (message.note) appendLog(message.note);
            if (sending) finalizeSendFlow();
          }
        }

        function createEmailBridge(onEvent) {
          if (typeof window === 'undefined' || typeof window.postMessage !== 'function') return null;

          let seq = 1;
          const pending = new Map();
          const queued = [];
          let queueTimer = null;
          let ready = false;
          const BRIDGE_QUEUE_TIMEOUT_MS = 1200;

          const flushQueue = (force = false) => {
            if (!queued.length) return;
            if (!force && !ready) return;
            const tasks = queued.splice(0);
            tasks.forEach((dispatch) => {
              try {
                dispatch();
              } catch (queueErr) {
                console.error('[EmailSender UI] bridge queue dispatch failed:', queueErr);
              }
            });
          };

          const markReady = () => {
            if (ready) return;
            ready = true;
            if (queueTimer) {
              window.clearTimeout(queueTimer);
              queueTimer = null;
            }
            flushQueue(true);
          };

          const enqueue = (dispatch) => {
            queued.push(dispatch);
            if (!queueTimer) {
              queueTimer = window.setTimeout(() => {
                queueTimer = null;
                flushQueue(true);
              }, BRIDGE_QUEUE_TIMEOUT_MS);
            }
          };

          const sendRequest = (message, callback) => {
            const requestId = `emailBridge-${Date.now()}-${seq++}`;
            const timeoutId = window.setTimeout(() => {
              const entry = pending.get(requestId);
              if (!entry) return;
              pending.delete(requestId);
              try {
                entry.callback({ response: null, error: 'bridge_timeout' });
              } catch (timeoutErr) {
                console.error('[EmailSender UI] bridge timeout callback error:', timeoutErr);
              }
            }, 15000);

            pending.set(requestId, {
              timeoutId,
              callback: (payload) => {
                window.clearTimeout(timeoutId);
                if (typeof callback === 'function') {
                  callback(payload);
                }
              }
            });

            try {
              window.postMessage({ type: EMAIL_BRIDGE.REQUEST, tool: 'emailSender', requestId, message }, '*');
            } catch (err) {
              const entry = pending.get(requestId);
              if (entry) {
                pending.delete(requestId);
                window.clearTimeout(timeoutId);
                entry.callback({ response: null, error: err?.message || String(err) });
              }
            }
          };

          const listener = (event) => {
            if (event.source !== window) return;
            const data = event.data || {};
            if (data.tool !== 'emailSender') return;

            if (data.type === EMAIL_BRIDGE.RESPONSE) {
              markReady();
              const entry = pending.get(data.requestId);
              if (!entry) return;
              pending.delete(data.requestId);
              window.clearTimeout(entry.timeoutId);
              try {
                entry.callback({
                  response: data.response ?? null,
                  error: data.error || null
                });
              } catch (callbackErr) {
                console.error('[EmailSender UI] bridge callback error:', callbackErr);
              }
              return;
            }

            if (data.type === EMAIL_BRIDGE.EVENT && data.message) {
              if (data.message.type === 'bridgeReady') {
                markReady();
                return;
              }
              markReady();
              try {
                onEvent?.(data.message);
              } catch (eventErr) {
                console.error('[EmailSender UI] bridge event handler error:', eventErr);
              }
            }
          };

          window.addEventListener('message', listener);

          return {
            send(message, callback) {
              if (!message || typeof message !== 'object') {
                if (typeof callback === 'function') {
                  callback({ response: null, error: 'invalid_message' });
                }
                return;
              }

              const dispatch = () => sendRequest(message, callback);
              if (!ready) {
                enqueue(dispatch);
                return;
              }
              dispatch();
            }
          };
        }

        const emailBridge = runtime ? null : createEmailBridge(handleEmailRuntimeMessage);

        const RETRYABLE_RUNTIME_ERROR = /Receiving end does not exist|Could not establish connection|The message port closed/i;

        const sendRuntimeMessage = (message, callback, attempt = 0) => {
          const dispatchResult = (payload) => {
            const errorText = payload?.error ? String(payload.error) : '';
            const shouldRetry = attempt === 0 && RETRYABLE_RUNTIME_ERROR.test(errorText);

            if (shouldRetry) {
              console.warn('[EmailSender UI] runtime message retry due to:', errorText);
              handleConnectionError(errorText);
              setTimeout(() => sendRuntimeMessage(message, callback, attempt + 1), 500);
              return;
            }

            handleConnectionError(errorText);
            if (typeof callback === 'function') {
              try {
                callback(payload);
              } catch (err) {
                console.error('[EmailSender UI] callback execution failed:', err);
              }
            }
          };

          if (runtime) {
            const finalize = (response) => {
              const err = (typeof chrome !== 'undefined' && chrome.runtime) ? chrome.runtime.lastError : null;
              dispatchResult({
                response,
                error: err ? (err.message || String(err)) : null
              });
            };

            try {
              if (extensionId) {
                runtime.sendMessage(extensionId, message, finalize);
              } else {
                runtime.sendMessage(message, finalize);
              }
            } catch (err) {
              dispatchResult({ response: null, error: err?.message || String(err) });
            }
            return;
          }

          if (emailBridge && typeof emailBridge.send === 'function') {
            emailBridge.send(message, dispatchResult);
            return;
          }

          console.warn('[EmailSender UI] Extension runtime unavailable; message not sent:', message);
          dispatchResult({ response: null, error: 'extension_unavailable' });
        };

      const ensureLogAreaReady = () => {
        if (!logArea) return;
        if (!logArea.dataset.initialized) {
          logArea.innerHTML = '';
          logArea.dataset.initialized = 'true';
        }
      };

      const appendLog = (message) => {
        if (!logArea) return;
        ensureLogAreaReady();
        const timestamp = new Date().toLocaleTimeString();
        const entry = document.createElement('div');
        entry.textContent = `[${timestamp}] ${message}`;
        logArea.appendChild(entry);
        logArea.scrollTo({ top: logArea.scrollHeight, behavior: 'smooth' });
        console.log('[EmailSender UI] log:', message);
      };

      const normalizeVariableKey = (input) => {
        if (input === undefined || input === null) return '';
        let base = String(input);
        if (typeof base.normalize === 'function') {
          base = base.normalize('NFKD').replace(/[\u0300-\u036f]/g, '');
        }
        const normalized = base
          .toLowerCase()
          .replace(/[^a-z0-9]+/g, '-')
          .replace(/^-+|-+$/g, '');
        return normalized;
      };

      const describeVariableToken = (key) => {
        if (!key) return '';
        return key.split('-').filter(Boolean).map((chunk) => chunk.charAt(0).toUpperCase() + chunk.slice(1)).join('-') || key.toUpperCase();
      };

      const buildPlaceholder = (key) => `{${describeVariableToken(key)}}`;

      const isLikelyNameLabel = (label = '', index = 0) => {
        if (index === 1) return true;
        return /name|contact|person/i.test(label);
      };

      const applyNameAliases = (target, value) => {
        if (!target || !value) return;
        NAME_ALIAS_KEYS.forEach((aliasKey) => {
          if (!target[aliasKey]) {
            target[aliasKey] = value;
          }
        });
      };

      const resetVariableRegistry = () => {
        variableRegistry.clear();
        renderVariableTokens();
      };

      const registerPersonalizationColumns = (entries = []) => {
        let added = 0;
        const registerEntry = (rawLabel, { hidden = false } = {}) => {
          if (hidden) return;
          const normalized = normalizeVariableKey(rawLabel);
          if (!normalized) return;
          if (variableRegistry.has(normalized)) return;
          const friendly = String(rawLabel || '').trim() || describeVariableToken(normalized);
          variableRegistry.set(normalized, {
            key: normalized,
            label: friendly,
            placeholder: buildPlaceholder(normalized)
          });
          added += 1;
        };

        entries.forEach((entry) => {
          if (!entry) return;
          if (typeof entry === 'string') {
            registerEntry(entry);
            return;
          }
          registerEntry(entry.label, { hidden: Boolean(entry.hidden) });
          if (Array.isArray(entry.aliases)) {
            entry.aliases.forEach((alias) => registerEntry(alias, { hidden: true }));
          }
        });

        if (added) renderVariableTokens();
      };

      const renderVariableTokens = () => {
        if (!variableListEl) return;
        variableListEl.innerHTML = '';
        const tokens = Array.from(variableRegistry.values());
        if (!tokens.length) {
          const empty = document.createElement('div');
          empty.textContent = 'No variables available yet.';
          empty.className = 'variable-note';
          variableListEl.appendChild(empty);
        } else {
          tokens.forEach((variable) => {
            const button = document.createElement('button');
            button.type = 'button';
            button.className = 'variable-token';
            button.dataset.token = variable.placeholder;
            button.innerHTML = `<strong>${variable.placeholder}</strong><span>${variable.label}</span>`;
            variableListEl.appendChild(button);
          });
        }
        if (variableNoteEl) {
          variableNoteEl.textContent = tokens.length
            ? 'Tokens mirror the columns in your uploaded sheet. Missing values stay blank for that contact.'
            : 'Upload a spreadsheet to unlock personalization placeholders derived from its column headers.';
        }
      };

      const insertTokenIntoBody = (token) => {
        if (!bodyInput || !token) return;
        const start = bodyInput.selectionStart ?? bodyInput.value.length;
        const end = bodyInput.selectionEnd ?? bodyInput.value.length;
        const before = bodyInput.value.slice(0, start);
        const after = bodyInput.value.slice(end);
        const result = `${before}${token}${after}`;
        bodyInput.value = result;
        const position = start + token.length;
        bodyInput.focus();
        bodyInput.setSelectionRange(position, position);
      };

      const upsertRecipientRecord = (contact) => {
        if (!contact || !contact.email) return;
        const key = contact.email.trim().toLowerCase();
        if (!key) return;
        const existing = recipientRecords.get(key) || { email: contact.email, fields: {}, normalized: {} };
        recipientRecords.set(key, {
          email: contact.email,
          fields: Object.assign({}, existing.fields, contact.fields || {}),
          normalized: Object.assign({}, existing.normalized, contact.normalized || {})
        });
      };

      const buildRecipientPayload = (emails) => emails.map((email) => {
        const key = email.trim().toLowerCase();
        const record = recipientRecords.get(key);
        const normalized = Object.assign({}, record?.normalized || {});
        normalized.email = email;
        return {
          email,
          variables: normalized,
          fields: record?.fields || {}
        };
      });

      const sanitizeHeaderLabel = (value, index) => {
        const trimmed = String(value ?? '').trim();
        if (index === 0) {
          if (!trimmed) return 'Email';
          return trimmed;
        }
        if (!trimmed) {
          return index === 1 ? 'First Name' : `Column ${index + 1}`;
        }
        if (index === 1 && /^(name|full name)$/i.test(trimmed)) {
          return 'First Name';
        }
        return trimmed;
      };

      const buildContactsFromRows = (rows, { treatFirstRowAsHeader = true } = {}) => {
        if (!Array.isArray(rows) || !rows.length) {
          return { contacts: [], headers: [], warnings: ['No rows detected in spreadsheet.'] };
        }

        let headerRow = rows[0];
        let dataRows = rows.slice(1);
        const firstCell = String(headerRow?.[0] ?? '').trim();
        const headerLooksLikeEmail = !treatFirstRowAsHeader ? true : EMAIL_CELL_PATTERN.test(firstCell);
        if (headerLooksLikeEmail) {
          headerRow = headerRow.map((_, index) => sanitizeHeaderLabel(index === 0 ? 'Email' : '', index));
          dataRows = rows;
        } else {
          headerRow = headerRow.map((value, index) => sanitizeHeaderLabel(value, index));
        }
        if (!headerRow.length) headerRow = ['Email'];
        if (!headerRow[0]) headerRow[0] = 'Email';

        const columnMeta = headerRow.slice(1).map((label, idx) => {
          const safeLabel = sanitizeHeaderLabel(label, idx + 1);
          const nameColumn = isLikelyNameLabel(safeLabel, idx + 1);
          const aliases = [];
          if (nameColumn) {
            aliases.push('First Name');
            NAME_ALIAS_KEYS.forEach((key) => {
              if (key && key !== 'email') {
                aliases.push(describeVariableToken(key));
              }
            });
          }
          return {
            label: safeLabel,
            isNameColumn: nameColumn,
            aliases
          };
        });

        const contacts = [];
        const warnings = [];

        dataRows.forEach((row, index) => {
          if (!Array.isArray(row)) return;
          const rawEmail = String(row[0] ?? '').trim();
          const spreadsheetRowNumber = headerLooksLikeEmail ? index + 1 : index + 2;
          if (!rawEmail) {
            warnings.push(`Row ${spreadsheetRowNumber} skipped: email missing.`);
            return;
          }
          const match = rawEmail.match(EMAIL_CELL_PATTERN);
          const email = match ? match[0].trim() : '';
          if (!email) {
            warnings.push(`Row ${spreadsheetRowNumber} skipped: invalid email "${rawEmail}".`);
            return;
          }

          const fields = {};
          const normalized = {};
          for (let colIdx = 1; colIdx < headerRow.length; colIdx += 1) {
            const columnInfo = columnMeta[colIdx - 1] || {
              label: sanitizeHeaderLabel('', colIdx),
              isNameColumn: colIdx === 1,
              aliases: colIdx === 1 ? ['First Name'] : []
            };
            const columnLabel = columnInfo.label || `Column ${colIdx + 1}`;
            const cellValue = row[colIdx];
            if (cellValue === null || cellValue === undefined) continue;
            const textValue = String(cellValue).trim();
            if (!textValue) continue;
            fields[columnLabel] = textValue;
            const normalizedKey = normalizeVariableKey(columnLabel);
            if (normalizedKey) {
              normalized[normalizedKey] = textValue;
            }
            if (columnInfo.isNameColumn || /name/i.test(columnLabel)) {
              applyNameAliases(normalized, textValue);
            }
          }
          contacts.push({ email, fields, normalized });
        });

        return { contacts, headers: columnMeta, warnings };
      };

      const parseWorkbookContacts = (workbook) => {
        if (!hasSpreadsheetSupport() || !workbook || !Array.isArray(workbook.SheetNames) || !workbook.SheetNames.length) {
          return { contacts: [], headers: [], warnings: ['Spreadsheet is empty or unsupported.'] };
        }
        const sheetName = workbook.SheetNames[0];
        const sheet = workbook.Sheets?.[sheetName];
        if (!sheet) {
          return { contacts: [], headers: [], warnings: ['Spreadsheet appears empty.'] };
        }
        const rows = window.XLSX.utils.sheet_to_json(sheet, { header: 1, blankrows: false, defval: '' });
        return buildContactsFromRows(rows);
      };

      const parseCsvContactsFromText = (text) => {
        if (!text || typeof text !== 'string') {
          return { contacts: [], headers: [], warnings: ['CSV file was empty.'] };
        }
        const rows = [];
        let current = [];
        let token = '';
        let insideQuotes = false;

        for (let i = 0; i < text.length; i += 1) {
          const char = text[i];
          if (char === '"') {
            if (insideQuotes && text[i + 1] === '"') {
              token += '"';
              i += 1;
            } else {
              insideQuotes = !insideQuotes;
            }
            continue;
          }
          if (char === ',' && !insideQuotes) {
            current.push(token.trim());
            token = '';
            continue;
          }
          if ((char === '\n' || char === '\r') && !insideQuotes) {
            if (token || current.length) {
              current.push(token.trim());
              rows.push(current);
              current = [];
              token = '';
            }
            continue;
          }
          token += char;
        }
        if (token || current.length) {
          current.push(token.trim());
          rows.push(current);
        }

        return buildContactsFromRows(rows);
      };

      resetVariableRegistry();

      const resetCounts = () => {
        if (queuedCountEl) queuedCountEl.textContent = '0';
        if (sentCountEl) sentCountEl.textContent = '0';
        if (failureCountEl) failureCountEl.textContent = '0';
        lastKnownStats = { total: 0, sent: 0, failed: 0 };
      };

      const updateCounts = ({ total, sent, failed }) => {
        if (typeof total === 'number' && queuedCountEl) {
          queuedCountEl.textContent = String(total);
          lastKnownStats.total = total;
        }
        if (typeof sent === 'number' && sentCountEl) {
          sentCountEl.textContent = String(sent);
          lastKnownStats.sent = sent;
        }
        if (typeof failed === 'number' && failureCountEl) {
          failureCountEl.textContent = String(failed);
          lastKnownStats.failed = failed;
        }
        syncActiveJobMetaFromStats({ total, sent, failed });
      };

      const updateSendButtonLabel = () => {
        if (!sendBtn) return;
        if (sending) {
          const total = activeJobMeta?.total || 0;
          const completed = Math.min(activeJobMeta?.completed || 0, total);
          const progress = total > 0 ? ` ${completed}/${total}` : '';
          sendBtn.innerHTML = `<i class="fa-solid fa-spinner fa-spin me-2"></i>Sending${progress}`;
        } else {
          sendBtn.innerHTML = defaultSendLabel;
        }
      };

      const setSendingState = (value, meta = null) => {
        sending = value;
        if (value) {
          const total = meta?.total ?? activeJobMeta?.total ?? 0;
          const completed = meta?.completed ?? activeJobMeta?.completed ?? 0;
          activeJobMeta = { total, completed };
          jobCompletionAnnounced = false;
          lastKnownStats = { total, sent: completed, failed: 0 };
        } else {
          activeJobMeta = null;
        }
        if (sendBtn) sendBtn.disabled = value;
        if (recipientsInput) recipientsInput.disabled = value;
        if (subjectInput) subjectInput.disabled = value;
        if (bodyInput) bodyInput.disabled = value;
        if (clearLogsBtn) clearLogsBtn.disabled = value;
        updateSendButtonLabel();
      };

      const syncActiveJobMetaFromStats = (stats) => {
        if (!sending || !stats) return;
        if (!activeJobMeta) {
          activeJobMeta = {
            total: Number(stats.total) || 0,
            completed: 0
          };
        }
        if (typeof stats.total === 'number' && !Number.isNaN(stats.total)) {
          activeJobMeta.total = stats.total;
        }
        const completed = (Number(stats.sent) || 0) + (Number(stats.failed) || 0);
        if (!Number.isNaN(completed)) {
          activeJobMeta.completed = Math.min(completed, activeJobMeta.total || completed);
        }
        updateSendButtonLabel();
      };

      const maybeAnnounceJobCompletion = (stats) => {
        if (jobCompletionAnnounced) return;
        const total = Number(stats?.total ?? lastKnownStats.total ?? activeJobMeta?.total ?? 0) || 0;
        const sent = Number(stats?.sent ?? lastKnownStats.sent ?? 0) || 0;
        const failed = Number(stats?.failed ?? lastKnownStats.failed ?? 0) || 0;
        if (!total) return;
        if ((sent + failed) < total) return;
        jobCompletionAnnounced = true;
        const summary = failed
          ? `Finished sending ${total} email(s): ${sent} delivered, ${failed} failed.`
          : `Success! Delivered all ${sent} email(s).`;
        appendLog(summary);
      };

      const parseRecipients = (rawValue) => {
        const raw = (rawValue || '').split(/[\n\r,;]+/).map((token) => token.trim()).filter(Boolean);
        const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        const valid = [];
        const invalid = [];

        raw.forEach((token) => {
          if (!emailPattern.test(token)) {
            invalid.push(token);
            return;
          }
          valid.push(token);
        });

        return { valid, invalid };
      };

      const hasSpreadsheetSupport = () => Boolean(window.XLSX && typeof window.XLSX.read === 'function' && window.XLSX.utils);

      const readFileAsArrayBuffer = (file) => new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result);
        reader.onerror = () => reject(reader.error || new Error('Failed to read file.'));
        reader.readAsArrayBuffer(file);
      });

      const readFileAsText = (file) => new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(typeof reader.result === 'string' ? reader.result : '');
        reader.onerror = () => reject(reader.error || new Error('Failed to read text file.'));
        reader.readAsText(file);
      });

      const readSpreadsheetFile = async (file, extension) => {
        if (!hasSpreadsheetSupport()) {
          throw new Error('Spreadsheet runtime unavailable.');
        }
        if (extension === 'csv') {
          const textContent = await readFileAsText(file);
          return window.XLSX.read(textContent, { type: 'string' });
        }
        const buffer = await readFileAsArrayBuffer(file);
        return window.XLSX.read(new Uint8Array(buffer), { type: 'array' });
      };

      const extractEmailsFromText = (text) => {
        if (!text) return [];
        const matches = text.match(EMAIL_CELL_PATTERN);
        return matches ? Array.from(new Set(matches.map((email) => email.trim()).filter(Boolean))) : [];
      };

      const applyImportedContacts = ({ contacts, columnHeaders = [], sourceLabel, warnings = [] }) => {
        if (!recipientsInput) return;
        if (!Array.isArray(contacts) || contacts.length === 0) {
          appendLog(`No entries detected in ${sourceLabel || 'the selected file'}.`);
          if (warnings && warnings.length) {
            warnings.forEach((note) => appendLog(`Note: ${note}`));
          }
          return;
        }

        const collectedEmails = [];
        contacts.forEach((contact) => {
          if (!contact || !contact.email) return;
          collectedEmails.push(contact.email);
          upsertRecipientRecord(contact);
        });

        if (!collectedEmails.length) {
          appendLog(`No valid email addresses found in ${sourceLabel || 'the selected file'}.`);
          if (warnings && warnings.length) {
            warnings.forEach((note) => appendLog(`Note: ${note}`));
          }
          return;
        }

        const combinedRaw = [recipientsInput.value, collectedEmails.join('\n')].filter(Boolean).join('\n');
        const merged = parseRecipients(combinedRaw).valid;
        recipientsInput.value = merged.join('\n');

        const personalizationEntries = [{ label: 'Email' }];
        if (Array.isArray(columnHeaders) && columnHeaders.length) {
          personalizationEntries.push(...columnHeaders);
        }
        registerPersonalizationColumns(personalizationEntries);

        appendLog(`Imported ${collectedEmails.length} recipients from ${sourceLabel || 'the selected file'}.`);
        if (warnings && warnings.length) {
          warnings.forEach((note) => appendLog(`Note: ${note}`));
        }
      };

      const handleRecipientFileSelection = async (event) => {
        const file = event?.target?.files?.[0];
        if (!file) return;

        const extension = (file.name.split('.').pop() || '').toLowerCase();
        const label = file.name || 'file';

        try {
          if (SHEET_FILE_EXTENSIONS.has(extension)) {
            if (!hasSpreadsheetSupport() && extension !== 'csv') {
              appendLog('Spreadsheet parsing requires the XLSX runtime. Export as CSV or enable the runtime and reload this page.');
              return;
            }
            if (extension === 'csv' && !hasSpreadsheetSupport()) {
              const textContent = await readFileAsText(file);
              const result = parseCsvContactsFromText(textContent);
              const warnings = (result.warnings || []).concat(['Parsed CSV without the XLSX runtime. Enable it for best compatibility.']);
              applyImportedContacts({
                contacts: result.contacts,
                columnHeaders: result.headers,
                sourceLabel: label,
                warnings
              });
            } else {
              const workbook = await readSpreadsheetFile(file, extension);
              const result = parseWorkbookContacts(workbook);
              applyImportedContacts({
                contacts: result.contacts,
                columnHeaders: result.headers,
                sourceLabel: label,
                warnings: result.warnings
              });
            }
            return;
          }

          const textContent = await readFileAsText(file);
          const candidates = extractEmailsFromText(textContent);
          const contacts = candidates.map((email) => ({ email, fields: {}, normalized: {} }));
          applyImportedContacts({ contacts, sourceLabel: label });
        } catch (err) {
          console.error('[EmailSender UI] Failed to import recipients:', err);
          appendLog(`Unable to import recipients from ${file.name || 'file'}: ${err?.message || err}`);
        } finally {
          if (event?.target) event.target.value = '';
        }
      };

      if (importRecipientsBtn && recipientFileInput) {
        importRecipientsBtn.addEventListener('click', () => recipientFileInput.click());
        recipientFileInput.addEventListener('change', handleRecipientFileSelection);
        if (!hasSpreadsheetSupport()) {
          importRecipientsBtn.title = 'Spreadsheet import relies on the XLSX runtime. Reload if you just enabled it.';
        }
      }

      if (variableListEl) {
        variableListEl.addEventListener('click', (event) => {
          const target = event.target.closest('.variable-token');
          if (!target) return;
          event.preventDefault();
          insertTokenIntoBody(target.dataset.token);
        });
      }

      const downloadLogs = () => {
        if (!logArea) return;
        const blob = new Blob([logArea.textContent || ''], { type: 'text/plain;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = `email-sender-logs-${Date.now()}.txt`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);
      };

      const describeAccountError = (code) => {
        if (!code) return '';
        if (/identity_unavailable|identity_error|identity_exception/i.test(code)) {
          return 'Unable to detect the Chrome profile email automatically.';
        }
        return code;
      };

      const showAccountMessage = (message, type = 'info') => {
        if (!accountNotice) return;
        accountNotice.textContent = message;
        accountNotice.style.display = 'block';
        accountNotice.classList.remove('border-info', 'border-danger');
        accountNotice.classList.add(type === 'error' ? 'border-danger' : 'border-info');
        console.log('[EmailSender UI] account notice:', { message, type });
      };

      const hideAccountMessage = () => {
        if (!accountNotice) return;
        accountNotice.textContent = '';
        accountNotice.style.display = 'none';
        accountNotice.classList.remove('border-info', 'border-danger');
      };

      const requestAccount = () => {
        if (!runtime && !emailBridge) {
          showAccountMessage('Open this page from the Chrome extension to enable automated sending.', 'error');
          if (sendBtn) sendBtn.disabled = true;
          return;
        }

        if (sendBtn) sendBtn.disabled = false;

        sendRuntimeMessage({ tool: 'emailSender', action: 'whoami' }, ({ response, error } = {}) => {
          if (error) {
            if (error === 'extension_unavailable' || error === 'bridge_timeout') {
              showAccountMessage('Open this page from the Chrome extension to enable automated sending.', 'error');
              if (sendBtn) sendBtn.disabled = true;
              return;
            }
            showAccountMessage(`Unable to verify Google account: ${error}`, 'error');
            return;
          }

          const account = response || {};
          console.log('[EmailSender UI] whoami response:', account);
          if (account.email) {
            console.log('[EmailSender UI] Logged-in Gmail account:', account.email);
            showAccountMessage(`Sending as: ${account.email}`);
            return;
          }

          const reason = describeAccountError(account.error);
          if (reason) {
            showAccountMessage(`Unable to determine Gmail account (${reason}). You may be prompted to sign in when sending.`, 'error');
          } else {
            hideAccountMessage();
          }
        });
      };

      if (exportLogsBtn) {
        exportLogsBtn.addEventListener('click', downloadLogs);
      }

      if (clearLogsBtn) {
        clearLogsBtn.addEventListener('click', () => {
          if (!logArea) return;
          logArea.innerHTML = '';
          delete logArea.dataset.initialized;
          resetCounts();
        });
      }

        if (runtime && runtime.onMessage && typeof runtime.onMessage.addListener === 'function') {
          runtime.onMessage.addListener(handleEmailRuntimeMessage);
        }

      senderForm?.addEventListener('reset', () => {
        recipientRecords.clear();
        resetVariableRegistry();
      });

      senderForm?.addEventListener('submit', (event) => {
        event.preventDefault();
        if (sending) return;

        const recipientsRaw = recipientsInput?.value || '';
        const subject = (subjectInput?.value || '').trim();
        const body = (bodyInput?.value || '').trim();
        console.log('[EmailSender UI] submit attempt', { recipientsRaw, subject, bodyLength: body.length });

        const { valid, invalid } = parseRecipients(recipientsRaw);
        if (!valid.length) {
          appendLog('Provide at least one valid recipient email address.');
          return;
        }
        if (!subject) {
          appendLog('Subject is required.');
          return;
        }
        if (!body) {
          appendLog('Email body cannot be empty.');
          return;
        }

        if (!runtime && !emailBridge) {
          appendLog('Automated sending requires the Chrome extension runtime.');
          return;
        }

        const recipientPayload = buildRecipientPayload(valid);

        setSendingState(true, { total: valid.length, completed: 0 });
        updateCounts({ total: valid.length, sent: 0, failed: 0 });
        appendLog(`Queued ${valid.length} emails for delivery.`);
        if (invalid.length) {
          appendLog(`Skipped ${invalid.length} invalid address(es): ${invalid.join(', ')}`);
        }

        console.log('[EmailSender UI] dispatching sendBatch request', { valid, subject, bodyLength: body.length });
        sendRuntimeMessage({
          tool: 'emailSender',
          action: 'sendBatch',
          payload: { recipients: recipientPayload, subject, body, bodyTemplate: body }
        }, ({ response, error } = {}) => {
          if (error) {
            console.error('[EmailSender UI] sendBatch error', error);
            appendLog(`Unable to start sending: ${error}`);
            setSendingState(false);
            return;
          }

          if (!response || !response.ok) {
            const msg = response?.error || 'Unknown error starting batch';
            console.warn('[EmailSender UI] sendBatch rejected', response);
            appendLog(`Unable to start sending: ${msg}`);
            setSendingState(false);
            return;
          }

          console.log('[EmailSender UI] sendBatch response:', response);
          currentJobId = response.jobId || null;
          if (response.stats && typeof response.stats.total === 'number') {
            activeJobMeta = { total: response.stats.total, completed: 0 };
            updateSendButtonLabel();
          }
          if (response.accountEmail) {
            appendLog(`Using Gmail account: ${response.accountEmail}`);
          } else if (response.warning) {
            appendLog(`Warning: ${response.warning}`);
          }
        });
      });

        requestAccount();

        if (runtime || emailBridge) {
          sendRuntimeMessage({ tool: 'emailSender', action: 'ping' }, ({ response, error } = {}) => {
            if (error) {
              console.warn('[EmailSender UI] ping failed:', error);
              return;
            }
            console.log('[EmailSender UI] background ping response:', response);
          });
        }
    } catch (err) {
        console.error('Email sender initialisation failed:', err);
      }
    });
  