// sloth-app Chrome Extension - background service worker
// Detects active tab changes, focus/idle, and forwards events to the native host.
// Data is sent locally to the sloth-app desktop app via Chrome Native Messaging.

const NATIVE_HOST = 'com.sloth_app.host';
const IDLE_THRESHOLD_SECONDS = 180; // matches AppSettings.default.idleThresholdSeconds
const HEARTBEAT_INTERVAL_SECONDS = 30;
const MAX_PENDING_EVENTS = 500;
const NATIVE_RETRY_DELAY_MS = 5000;

// URL patterns that match our content scripts (kept in sync with manifest).
const CONTENT_SCRIPT_PATTERNS = [
    /^https:\/\/mail\.google\.com\/(chat|mail)\//,
    /^https:\/\/chat\.google\.com\//,
    /^https:\/\/calendar\.google\.com\//,
    /^https:\/\/docs\.google\.com\/(document|spreadsheets|presentation|forms)\//,
    /^https:\/\/[^/]+\.atlassian\.net\//,
];

function matchesAnyContentScript(url) {
    if (!url) return false;
    return CONTENT_SCRIPT_PATTERNS.some((re) => re.test(url));
}

// On install or update, reload every tab that has an old content-script context
// running so the page picks up the new scripts (no invalidated-context errors).
chrome.runtime.onInstalled.addListener((details) => {
    if (details.reason !== 'update' && details.reason !== 'install') return;
    chrome.tabs.query({}, (tabs) => {
        for (const tab of tabs) {
            if (matchesAnyContentScript(tab.url)) {
                try {
                    chrome.tabs.reload(tab.id, { bypassCache: false });
                } catch (_) {
                }
            }
        }
    });
});

let lastActiveTab = null; // { tabId, windowId, url, title }
let lastEmittedTabKey = '';
let queue = [];
let isFlushingQueue = false;
let retryTimerId = null;
let lastNativeStatus = {
    ok: false,
    message: 'not contacted',
    checkedAt: null,
};

// ---------- Native Messaging ----------
function updateNativeStatus(ok, message) {
    lastNativeStatus = {
        ok,
        message: message || (ok ? 'sent' : 'failed'),
        checkedAt: new Date().toISOString(),
    };
}

function handleNativeResponse(response) {
    if (response && response.type === 'reload') {
        console.log('[SlothApp] reload requested by host - calling chrome.runtime.reload()');
        try {
            chrome.runtime.reload();
        } catch (e) {
            console.warn('[SlothApp] chrome.runtime.reload failed:', e);
        }
    }
}

function sendNativeEvent(event) {
    return new Promise((resolve, reject) => {
        try {
            chrome.runtime.sendNativeMessage(NATIVE_HOST, event, (response) => {
                const err = chrome.runtime.lastError;
                if (err) {
                    reject(new Error(err.message || 'native messaging failed'));
                    return;
                }
                resolve(response || null);
            });
        } catch (e) {
            reject(e);
        }
    });
}

function scheduleRetry() {
    if (retryTimerId !== null) return;
    retryTimerId = setTimeout(() => {
        retryTimerId = null;
        flushQueue();
    }, NATIVE_RETRY_DELAY_MS);
}

async function flushQueue() {
    if (isFlushingQueue) return;
    isFlushingQueue = true;

    try {
        while (queue.length > 0) {
            const event = queue[0];
            try {
                const response = await sendNativeEvent(event);
                queue.shift();
                updateNativeStatus(true, response && response.type ? response.type : 'sent');
                handleNativeResponse(response);
            } catch (e) {
                const message = e && e.message ? e.message : String(e);
                console.warn('[SlothApp] sendNativeMessage failed:', message);
                updateNativeStatus(false, message);
                scheduleRetry();
                return;
            }
        }
    } finally {
        isFlushingQueue = false;
    }
}

function safeSend(event) {
    if (queue.length >= MAX_PENDING_EVENTS) {
        queue.shift();
    }
    queue.push(event);
    flushQueue();
}

function emit(type, extra = {}) {
    const event = {
        type,
        timestamp: new Date().toISOString(),
        ...extra,
    };
    safeSend(event);
}

// ---------- Tab tracking ----------
async function getTab(tabId) {
    try {
        return await chrome.tabs.get(tabId);
    } catch {
        return null;
    }
}

