// retoor <retoor@molodetz.nl>

const ROSTER_TOPIC = "public.presence.roster";

export class PresenceManager {
    constructor(pubsub, root = document) {
        this.online = null;
        this.tracked = new Map();
        this.scan(root);
        this.observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === 1) this.scan(node);
                }
            }
        });
        const host = root === document ? document.body : root;
        if (host) this.observer.observe(host, { childList: true, subtree: true });
        if (pubsub) pubsub.subscribe(ROSTER_TOPIC, (data) => this.apply(data));
    }

    stop() {
        this.observer.disconnect();
    }

    scan(root) {
        const targets = [];
        if (root.matches && root.matches("[data-presence-uid]")) targets.push(root);
        if (root.querySelectorAll) {
            root.querySelectorAll("[data-presence-uid]").forEach((el) => targets.push(el));
        }
        for (const el of targets) this.track(el);
    }

    track(el) {
        const uid = el.dataset.presenceUid;
        if (!uid) return;
        let entry = this.tracked.get(uid);
        if (!entry) {
            entry = { els: new Set(), lastSeen: null };
            this.tracked.set(uid, entry);
        }
        if (entry.els.has(el)) return;
        entry.els.add(el);
        const seed = el.dataset.presenceLastSeen || null;
        if (seed) entry.lastSeen = seed;
        this.render(uid, entry);
    }

    apply(roster) {
        if (!roster || !Array.isArray(roster.online)) return;
        const previous = this.online;
        this.online = new Set(roster.online);
        const wentOffline = new Date().toISOString();
        for (const [uid, entry] of this.tracked.entries()) {
            if (previous && previous.has(uid) && !this.online.has(uid)) {
                entry.lastSeen = wentOffline;
            }
            this.render(uid, entry);
        }
    }

    isOnline(uid) {
        return !!this.online && this.online.has(uid);
    }

    relative(iso) {
        const localTime = window.app && window.app.localTime;
        if (localTime) return localTime.format(iso, "ago");
        const date = new Date(iso);
        return Number.isNaN(date.getTime()) ? null : date.toLocaleDateString("en-GB");
    }

    label(uid, entry) {
        if (this.isOnline(uid)) return { text: "online" };
        const relative = entry.lastSeen ? this.relative(entry.lastSeen) : null;
        if (!relative) return { text: "offline" };
        return { text: "last seen ", iso: entry.lastSeen, relative };
    }

    timeElement(iso, relative) {
        const time = document.createElement("time");
        time.setAttribute("datetime", iso);
        time.dataset.dt = "";
        time.dataset.dtMode = "ago";
        time.textContent = relative;
        return time;
    }

    render(uid, entry) {
        if (!this.online) return;
        const online = this.isOnline(uid);
        const label = this.label(uid, entry);
        const title = label.iso ? label.text + label.relative : label.text;
        for (const el of entry.els) {
            el.classList.toggle("online", online);
            el.title = title;
            if (!el.hasAttribute("data-presence-label")) continue;
            el.textContent = label.text;
            if (label.iso) el.appendChild(this.timeElement(label.iso, label.relative));
        }
    }
}
