"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const QueuePool_1 = require("../util/QueuePool");
const logging_1 = __importDefault(require("../logging"));
const log = logging_1.default("BridgeStateSyncer");
const SYNC_CONCURRENCY = 3;
/**
 * This class will set bridge room state according to [MSC2346](https://github.com/matrix-org/matrix-doc/pull/2346)
 */
class BridgeStateSyncer {
    constructor(datastore, bridge, ircBridge) {
        this.datastore = datastore;
        this.bridge = bridge;
        this.ircBridge = ircBridge;
        this.syncQueue = new QueuePool_1.QueuePool(SYNC_CONCURRENCY, this.syncRoom.bind(this));
    }
    async beginSync() {
        log.info("Beginning sync of bridge state events");
        const allMappings = await this.datastore.getAllChannelMappings();
        Object.entries(allMappings).forEach(([roomId, mappings]) => {
            this.syncQueue.enqueue(roomId, { roomId, mappings });
        });
    }
    async syncRoom(item) {
        log.info(`Syncing ${item.roomId}`);
        const intent = this.bridge.getIntent();
        for (const mapping of item.mappings) {
            const key = BridgeStateSyncer.createStateKey(mapping.networkId, mapping.channel);
            try {
                const eventData = await this.getStateEvent(item.roomId, BridgeStateSyncer.EventType, key);
                if (eventData !== null) { // If found, validate.
                    const expectedContent = this.createBridgeInfoContent(mapping.networkId, mapping.channel);
                    const isValid = expectedContent.channel.id === eventData.channel.id &&
                        expectedContent.network.id === eventData.network.id &&
                        expectedContent.network.displayname === eventData.network.displayname &&
                        expectedContent.protocol.id === eventData.protocol.id &&
                        expectedContent.protocol.displayname === eventData.protocol.displayname;
                    if (isValid) {
                        log.debug(`${key} is valid`);
                        continue;
                    }
                    log.info(`${key} is invalid`);
                }
            }
            catch (ex) {
                log.warn(`Encountered error when trying to sync ${item.roomId}`);
                break; // To be on the safe side, do not retry this room.
            }
            // Event wasn't found or was invalid, let's try setting one.
            const eventContent = this.createBridgeInfoContent(mapping.networkId, mapping.channel);
            const owner = await this.determineProvisionedOwner(item.roomId, mapping.networkId, mapping.channel);
            eventContent.creator = owner || intent.client.credentials.userId;
            try {
                await intent.sendStateEvent(item.roomId, BridgeStateSyncer.EventType, key, eventContent);
            }
            catch (ex) {
                log.error(`Failed to update room with new state content: ${ex.message}`);
            }
        }
    }
    createInitialState(server, channel, owner) {
        return {
            type: BridgeStateSyncer.EventType,
            content: this.createBridgeInfoContent(server, channel, owner),
            state_key: BridgeStateSyncer.createStateKey(server.domain, channel)
        };
    }
    static createStateKey(networkId, channel) {
        networkId = networkId.replace(/\//g, "%2F");
        channel = channel.replace(/\//g, "%2F");
        return `org.matrix.appservice-irc://irc/${networkId}/${channel}`;
    }
    createBridgeInfoContent(networkIdOrServer, channel, creator) {
        const server = typeof (networkIdOrServer) === "string" ?
            this.ircBridge.getServer(networkIdOrServer) : networkIdOrServer;
        if (!server) {
            throw Error("Server not known");
        }
        const serverName = server.getReadableName();
        return {
            creator: creator || "",
            protocol: {
                id: "irc",
                displayname: "IRC",
            },
            network: {
                id: server.domain,
                displayname: serverName,
            },
            channel: {
                id: channel,
                external_url: `irc://${server.domain}/${channel}`
            }
        };
    }
    async determineProvisionedOwner(roomId, networkId, channel) {
        const room = await this.datastore.getRoom(roomId, networkId, channel);
        if (!room || room.data.origin !== "provision") {
            return null;
        }
        // Find out who dun it
        try {
            const ev = await this.getStateEvent(roomId, "m.room.bridging", `irc://${networkId}/${channel}`);
            if ((ev === null || ev === void 0 ? void 0 : ev.status) === "success") {
                return ev.user_id;
            }
            // Event not found or invalid, leave blank.
        }
        catch (ex) {
            log.warn(`Failed to get m.room.bridging information for room: ${ex.message}`);
        }
        return null;
    }
    async getStateEvent(roomId, eventType, key) {
        const intent = this.bridge.getIntent();
        try {
            return await intent.getStateEvent(roomId, eventType, key);
        }
        catch (ex) {
            if (ex.errcode !== "M_NOT_FOUND") {
                throw ex;
            }
        }
        return null;
    }
}
exports.BridgeStateSyncer = BridgeStateSyncer;
BridgeStateSyncer.EventType = "uk.half-shot.bridge";
//# sourceMappingURL=BridgeStateSyncer.js.map