"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const LRU = require("lru-cache");
const __1 = require("..");
const MembershipEvent_1 = require("../models/events/MembershipEvent");
/**
 * Functions for avoiding calls to profile endpoints. Useful for bots when
 * people are mentioned often or bridges which need profile information
 * often.
 * @category Utilities
 */
class ProfileCache {
    /**
     * Creates a new profile cache.
     * @param {number} maxEntries The maximum number of entries to cache.
     * @param {number} maxAgeMs The maximum age of an entry in milliseconds.
     * @param {MatrixClient} client The client to use to get profile updates.
     */
    constructor(maxEntries, maxAgeMs, client) {
        this.client = client;
        this.cache = new LRU({
            max: maxEntries,
            maxAge: maxAgeMs,
        });
    }
    getCacheKey(userId, roomId) {
        return `${userId}@${roomId || '<none>'}`;
    }
    /**
     * Watch for profile changes to cached entries with the provided client. The
     * same client will also be used to update the user's profile in the cache.
     * @param {MatrixClient} client The client to watch for profile changes with.
     */
    watchWithClient(client) {
        client.on("room.event", (roomId, event) => __awaiter(this, void 0, void 0, function* () {
            if (!event['state_key'] || !event['content'] || event['type'] !== 'm.room.member')
                return;
            yield this.tryUpdateProfile(roomId, new MembershipEvent_1.MembershipEvent(event), client);
        }));
    }
    /**
     * Watch for profile changes to cached entries with the provided application
     * service. The clientFn will be called to get the relevant client for any
     * updates. If the clientFn is null, the appservice's bot user will be used.
     * The clientFn takes two arguments: the user ID being updated and the room ID
     * they are being updated in (shouldn't be null). The return value should be the
     * MatrixClient to use, or null to use the appservice's bot client. The same
     * client will be used to update the user's general profile, if that profile
     * is cached.
     * @param {Appservice} appservice The application service to watch for profile changes with.
     * @param {Function} clientFn The function to use to acquire profile updates with. If null, the appservice's bot client will be used.
     */
    watchWithAppservice(appservice, clientFn = null) {
        if (!clientFn)
            clientFn = () => appservice.botClient;
        appservice.on("room.event", (roomId, event) => __awaiter(this, void 0, void 0, function* () {
            if (!event['state_key'] || !event['content'] || event['type'] !== 'm.room.member')
                return;
            const memberEvent = new MembershipEvent_1.MembershipEvent(event);
            let client = clientFn(memberEvent.membershipFor, roomId);
            if (!client)
                client = appservice.botClient;
            yield this.tryUpdateProfile(roomId, memberEvent, client);
        }));
    }
    /**
     * Gets a profile for a user in optional room.
     * @param {string} userId The user ID to get a profile for.
     * @param {string|null} roomId Optional room ID to get a per-room profile for the user.
     * @returns {Promise<MatrixProfile>} Resolves to the user's profile.
     */
    getUserProfile(userId, roomId = null) {
        return __awaiter(this, void 0, void 0, function* () {
            const cacheKey = this.getCacheKey(userId, roomId);
            const cached = this.cache.get(cacheKey);
            if (cached)
                return Promise.resolve(cached);
            const profile = yield this.getUserProfileWith(userId, roomId, this.client);
            this.cache.set(cacheKey, profile);
            return profile;
        });
    }
    getUserProfileWith(userId, roomId, client) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                if (roomId) {
                    const membership = yield client.getRoomStateEvent(roomId, "m.room.member", userId);
                    return new __1.MatrixProfile(userId, membership);
                }
                else {
                    const profile = yield client.getUserProfile(userId);
                    return new __1.MatrixProfile(userId, profile);
                }
            }
            catch (e) {
                __1.LogService.warn("ProfileCache", "Non-fatal error getting user profile. They might not exist.");
                __1.LogService.warn("ProfileCache", e);
                return new __1.MatrixProfile(userId, {});
            }
        });
    }
    tryUpdateProfile(roomId, memberEvent, client) {
        return __awaiter(this, void 0, void 0, function* () {
            const roomCacheKey = this.getCacheKey(memberEvent.membershipFor, roomId);
            const generalCacheKey = this.getCacheKey(memberEvent.membershipFor, null);
            if (this.cache.has(roomCacheKey)) {
                this.cache.set(roomCacheKey, new __1.MatrixProfile(memberEvent.membershipFor, memberEvent.content));
            }
            // TODO: Try and figure out semantics for this updating.
            // Large accounts could cause hammering on the profile endpoint, but hopefully it is cached by the server.
            if (this.cache.has(generalCacheKey)) {
                const profile = yield this.getUserProfileWith(memberEvent.membershipFor, null, client);
                this.cache.set(generalCacheKey, profile);
            }
        });
    }
}
exports.ProfileCache = ProfileCache;
