import { Notes, Notifications, UserListJoinings } from "../../../../models/index.js";
import { Brackets } from "typeorm";
import { generateChannelQuery } from "../../common/generate-channel-query.js";
import { generateRepliesQuery } from "../../common/generate-replies-query.js";
import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
import { generateMutedUserRenotesQueryForNotes } from "../../common/generated-muted-renote-query.js";
import { fetchMeta } from "../../../../misc/fetch-meta.js";
import { PaginationHelpers } from "./pagination.js";
import { UserHelpers } from "./user.js";
import { UserConverter } from "../converters/user.js";
import { NoteConverter } from "../converters/note.js";
import { awaitAll } from "../../../../prelude/await-all.js";
import { unique } from "../../../../prelude/array.js";
import { MastoApiError } from "../middleware/catch-errors.js";
import { generatePaginationData } from "../middleware/pagination.js";
import { generateListQuery } from "../../common/generate-list-query.js";
import { generateFollowingQuery } from "../../common/generate-following-query.js";
export class TimelineHelpers {
    static async getHomeTimeline(maxId, sinceId, minId, limit = 20, ctx) {
        if (limit > 40) limit = 40;
        const user = ctx.user;
        const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, maxId, minId).leftJoinAndSelect("note.user", "user").leftJoinAndSelect("note.renote", "renote");
        await generateFollowingQuery(query, user);
        generateListQuery(query, user);
        generateChannelQuery(query, user);
        generateRepliesQuery(query, true, user);
        generateVisibilityQuery(query, user);
        generateMutedUserQuery(query, user);
        generateBlockedUserQuery(query, user);
        generateMutedUserRenotesQueryForNotes(query, user);
        query.andWhere("note.visibility != 'hidden'");
        return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
    }
    static async getPublicTimeline(maxId, sinceId, minId, limit = 20, onlyMedia = false, local = false, remote = false, ctx) {
        if (limit > 40) limit = 40;
        const user = ctx.user;
        if (local && remote) {
            throw new Error("local and remote are mutually exclusive options");
        }
        if (!local) {
            const m = await fetchMeta();
            if (m.disableGlobalTimeline) {
                if (user == null || !(user.isAdmin || user.isModerator)) {
                    throw new Error("global timeline is disabled");
                }
            }
        }
        const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, maxId, minId).andWhere("note.visibility = 'public'");
        if (remote) query.andWhere("note.userHost IS NOT NULL");
        if (local) query.andWhere("note.userHost IS NULL");
        if (!local) query.andWhere("note.channelId IS NULL");
        query.leftJoinAndSelect("note.user", "user").leftJoinAndSelect("note.renote", "renote");
        generateRepliesQuery(query, true, user);
        if (user) {
            generateMutedUserQuery(query, user);
            generateBlockedUserQuery(query, user);
            generateMutedUserRenotesQueryForNotes(query, user);
        }
        if (onlyMedia) query.andWhere("note.fileIds != '{}'");
        return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
    }
    static async getListTimeline(list, maxId, sinceId, minId, limit = 20, ctx) {
        if (limit > 40) limit = 40;
        const user = ctx.user;
        if (user.id != list.userId) throw new Error("List is not owned by user");
        const listQuery = UserListJoinings.createQueryBuilder("member").select("member.userId", 'userId').where("member.userListId = :listId");
        const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, maxId, minId).andWhere(`note.userId IN (${listQuery.getQuery()})`).andWhere("note.visibility != 'specified'").leftJoinAndSelect("note.user", "user").leftJoinAndSelect("note.renote", "renote").setParameters({
            listId: list.id
        });
        generateVisibilityQuery(query, user);
        return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
    }
    static async getTagTimeline(tag, maxId, sinceId, minId, limit = 20, any, all, none, onlyMedia = false, local = false, remote = false, ctx) {
        if (limit > 40) limit = 40;
        const user = ctx.user;
        if (tag.length < 1) throw new MastoApiError(400, "Tag cannot be empty");
        if (local && remote) {
            throw new Error("local and remote are mutually exclusive options");
        }
        const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, maxId, minId).andWhere("note.visibility = 'public'").andWhere("note.tags @> array[:tag]::varchar[]", {
            tag: tag
        });
        if (any.length > 0) query.andWhere("note.tags && array[:...any]::varchar[]", {
            any: any
        });
        if (all.length > 0) query.andWhere("note.tags @> array[:...all]::varchar[]", {
            all: all
        });
        if (none.length > 0) query.andWhere("NOT(note.tags @> array[:...none]::varchar[])", {
            none: none
        });
        if (remote) query.andWhere("note.userHost IS NOT NULL");
        if (local) query.andWhere("note.userHost IS NULL");
        if (!local) query.andWhere("note.channelId IS NULL");
        query.leftJoinAndSelect("note.user", "user").leftJoinAndSelect("note.renote", "renote");
        generateRepliesQuery(query, true, user);
        if (user) {
            generateMutedUserQuery(query, user);
            generateBlockedUserQuery(query, user);
            generateMutedUserRenotesQueryForNotes(query, user);
        }
        if (onlyMedia) query.andWhere("note.fileIds != '{}'");
        return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
    }
    static async getConversations(maxId, sinceId, minId, limit = 20, ctx) {
        if (limit > 40) limit = 40;
        const user = ctx.user;
        const sq = Notes.createQueryBuilder("note").select("COALESCE(note.threadId, note.id)", "conversationId").addSelect("note.id", "latest").distinctOn([
            "COALESCE(note.threadId, note.id)"
        ]).orderBy({
            "COALESCE(note.threadId, note.id)": minId ? "ASC" : "DESC",
            "note.id": "DESC"
        }).andWhere("note.visibility = 'specified'").andWhere(new Brackets((qb)=>{
            qb.where("note.userId = :userId");
            qb.orWhere("note.visibleUserIds @> array[:userId]::varchar[]");
        }));
        const query = PaginationHelpers.makePaginationQuery(Notes.createQueryBuilder("note"), sinceId, maxId, minId).innerJoin(`(${sq.getQuery()})`, "sq", "note.id = sq.latest").setParameters({
            userId: user.id
        });
        return query.take(limit).getMany().then((p)=>{
            if (minId !== undefined) p = p.reverse();
            const conversations = p.map((c)=>{
                // Gather all unique IDs except for the local user
                const userIds = unique([
                    c.userId
                ].concat(c.visibleUserIds).filter((p)=>p != user.id));
                const users = userIds.map((id)=>UserHelpers.getUserCached(id, ctx).catch((_)=>null));
                const accounts = Promise.all(users).then((u)=>UserConverter.encodeMany(u.filter((u)=>u), ctx));
                const unread = Notifications.createQueryBuilder('notification').where("notification.noteId = :noteId").andWhere("notification.notifieeId = :userId").andWhere("notification.isRead = FALSE").andWhere("notification.type IN (:...types)").setParameter("noteId", c.id).setParameter("userId", user.id).setParameter("types", [
                    'reply',
                    'mention'
                ]).getExists();
                return {
                    id: c.threadId ?? c.id,
                    accounts: accounts.then((u)=>u.length > 0 ? u : UserConverter.encodeMany([
                            user
                        ], ctx)),
                    last_status: NoteConverter.encode(c, ctx),
                    unread: unread
                };
            });
            ctx.pagination = generatePaginationData(p.map((p)=>p.threadId ?? p.id), limit);
            return Promise.all(conversations.map((c)=>awaitAll(c)));
        });
    }
}
