// SlothApp - Gmail (mail.google.com) DOM observer.
//
// What it does: when the user opens an email (the conversation thread view is
// visible), extracts subject/from/snippet and forwards to the native host.
// Designed to be narrow: only emits per opened conversation, deduplicates by
// thread+message, never emits while the inbox list is the active view.
//
// What it does NOT do: scrape every list row, every preview hover, sent
// drafts, or attachments. Just "the message I actually opened."

(() => {
    const MAX_SNIPPET = 600;
    const REPORT_DEBOUNCE_MS = 1200;
    const seenMessageIds = new Set();
    let lastReportAt = 0;

    function txt(el) {
        return el ? (el.innerText || el.textContent || '').trim() : '';
    }

    /** Detect whether the chat side panel rather than mail is the active view. */
    function isChatView() {
        return /\/chat\//.test(location.hash) || /\/mail\/.*\/chat/.test(location.pathname);
    }

    /** Detect whether a single conversation is open (the rendered open message). */
    function openedConversationElements() {
        // Gmail uses h2.hP for the conversation subject when one is open.
        const subjectEl = document.querySelector('h2.hP');
        if (!subjectEl) return null;
        // Open message body containers (last is usually the most-recent visible message).
        const openMessages = Array.from(document.querySelectorAll('div.adn'));
        return { subjectEl, openMessages };
    }

    /** Extract the visible message metadata. Returns {message...} or null. */
    function extractOpenedMessage() {
        if (isChatView()) return null;
        const opened = openedConversationElements();
        if (!opened) return null;
        const subject = txt(opened.subjectEl);
        if (!subject) return null;

        // Pick the last expanded message (newest visible)
        const msg = opened.openMessages.reverse().find((el) => {
            // Only consider expanded ones: collapsed messages have no visible body
            const body = el.querySelector('div.a3s');
            return body && body.offsetHeight > 0;
        });
        if (!msg) return null;

        // Sender (header has data-hovercard-id email).
        const fromEl = msg.querySelector('span.gD');
        const from = fromEl
            ? (fromEl.getAttribute('email') || fromEl.getAttribute('data-hovercard-id') || txt(fromEl))
            : '';

        // Recipients displayed inline ("to me, alice, bob …"). Best-effort.
        const toEls = msg.querySelectorAll('div.akl span.g2, div.aju span.g2');
        const toList = Array.from(toEls)
            .map((el) => el.getAttribute('email') || el.getAttribute('data-hovercard-id') || txt(el))
            .filter(Boolean);

        // Body
        const bodyEl = msg.querySelector('div.a3s');
        let snippet = txt(bodyEl);
        if (snippet.length > MAX_SNIPPET) {
            snippet = snippet.slice(0, MAX_SNIPPET) + '…';
        }

        // Message identifier (data-message-id includes <abc@...> form)
        const messageId = msg.getAttribute('data-message-id') || msg.id || null;
        const threadId = (location.hash.match(/\/(?:[A-Za-z0-9]+\/)*([A-Za-z0-9]+)$/) || [])[1] || null;

        // Direction heuristic:
        //   - if the user's account email appears in `from`, treat as outbound
        //   - if the toolbar URL or DOM marks "Sent" folder, treat as outbound
        let direction = 'inbound';
        if (/#sent/.test(location.hash) || /#drafts/.test(location.hash)) {
            direction = 'outbound';
        } else {
            const meEmail = guessUserEmail();
            if (meEmail && from.toLowerCase().includes(meEmail.toLowerCase())) {
                direction = 'outbound';
            }
        }

        // Receive time
        const tsEl = msg.querySelector('span.g3, span.gK span[title]');
        let receivedAt = null;
        if (tsEl) {
            const title = tsEl.getAttribute('title') || tsEl.getAttribute('data-tooltip') || '';
            const parsed = title ? new Date(title) : null;
            if (parsed && !isNaN(parsed.getTime())) {
                receivedAt = parsed.toISOString();
            }
        }

        return {
            messageId,
            threadId,
            subject,
            snippet,
            from,
            to: toList,
            receivedAt,
            direction,
            url: location.href,
        };
    }

    /** Try to read the logged-in account's email from the avatar/account pill. */
    function guessUserEmail() {
        const candidates = [
            document.querySelector('a[aria-label*="@"]'),
            document.querySelector('a.gb_d[aria-label]'),
            document.querySelector('img.gb_P'),
        ];
        for (const el of candidates) {
            if (!el) continue;
            const label = el.getAttribute('aria-label') || el.getAttribute('alt') || '';
            const m = label.match(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/);
            if (m) return m[0];
        }
        return null;
    }

    function reportEmail(msg) {
        if (!msg || !msg.subject) return;
        const sig = msg.messageId || `${msg.threadId}:${msg.subject}:${msg.snippet.slice(0, 50)}`;
        if (seenMessageIds.has(sig)) return;
        const now = Date.now();
        if (now - lastReportAt < REPORT_DEBOUNCE_MS) {
            // still allow the dedupe-by-id below; just delay re-emit window
        }
        seenMessageIds.add(sig);
        lastReportAt = now;
        safeSend({ type: 'emailMessage', email: msg });
    }

    function scan() {
        const m = extractOpenedMessage();
        if (m) reportEmail(m);
    }

    // ---- Extension lifecycle guards ----
    let cleanedUp = false;
    let urlPollInterval = null;

    function isExtAlive() {
        try { return !!(chrome && chrome.runtime && chrome.runtime.id); }
        catch (_) { return false; }
    }

    function cleanup() {
        if (cleanedUp) return;
        cleanedUp = true;
        try { observer.disconnect(); } catch (_) {}
        if (urlPollInterval) { clearInterval(urlPollInterval); urlPollInterval = null; }
        console.log('[SlothApp] Gmail collector stopped (extension context gone).');
    }

    function safeSend(payload) {
        if (cleanedUp) return;
        if (!isExtAlive()) { cleanup(); return; }
        try {
            chrome.runtime.sendMessage(payload, () => {
                try { void chrome.runtime.lastError; } catch (_) {}
            });
        } catch (_) { cleanup(); }
    }

    // Observe DOM mutations for SPA navigation.
    const safeScan = () => {
        if (cleanedUp) return;
        if (!isExtAlive()) { cleanup(); return; }
        try { scan(); } catch (_) { if (!isExtAlive()) cleanup(); }
    };
    const observer = new MutationObserver(() => {
        if (cleanedUp) return;
        if (!isExtAlive()) { cleanup(); return; }
        clearTimeout(scan._t);
        scan._t = setTimeout(safeScan, 300);
    });
    observer.observe(document.body, { childList: true, subtree: true });

    // Initial pass + URL polling for SPA route changes.
    setTimeout(safeScan, 1500);
    let lastHref = location.href;
    urlPollInterval = setInterval(() => {
        if (cleanedUp) return;
        if (!isExtAlive()) { cleanup(); return; }
        if (location.href !== lastHref) {
            lastHref = location.href;
            setTimeout(safeScan, 600);
        }
    }, 1500);

    window.addEventListener('pagehide', cleanup, { once: true });

    console.log('[SlothApp] Gmail collector active.');
})();
