"use strict";
// SPDX-FileCopyrightText: 2025 Gnuxie <Gnuxie@protonmail.com>
//
// SPDX-License-Identifier: Apache-2.0
Object.defineProperty(exports, "__esModule", { value: true });
exports.matchMembers = matchMembers;
exports.revisionRulesMatchingEntity = revisionRulesMatchingEntity;
exports.revisionRulesMatchingUser = revisionRulesMatchingUser;
exports.findBanPoliciesMatchingUsers = findBanPoliciesMatchingUsers;
exports.unbanMembers = unbanMembers;
exports.findUnbanInformationForMember = findUnbanInformationForMember;
const matrix_basic_types_1 = require("@the-draupnir-project/matrix-basic-types");
const matrix_protection_suite_1 = require("matrix-protection-suite");
function matchMembers(setRoomMembership, matches, options) {
    const map = new Map();
    const addRoomMembership = (membership, room) => {
        const isToInvite = (() => {
            if (!options.inviteMembers) {
                return false;
            }
            switch (membership.membership) {
                case matrix_protection_suite_1.Membership.Ban:
                case matrix_protection_suite_1.Membership.Leave:
                    return membership.userID !== membership.sender;
                default:
                    return false;
            }
        })();
        const isToUnban = membership.membership === matrix_protection_suite_1.Membership.Ban;
        if (!isToInvite && !isToUnban) {
            return;
        }
        const entry = map.get(membership.userID);
        if (entry === undefined) {
            map.set(membership.userID, {
                member: membership.userID,
                roomsBannedFrom: isToUnban ? [room] : [],
                roomsToInviteTo: isToInvite ? [room] : [],
            });
        }
        else {
            if (isToInvite) {
                entry.roomsToInviteTo.push(room);
            }
            if (isToUnban) {
                entry.roomsBannedFrom.push(room);
            }
        }
    };
    for (const revision of setRoomMembership.allRooms) {
        for (const membership of revision.members()) {
            if (matches.literals.includes(membership.userID) ||
                matches.globs.some((glob) => glob.test(membership.userID))) {
                addRoomMembership(membership, revision.room);
            }
        }
    }
    return [...map.values()];
}
function revisionRulesMatchingEntity(entity, ruleType, recommendations, revision) {
    return recommendations.flatMap((recommendation) => revision.allRulesMatchingEntity(entity, {
        type: ruleType,
        recommendation,
        searchHashedRules: true,
    }));
}
function revisionRulesMatchingUser(userID, recommendations, revision) {
    return [
        ...revisionRulesMatchingEntity(userID, matrix_protection_suite_1.PolicyRuleType.User, recommendations, revision),
        ...revisionRulesMatchingEntity((0, matrix_basic_types_1.userServerName)(userID), matrix_protection_suite_1.PolicyRuleType.Server, recommendations, revision),
    ];
}
function findBanPoliciesMatchingUsers(watchedPolicyRooms, users) {
    const policies = new Map();
    const addPolicy = (policyRule) => {
        const entry = policies.get(policyRule.sourceEvent.room_id);
        if (entry === undefined) {
            policies.set(policyRule.sourceEvent.room_id, new Set([policyRule]));
        }
        else {
            entry.add(policyRule);
        }
    };
    for (const user of users) {
        const memberPolicies = revisionRulesMatchingUser(user, [matrix_protection_suite_1.Recommendation.Ban, matrix_protection_suite_1.Recommendation.Takedown], watchedPolicyRooms.currentRevision);
        for (const policy of memberPolicies) {
            addPolicy(policy);
        }
    }
    return [...policies.entries()].map(([roomID, matches]) => {
        const profile = watchedPolicyRooms.allRooms.find((profile) => profile.room.toRoomIDOrAlias() === roomID);
        if (profile === undefined) {
            throw new TypeError(`Shouldn't be possible to have sourced policies from an unwatched list`);
        }
        return {
            room: profile.room,
            roomID: profile.room.toRoomIDOrAlias(),
            revision: profile.revision,
            profile,
            matches: [...matches],
        };
    });
}
async function unbanMembers(members, capabilities, options) {
    const policiesRemoved = new matrix_protection_suite_1.RoomSetResultBuilder();
    const unbanResultBuilder = new matrix_protection_suite_1.ResultForUsersInSetBuilder();
    const invitationsSent = new matrix_protection_suite_1.ResultForUsersInSetBuilder();
    for (const policyRoom of members.policyMatchesToRemove) {
        const policyRoomEditor = await capabilities.policyRoomManager.getPolicyRoomEditor(policyRoom.room);
        if ((0, matrix_protection_suite_1.isError)(policyRoomEditor)) {
            policiesRemoved.addResult(policyRoom.roomID, policyRoomEditor);
        }
        else {
            for (const policy of policyRoom.matches) {
                policiesRemoved.addResult(policyRoom.roomID, await policyRoomEditor.ok.removePolicyByStateKey(policy.kind, policy.sourceEvent.state_key));
            }
        }
    }
    // There's no point in unbanning and inviting if policies are still enacted against users.
    if (!policiesRemoved.getResult().isEveryResultOk) {
        return (0, matrix_protection_suite_1.Ok)({
            ...members,
            policyRemovalResult: policiesRemoved.getResult(),
            usersUnbanned: unbanResultBuilder.getResult(),
            usersInvited: invitationsSent.getResult(),
        });
    }
    for (const member of members.membersToUnban) {
        capabilities.unlistedUserRedactionQueue.removeUser(member.member);
        for (const room of member.roomsBannedFrom) {
            unbanResultBuilder.addResult(member.member, room.toRoomIDOrAlias(), await capabilities.roomUnbanner.unbanUser(room, member.member));
        }
        for (const room of member.roomsToInviteTo) {
            if (options.inviteMembers) {
                invitationsSent.addResult(member.member, room.toRoomIDOrAlias(), await capabilities.roomInviter.inviteUser(room, member.member));
            }
        }
    }
    return (0, matrix_protection_suite_1.Ok)({
        ...members,
        policyRemovalResult: policiesRemoved.getResult(),
        usersUnbanned: unbanResultBuilder.getResult(),
        usersInvited: invitationsSent.getResult(),
    });
}
function findUnbanInformationForMember(setRoomMembership, entity, watchedPolicyRooms, { inviteMembers }) {
    const membersMatchingEntity = matchMembers(setRoomMembership, {
        ...(entity.isContainingGlobCharacters()
            ? { globs: [new matrix_basic_types_1.MatrixGlob(entity.toString())], literals: [] }
            : { literals: [entity.toString()], globs: [] }),
    }, { inviteMembers });
    // we need to also search for policies that match the literal rule given to us
    const policyEntitiesToSearchFor = new Set([
        ...membersMatchingEntity.map((memberRooms) => memberRooms.member),
        entity.toString(),
    ]);
    const policyMatchesToRemove = findBanPoliciesMatchingUsers(watchedPolicyRooms, [...policyEntitiesToSearchFor]);
    const globsToScan = [
        ...(entity.isContainingGlobCharacters()
            ? [new matrix_basic_types_1.MatrixGlob(entity.toString())]
            : []),
    ];
    const literalsToScan = [
        ...(entity.isContainingGlobCharacters() ? [] : [entity.toString()]),
    ];
    for (const { matches } of policyMatchesToRemove) {
        for (const match of matches) {
            if (match.matchType === matrix_protection_suite_1.PolicyRuleMatchType.Glob) {
                globsToScan.push(new matrix_basic_types_1.MatrixGlob(match.entity));
            }
            else if (match.matchType === matrix_protection_suite_1.PolicyRuleMatchType.Literal &&
                (0, matrix_basic_types_1.isStringUserID)(match.entity)) {
                literalsToScan.push(match.entity);
            }
            // HashedLiterals that match will be removed indriectly when their sourceEvent's
            // get removed.
        }
    }
    const membersMatchingPoliciesAndEntity = matchMembers(setRoomMembership, { globs: globsToScan, literals: literalsToScan }, { inviteMembers });
    const isMemberStillBannedAfterPolicyRemoval = (member) => {
        const policiesMatchingMember = revisionRulesMatchingUser(member.member, [matrix_protection_suite_1.Recommendation.Takedown, matrix_protection_suite_1.Recommendation.Ban], watchedPolicyRooms.currentRevision);
        return (policiesMatchingMember.filter((policy) => !policyMatchesToRemove
            .flatMap((list) => list.matches)
            .includes(policy)).length > 0);
    };
    // Now we need to filter out only members that are completly free of policies.
    const membersToUnban = membersMatchingPoliciesAndEntity.filter((member) => !isMemberStillBannedAfterPolicyRemoval(member));
    const unbanInformation = {
        policyMatchesToRemove,
        membersToUnban,
        entity,
    };
    return unbanInformation;
}
//# sourceMappingURL=UnbanUsers.js.map