import { Blockings, Followings, UserListJoinings, UserLists } from "../../../../models/index.js";
import { PaginationHelpers } from "./pagination.js";
import { pushUserToUserList } from "../../../../services/user-list/push.js";
import { genId } from "../../../../misc/gen-id.js";
import { MastoApiError } from "../middleware/catch-errors.js";
import { pullUserFromUserList } from "../../../../services/user-list/pull.js";
import { publishUserEvent } from "../../../../services/stream.js";
export class ListHelpers {
    static async getLists(ctx) {
        const user = ctx.user;
        return UserLists.findBy({
            userId: user.id
        }).then((p)=>p.map((list)=>{
                return {
                    id: list.id,
                    title: list.name,
                    exclusive: list.hideFromHomeTl
                };
            }));
    }
    static async getList(id, ctx) {
        const user = ctx.user;
        return UserLists.findOneByOrFail({
            userId: user.id,
            id: id
        }).then((list)=>{
            return {
                id: list.id,
                title: list.name,
                exclusive: list.hideFromHomeTl
            };
        });
    }
    static async getListOr404(id, ctx) {
        return this.getList(id, ctx).catch((_)=>{
            throw new MastoApiError(404);
        });
    }
    static async getListUsers(id, maxId, sinceId, minId, limit = 40, ctx) {
        if (limit > 80) limit = 80;
        const user = ctx.user;
        const list = await UserLists.findOneBy({
            userId: user.id,
            id: id
        });
        if (!list) throw new MastoApiError(404);
        const query = PaginationHelpers.makePaginationQuery(UserListJoinings.createQueryBuilder('member'), sinceId, maxId, minId).andWhere("member.userListId = :listId", {
            listId: list.id
        }).innerJoinAndSelect("member.user", "user");
        return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx).then((members)=>{
            return members.map((p)=>p.user).filter((p)=>p);
        });
    }
    static async deleteList(list, ctx) {
        const user = ctx.user;
        if (user.id != list.userId) throw new Error("List is not owned by user");
        await UserLists.delete(list.id);
    }
    static async addToList(list, usersToAdd, ctx) {
        const localUser = ctx.user;
        if (localUser.id != list.userId) throw new Error("List is not owned by user");
        for (const user of usersToAdd){
            if (user.id !== localUser.id) {
                const isBlocked = await Blockings.exist({
                    where: {
                        blockerId: user.id,
                        blockeeId: localUser.id
                    }
                });
                const isFollowed = await Followings.exist({
                    where: {
                        followeeId: user.id,
                        followerId: localUser.id
                    }
                });
                if (isBlocked) throw Error("Can't add users you've been blocked by to list");
                if (!isFollowed) throw Error("Can't add users you're not following to list");
            }
            const exist = await UserListJoinings.exist({
                where: {
                    userListId: list.id,
                    userId: user.id
                }
            });
            if (exist) continue;
            await pushUserToUserList(user, list);
        }
    }
    static async removeFromList(list, usersToRemove, ctx) {
        const localUser = ctx.user;
        if (localUser.id != list.userId) throw new Error("List is not owned by user");
        for (const user of usersToRemove){
            const exist = await UserListJoinings.exist({
                where: {
                    userListId: list.id,
                    userId: user.id
                }
            });
            if (!exist) continue;
            await pullUserFromUserList(user, list);
        }
    }
    static async createList(title, ctx) {
        if (title.length < 1) throw new MastoApiError(400, "Title must not be empty");
        const user = ctx.user;
        const list = await UserLists.insert({
            id: genId(),
            createdAt: new Date(),
            userId: user.id,
            name: title
        }).then(async (res)=>await UserLists.findOneByOrFail(res.identifiers[0]));
        return {
            id: list.id,
            title: list.name,
            exclusive: list.hideFromHomeTl
        };
    }
    static async updateList(list, title, exclusive, ctx) {
        if (title.length < 1 && exclusive === undefined) throw new MastoApiError(400, "Either title or exclusive must be set");
        const user = ctx.user;
        if (user.id != list.userId) throw new Error("List is not owned by user");
        const name = title.length > 0 ? title : undefined;
        const partial = {
            name: name,
            hideFromHomeTl: exclusive
        };
        const result = await UserLists.update(list.id, partial).then(async (_)=>await UserLists.findOneByOrFail({
                id: list.id
            }));
        if (exclusive !== undefined) {
            UserListJoinings.findBy({
                userListId: list.id
            }).then((members)=>{
                for (const member of members){
                    publishUserEvent(list.userId, exclusive ? "userHidden" : "userUnhidden", member.userId);
                }
            });
        }
        return {
            id: result.id,
            title: result.name,
            exclusive: result.hideFromHomeTl
        };
    }
    static async getListsByMember(member, ctx) {
        const user = ctx.user;
        const joinQuery = UserListJoinings.createQueryBuilder('member').select("member.userListId").where("member.userId = :memberId");
        const query = UserLists.createQueryBuilder('list').where("list.userId = :userId", {
            userId: user.id
        }).andWhere(`list.id IN (${joinQuery.getQuery()})`).setParameters({
            memberId: member.id
        });
        return query.getMany().then((results)=>results.map((result)=>{
                return {
                    id: result.id,
                    title: result.name,
                    exclusive: result.hideFromHomeTl
                };
            }));
    }
}
