// Atlassian (Jira / Service Desk / Confluence) DOM observer.
//
// Captures three things from *.atlassian.net pages the user is already viewing:
//   1) The currently opened issue (key, title, status, assignee)
//   2) Issues in board / queue / search lists
//   3) Comments on the currently opened issue, with author + text + createdAt
//
// Service Desk has a *different* DOM than standard Jira; the selectors below
// fall back through several variants so both views work.

(() => {
    const REPORT_DEBOUNCE_MS = 800;
    // Diagnostic logs off by default (page/issue text must not leak to DevTools).
    // Set window.SLOTH_APP_DEBUG = true in DevTools to enable while tuning selectors.
    const SLOTH_APP_DEBUG = (typeof window !== 'undefined' && window.SLOTH_APP_DEBUG === true);
    const seenIssueSigs = new Set();
    const seenCommentSigs = new Set();
    let lastReportAt = 0;

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

    function pickFirst(root, selectors) {
        for (const sel of selectors) {
            try {
                const el = root.querySelector(sel);
                if (el) return el;
            } catch { /* invalid selector — skip */ }
        }
        return null;
    }

    function pickAll(root, selectors) {
        const seen = new Set();
        const out = [];
        for (const sel of selectors) {
            try {
                root.querySelectorAll(sel).forEach((el) => {
                    if (!seen.has(el)) { seen.add(el); out.push(el); }
                });
            } catch { /* invalid selector — skip */ }
        }
        return out;
    }

    function urlIssueKey() {
        // Matches /browse/PROJ-123, /portal/16/PROJ-123, ?selectedIssue=PROJ-123, etc.
        const m = location.href.match(/[/=]([A-Z][A-Z0-9_]+-\d+)(?:[/?#]|$)/);
        return m ? m[1] : null;
    }

    /** Best-effort lookup of the current user's display name for "isMine" comments. */
    function currentUserName() {
        const cached = currentUserName._cached;
        if (cached !== undefined) return cached;
        const candidates = [
            document.querySelector('[data-testid="atlassian-navigation--profile-trigger"] img'),
            document.querySelector('button[aria-label^="Account"] img'),
            document.querySelector('a[href*="/people/"] img'),
            document.querySelector('img.css-1bhhrgc'),
        ];
        for (const el of candidates) {
            if (!el) continue;
            const label = el.getAttribute('alt') || el.getAttribute('aria-label') || '';
            if (label && !/^Profile|Avatar$/i.test(label)) {
                currentUserName._cached = label.trim();
                return currentUserName._cached;
            }
        }
        currentUserName._cached = null;
        return null;
    }

    // ---------- Issue extraction ----------

    function extractIssueView() {
        const keyFromUrl = urlIssueKey();
        const keyEl = pickFirst(document, [
            '[data-testid="issue.views.issue-base.foundation.breadcrumbs.current-issue.item"]',
            '[data-component-selector="jira.issue-view.foundation.breadcrumbs.current-issue.item"]',
            'a[data-testid="issue.views.issue-base.foundation.breadcrumbs.current-issue.item"]',
            '[data-testid="request-key"]',                                  // Service Desk customer portal
        ]);
        const key = keyFromUrl || txt(keyEl) || null;
        if (!key) return null;

        const titleEl = pickFirst(document, [
            'h1[data-testid="issue.views.issue-base.foundation.summary.heading"]',
            'h1[data-test-id="issue.views.issue-base.foundation.summary.heading"]',
            'h1[data-testid="request-summary"]',
            '[data-testid="request-details.request-summary"] h1',
            'main h1',
            '[role="main"] h1',
        ]);
        const title = txt(titleEl) || document.title.replace(/\s*-\s*Jira.*/i, '');

        const statusEl = pickFirst(document, [
            '[data-testid="issue.views.issue-base.foundation.status.status-field-wrapper"] button',
            '[data-test-id="issue.fields.status.common.ui.status-lozenge"]',
            '[data-testid="request-status"]',                               // Service Desk
            'span[data-testid="lozenge"]',
            '[data-test-id="request-status-lozenge"]',
        ]);
        const status = txt(statusEl);

        const assigneeEl = pickFirst(document, [
            '[data-testid="issue.views.field.user.assignee"] a',
            '[data-testid="issue.views.field.user.assignee"]',
            '[data-test-id="issue.views.field.user.assignee"]',
            '[data-testid="request-assignee"]',                             // Service Desk
        ]);
        const assignee = txt(assigneeEl);

        const typeEl = pickFirst(document, [
            '[data-testid="issue.views.issue-base.foundation.breadcrumbs.issue-type-switcher.button"] img',
            '[data-test-id="issue-type-switcher"] img',
            '[data-testid="request-type-icon"] img',
        ]);
        const issueType = typeEl ? (typeEl.getAttribute('alt') || typeEl.getAttribute('title')) : null;

        return { key, title, status, assignee, issueType, url: location.href };
    }

    function extractList() {
        const out = [];
        const rows = pickAll(document, [
            '[data-test-id^="board-card"]',
            '[data-testid^="software-board.board-container.board.card-container"]',
            'tr[data-testid^="native-issue-table.ui.issue-row"]',
            '[data-test-id="request-list-item"]',                           // Service Desk requests list
            '[data-testid="request-list-item"]',
        ]);
        rows.forEach((r) => {
            const linkEl = r.querySelector('a[href*="/browse/"], a[href*="/portal/"][href*="-"]');
            const hrefKey = linkEl
                ? (linkEl.href.match(/(?:\/browse\/|\/portal\/\d+\/)([A-Z][A-Z0-9_]+-\d+)/) || [])[1]
                : null;
            const key = hrefKey
                || txt(r.querySelector('[data-testid$="key"]'))
                || null;
            if (!key) return;
            const title = txt(r.querySelector('[data-testid$="summary"]'))
                || txt(linkEl);
            const status = txt(r.querySelector('[data-testid$="status"]'))
                || txt(r.querySelector('span[data-testid="lozenge"]'));
            const assignee = (r.querySelector('img[alt]')?.alt) || null;
            const url = linkEl ? linkEl.href : null;
            out.push({ key, title, status, assignee, issueType: null, url });
        });
        return out;
    }

    function reportIssue(issue) {
        if (!issue || !issue.key) return;
        const sig = `${issue.key}:${issue.status}:${issue.assignee}`;
        if (seenIssueSigs.has(sig)) return;
        seenIssueSigs.add(sig);
        safeSend({ type: 'jiraIssue', issue });
    }

    // ---------- Comment extraction ----------

    function extractComments(key) {
        const out = [];
        const containers = pickAll(document, [
            // --- Standard Jira agent issue view ---
            '[data-testid="issue-activity-feed"] [data-testid="comment-base-item"]',
            '[data-testid="issue.activity.comment"]',
            '[data-test-id^="issue-comment-base-"]',
            // --- Service Desk customer portal (verified on nudge1.atlassian.net) ---
            // Each activity-item wraps one entry (comment OR status change).
            // We filter to comment-only later by checking for .is-comment inside.
            '[data-testid="request-details.activity-item"]',
            '[data-test-id="request-details.activity-item"]',
            // --- Service Desk legacy (Atlassian's older JSP-based portal) ---
            'div.activity-comment',
            'li.activity-item',
        ]);
        // Keep only activity items that contain an actual comment body
        // (.is-comment class is the Service Desk marker). Status-change rows
        // and empty editor/hidden containers get filtered out here.
        const usefulContainers = containers.filter((c) => {
            if ((c.innerText || '').trim().length === 0) return false;
            // Service Desk: must contain .is-comment to be a real comment.
            if (c.matches('[data-testid="request-details.activity-item"], [data-test-id="request-details.activity-item"]')) {
                return !!c.querySelector('.is-comment, [class*="is-comment"]');
            }
            return true;
        });

        if (SLOTH_APP_DEBUG) {
            const sig = `${containers.length}:${usefulContainers.length}`;
            if (extractComments._lastDebugSig !== sig) {
                extractComments._lastDebugSig = sig;
                console.log('[SlothApp.atlassian] scan:',
                    containers.length, 'raw,',
                    usefulContainers.length, 'with text on', location.href);

                if (usefulContainers.length === 0) {
                    // Show all data-test* and aria-label values that contain
                    // 'omment' anywhere on the page — case-insensitive scan.
                    const allEls = document.querySelectorAll('*');
                    const seenIds = new Set();
                    const candidates = [];
                    allEls.forEach((el) => {
                        for (const a of el.attributes) {
                            const v = (a.value || '').toLowerCase();
                            const n = a.name.toLowerCase();
                            if ((n.includes('test') || n.includes('aria') || n === 'class' || n === 'id')
                                && (v.includes('omment') || v.includes('ctivit'))) {
                                const tag = `${el.tagName}|${a.name}=${a.value.slice(0, 40)}`;
                                if (!seenIds.has(tag)) {
                                    seenIds.add(tag);
                                    candidates.push({ el, attr: a.name, val: a.value });
                                }
                                break;
                            }
                        }
                    });
                    console.log('[SlothApp.atlassian] candidates found:', candidates.length);
                    candidates.slice(0, 25).forEach((c, idx) => {
                        const text = (c.el.innerText || '').replace(/\s+/g, ' ').trim();
                        console.log(`[SlothApp.atlassian]   #${idx + 1}:`,
                            c.el.tagName,
                            `${c.attr}="${c.val.slice(0, 80)}"`,
                            '|text-len:', text.length,
                            '|text:', text.slice(0, 100));
                    });
                } else {
                    usefulContainers.slice(0, 5).forEach((el, idx) => {
                        console.log(`[SlothApp.atlassian]   matched #${idx + 1}:`,
                            el.tagName,
                            Array.from(el.attributes).map(a => `${a.name}="${(a.value || '').slice(0, 60)}"`).join(' '),
                            '|text:', (el.innerText || '').replace(/\s+/g, ' ').slice(0, 150));
                    });
                }
            }
        }


        usefulContainers.forEach((c) => {
            const authorEl = pickFirst(c, [
                '[data-testid="comment-base-item.author"]',
                '[data-testid="activitystream-comment.author"]',
                '[data-testid$=".author"]',
                '[data-test-id$=".author"]',
                // Service Desk customer portal: author lives in the header
                // above the renderer wrapper.
                '[data-testid="request-details.activity-item.author"]',
                '[data-test-id="request-details.activity-item.author"]',
                'a[href*="/people/"]',
                '[data-test-id="comment-author"]',
                'span[class*="author"]',
                'header span',
                'header a',
            ]);
            const bodyEl = pickFirst(c, [
                // Service Desk customer portal: the verified marker.
                '.ak-renderer-wrapper.is-comment',
                '[class*="is-comment"] .ak-renderer-document',
                '[class*="is-comment"]',
                '[data-testid="comment-base-item.body"]',
                '[data-testid="activitystream-comment.body"]',
                '[data-testid$=".body"]',
                '[data-test-id$=".body"]',
                '.ak-renderer-document',
                '[data-test-id="comment-body"]',
                'div[class*="ak-renderer"]',
                'div[class*="comment-body"]',
            ]);
            const tsEl = pickFirst(c, [
                'time',
                '[data-testid$=".timestamp"]',
                '[data-test-id$=".timestamp"]',
                '[data-test-id="comment-created"]',
                'span[title*=":"]',
                'span[title*="20"]',
            ]);

            let author = txt(authorEl);
            const body = txt(bodyEl);
            if (!body || body.length < 2) return;

            // If we couldn't find a dedicated author element, fall back to
            // the text BEFORE the comment body inside the activity-item.
            // Service Desk renders e.g. "<author>오주혜<time>어제 10:44</time>"
            // immediately before the .is-comment block.
            if (!author && bodyEl) {
                const full = (c.innerText || '').trim();
                const bodyText = (bodyEl.innerText || '').trim();
                const idx = bodyText ? full.indexOf(bodyText) : -1;
                if (idx > 0) {
                    // Strip trailing timestamp-ish tokens ("어제 10:44", "2024-12-05", "5분 전")
                    author = full.slice(0, idx)
                        .replace(/(어제|오늘|\d+\s*(분|시간|일|주|개월|년)\s*전|\d{4}[-./]\d{1,2}[-./]\d{1,2}|\d{1,2}:\d{2})\s*$/g, '')
                        .trim();
                }
            }
            if (!author) author = '(작성자 미상)';

            let createdAt = null;
            if (tsEl) {
                const abs = tsEl.getAttribute('datetime')
                    || tsEl.getAttribute('title')
                    || tsEl.getAttribute('data-datetime');
                if (abs) {
                    const t = new Date(abs);
                    if (!isNaN(t.getTime())) createdAt = t.toISOString();
                }
            }

            const commentId = c.getAttribute('data-comment-id')
                || c.getAttribute('id')
                || `${key}:${author}:${body.slice(0, 40)}`;

            const me = currentUserName();
            const isMine = !!(me && author.toLowerCase().includes(me.toLowerCase()));

            const sig = `${key}:${commentId}`;
            if (seenCommentSigs.has(sig)) return;
            seenCommentSigs.add(sig);

            const text = body.length > 800 ? body.slice(0, 800) + '…' : body;

            out.push({
                key, commentId, author,
                text,
                createdAt,
                isMine,
                url: location.href,
            });
        });
        return out;
    }

    function reportComment(comment) {
        safeSend({ type: 'jiraComment', jiraComment: comment });
    }

    // ---- 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] Atlassian 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(); }
    }

    // ---------- Scan loop ----------

    function scan() {
        const now = Date.now();
        if (now - lastReportAt < REPORT_DEBOUNCE_MS) return;
        lastReportAt = now;

        const single = extractIssueView();
        if (single) {
            reportIssue(single);
            // Comments only make sense on a single-issue view.
            for (const c of extractComments(single.key)) reportComment(c);
        }
        for (const it of extractList()) reportIssue(it);
    }

    const observer = new MutationObserver(() => {
        if (cleanedUp) return;
        if (!isExtAlive()) { cleanup(); return; }
        try { scan(); } catch (_) { if (!isExtAlive()) cleanup(); }
    });
    observer.observe(document.body, { childList: true, subtree: true });

    setTimeout(() => {
        if (cleanedUp || !isExtAlive()) { cleanup(); return; }
        try { scan(); } catch (_) { if (!isExtAlive()) cleanup(); }
    }, 1500);
    let lastHref = location.href;
    urlPollInterval = setInterval(() => {
        if (cleanedUp) return;
        if (!isExtAlive()) { cleanup(); return; }
        if (location.href !== lastHref) {
            lastHref = location.href;
            seenIssueSigs.clear();
            seenCommentSigs.clear();
            currentUserName._cached = undefined;
            extractComments._loggedOnce = false;
            setTimeout(() => {
                if (cleanedUp || !isExtAlive()) { cleanup(); return; }
                try { scan(); } catch (_) { if (!isExtAlive()) cleanup(); }
            }, 600);
        }
    }, 1500);

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

    console.log('[SlothApp] Atlassian collector active (issues + comments).');
})();