// SPA pages churn `tabs.onUpdated` for every hash/route change. Normalize the
// URL down to scheme+host+path and dedupe so native side sees real navigation.
function normalizeForDedup(rawUrl) {
    if (!rawUrl) return '';
    try {
        const u = new URL(rawUrl);
        return `${u.protocol}//${u.host}${u.pathname}`;
    } catch {
        return rawUrl;
    }
}

async function handleActivation(tabId, windowId) {
    const tab = await getTab(tabId);
    if (!tab) return;
    if (!tab.url || tab.incognito) return; // skip incognito (privacy default)
    lastActiveTab = { tabId, windowId, url: tab.url, title: tab.title };
    const key = `${tabId}:${normalizeForDedup(tab.url)}`;
    if (key === lastEmittedTabKey) return;
    lastEmittedTabKey = key;
    emit('tabActivated', { url: tab.url, title: tab.title, tabId, windowId });
}

chrome.tabs.onActivated.addListener(({ tabId, windowId }) => {
    handleActivation(tabId, windowId);
});

chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
    if (!tab.active) return;
    if (tab.incognito) return;
    if (changeInfo.status === 'complete' || changeInfo.url) {
        if (!tab.url) return;
        const key = `${tabId}:${normalizeForDedup(tab.url)}`;
        lastActiveTab = { tabId, windowId: tab.windowId, url: tab.url, title: tab.title };
        if (key === lastEmittedTabKey) return;
        lastEmittedTabKey = key;
        emit('tabUpdated', { url: tab.url, title: tab.title, tabId, windowId: tab.windowId });
    }
});

chrome.windows.onFocusChanged.addListener(async (windowId) => {
    if (windowId === chrome.windows.WINDOW_ID_NONE) {
        lastEmittedTabKey = '';
        emit('windowFocusLost');
        return;
    }
    try {
        const tabs = await chrome.tabs.query({ active: true, windowId });
        const tab = tabs[0];
        if (tab && !tab.incognito && tab.url) {
            lastActiveTab = { tabId: tab.id, windowId, url: tab.url, title: tab.title };
            emit('windowFocusChanged', { url: tab.url, title: tab.title, tabId: tab.id, windowId });
        } else {
            emit('windowFocusChanged');
        }
    } catch {
        emit('windowFocusChanged');
    }
});

// ---------- Idle detection ----------
chrome.idle.setDetectionInterval(IDLE_THRESHOLD_SECONDS);
chrome.idle.onStateChanged.addListener((state) => {
    if (state === 'active') emit('userActive');
    else emit('userIdle'); // both 'idle' and 'locked' treated as idle
});

// ---------- Heartbeat ----------
chrome.alarms.create('heartbeat', { periodInMinutes: HEARTBEAT_INTERVAL_SECONDS / 60 });
chrome.alarms.onAlarm.addListener((alarm) => {
    if (alarm.name === 'heartbeat') {
        emit('heartbeat', lastActiveTab ? { url: lastActiveTab.url } : {});
    }
});

// ---------- Receive content-script events + popup status ----------
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
    if (!msg || !msg.type) return;
    if (msg.type === 'chatMessage' || msg.type === 'chatRoomOpened') {
        emit(msg.type, { chat: msg.chat });
        sendResponse({ ok: true });
        return true;
    }
    if (msg.type === 'jiraIssue') {
        emit('jiraIssue', { jira: msg.issue });
        sendResponse({ ok: true });
        return true;
    }
    if (msg.type === 'jiraComment') {
        emit('jiraComment', { jiraComment: msg.jiraComment });
        sendResponse({ ok: true });
        return true;
    }
    if (msg.type === 'emailMessage') {
        emit('emailMessage', { email: msg.email });
        sendResponse({ ok: true });
        return true;
    }
    if (msg.type === 'webCalendarEvent') {
        emit('webCalendarEvent', { webCalendar: msg.webCalendar });
        sendResponse({ ok: true });
        return true;
    }
    if (msg.type === 'gwsDocActivity') {
        emit('gwsDocActivity', { gwsDoc: msg.gwsDoc });
        sendResponse({ ok: true });
        return true;
    }
    if (msg.type === 'getStatus') {
        sendResponse({
            connected: lastNativeStatus.ok,
            lastNativeStatus,
            pendingEvents: queue.length,
            mode: 'one-shot',
            lastTab: lastActiveTab,
        });
        return true;
    }
    sendResponse({ ok: true });
    return true;
});
