// SlothApp - Google Calendar DOM observer.
//
// macOS Calendar.app에 회사 Google 계정이 동기화 안 된 환경을 위한 fallback.
// 사용자가 calendar.google.com을 띄워두면 페이지의 일정 카드 DOM에서 직접
// 추출해서 native app으로 보낸다. API/OAuth 호출 없음.
//
// Strategy: every event chip on the page exposes:
//   - aria-label "할 일 제목, M월 d일 (요일) ⋅ HH:MM - HH:MM" (Korean) or
//                "Title, Month Day · HH:MM AM – HH:MM PM" (English)
//   - one or more <time datetime="ISO-8601"> children (most reliable)
//   - data-eventid / data-eventchip / role="button"
//
// We rely on the <time datetime> first, fall back to parsing aria-label.

(() => {
    const REPORT_DEBOUNCE_MS = 2000;
    let lastReportAt = 0;
    const seenSigs = new Set();

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

    function eventCardCandidates() {
        // Several selectors because Google Calendar's DOM varies by view.
        const selectors = [
            '[data-eventid]',
            '[data-eventchip]',
            '[role="button"][jslog*="event"]',
            'div[role="button"][aria-label]',
        ];
        const seen = new Set();
        const out = [];
        for (const sel of selectors) {
            try {
                document.querySelectorAll(sel).forEach((el) => {
                    if (!seen.has(el)) { seen.add(el); out.push(el); }
                });
            } catch { /* skip */ }
        }
        return out;
    }

    function extractTimes(el) {
        const times = el.querySelectorAll('time[datetime]');
        const isos = [];
        times.forEach((t) => {
            const v = t.getAttribute('datetime');
            if (!v) return;
            const d = new Date(v);
            if (!isNaN(d.getTime())) isos.push(d.toISOString());
        });
        return isos;
    }

    /** Parse "HH:MM" or "HH:MM AM/PM" or "오전/오후 HH:MM" out of a string. */
    function parseTimePart(str, fallbackDate) {
        if (!str) return null;
        const s = str.trim();
        // 24h or 12h numeric
        let m = s.match(/(?:(오전|오후)\s*)?(\d{1,2})\s*[:시]\s*(\d{2})/);
        if (m) {
            const ampm = m[1];
            let h = parseInt(m[2], 10);
            const mn = parseInt(m[3], 10);
            if (ampm === '오후' && h < 12) h += 12;
            if (ampm === '오전' && h === 12) h = 0;
            const d = new Date(fallbackDate);
            d.setHours(h, mn, 0, 0);
            return d;
        }
        m = s.match(/(\d{1,2})\s*(AM|PM)/i);
        if (m) {
            let h = parseInt(m[1], 10);
            const ap = m[2].toUpperCase();
            if (ap === 'PM' && h < 12) h += 12;
            if (ap === 'AM' && h === 12) h = 0;
            const d = new Date(fallbackDate);
            d.setHours(h, 0, 0, 0);
            return d;
        }
        return null;
    }

    function extractFromAriaLabel(label) {
        if (!label) return null;
        // Common formats:
        //   "회의제목, 11:00 ~ 12:00" (KR day view)
        //   "회의제목, 오전 11:00 ~ 오후 12:00" (KR)
        //   "Meeting, 11:00 to 12:00" (EN)
        //   "Meeting, 11am – 12pm" (EN)
        const today = new Date();
        const parts = label.split(/,/).map((p) => p.trim()).filter(Boolean);
        let title = parts[0] || label.trim();
        let start = null, end = null;
        for (let i = parts.length - 1; i >= 1; i--) {
            const seg = parts[i];
            const m = seg.match(/(.+?)\s*[-~–to에서까지]+\s*(.+)/);
            if (m) {
                const s = parseTimePart(m[1], today);
                const e = parseTimePart(m[2], today);
                if (s && e) { start = s; end = e; break; }
            }
            const single = parseTimePart(seg, today);
            if (single && !start) { start = single; }
        }
        if (!start) return null;
        if (!end) end = new Date(start.getTime() + 30 * 60 * 1000);
        return { title, start, end };
    }

    function extractCard(el) {
        const id = el.getAttribute('data-eventid')
            || el.getAttribute('data-eventchip')
            || el.id
            || null;
        const aria = el.getAttribute('aria-label') || '';
        if (!aria) return null;

        // Skip obvious non-events
        if (/google\s*calendar|search|navigation|^[\s\S]{0,3}$/i.test(aria)) return null;

        // Title is first comma-separated chunk (works for both KR/EN labels).
        const titleGuess = aria.split(',')[0].trim();
        if (!titleGuess || titleGuess.length < 2) return null;

        const isos = extractTimes(el);
        let start = null;
        let end = null;
        if (isos.length >= 2) {
            start = new Date(isos[0]);
            end = new Date(isos[isos.length - 1]);
        } else if (isos.length === 1) {
            start = new Date(isos[0]);
            end = new Date(start.getTime() + 30 * 60 * 1000);
        } else {
            const parsed = extractFromAriaLabel(aria);
            if (parsed) {
                start = parsed.start;
                end = parsed.end;
            }
        }
        if (!start || !end || isNaN(start.getTime()) || isNaN(end.getTime())) return null;
        if (end <= start) end = new Date(start.getTime() + 30 * 60 * 1000);

        const sig = id || `${titleGuess}|${start.toISOString()}|${end.toISOString()}`;

        return {
            sourceKey: `gcal:${sig}`,
            title: titleGuess,
            start: start.toISOString(),
            end: end.toISOString(),
            calendarName: 'Google Calendar (web)',
            attendees: [],
            url: location.href,
            isAllDay: false,
        };
    }

    function reportEvent(payload) {
        if (!payload) return;
        if (seenSigs.has(payload.sourceKey)) return;
        seenSigs.add(payload.sourceKey);
        safeSend({ type: 'webCalendarEvent', webCalendar: payload });
    }

    function scan() {
        const now = Date.now();
        if (now - lastReportAt < REPORT_DEBOUNCE_MS) return;
        lastReportAt = now;
        const cards = eventCardCandidates();
        cards.forEach((el) => {
            const p = extractCard(el);
            if (p) reportEvent(p);
        });
    }

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

    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, 800);
    });
    observer.observe(document.body, { childList: true, subtree: true });

    // Re-scan on URL changes (Google Calendar's date navigation).
    let lastHref = location.href;
    urlPollInterval = setInterval(() => {
        if (cleanedUp) return;
        if (!isExtAlive()) { cleanup(); return; }
        if (location.href !== lastHref) {
            lastHref = location.href;
            seenSigs.clear();
            setTimeout(safeScan, 800);
        }
    }, 1500);

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

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