import config from "../../../../config/index.js";
import { FILE_TYPE_BROWSERSAFE, MAX_NOTE_TEXT_LENGTH } from "../../../../const.js";
import { fetchMeta } from "../../../../misc/fetch-meta.js";
import { AnnouncementReads, Announcements, Emojis, Instances, Notes, UserProfiles, Users } from "../../../../models/index.js";
import { IsNull } from "typeorm";
import { awaitAll } from "../../../../prelude/await-all.js";
import { UserConverter } from "../converters/user.js";
import { AnnouncementConverter } from "../converters/announcement.js";
import { genId } from "../../../../misc/gen-id.js";
import * as Acct from "../../../../misc/acct.js";
import { UserHelpers } from "./user.js";
import { generateMutedUserQueryForUsers } from "../../common/generate-muted-user-query.js";
import { generateBlockQueryForUsers } from "../../common/generate-block-query.js";
import { uniqBy } from "../../../../prelude/array.js";
import { EmojiConverter } from "../converters/emoji.js";
import { populateEmojis } from "../../../../misc/populate-emojis.js";
import { NoteConverter } from "../converters/note.js";
import { VisibilityConverter } from "../converters/visibility.js";
export class MiscHelpers {
    static async getInstance(ctx) {
        const userCount = Users.count({
            where: {
                host: IsNull()
            }
        });
        const noteCount = Notes.count({
            where: {
                userHost: IsNull()
            }
        });
        const instanceCount = Instances.count({
            cache: 3600000
        });
        const contact = await Users.findOne({
            where: {
                host: IsNull(),
                isAdmin: true,
                isDeleted: false,
                isSuspended: false
            },
            order: {
                id: "ASC"
            }
        }).then((p)=>p ? UserConverter.encode(p, ctx) : null);
        const meta = await fetchMeta(true);
        const res = {
            uri: config.domain,
            title: meta.name || "Iceshrimp",
            short_description: meta.description?.substring(0, 50) || "This is an Iceshrimp instance. It doesn't seem to have a description.",
            description: meta.description || "This is an Iceshrimp instance. It doesn't seem to have a description.",
            email: meta.maintainerEmail || "",
            version: `4.2.1 (compatible; Iceshrimp ${config.version})`,
            urls: {
                streaming_api: `${config.url.replace(/^http(?=s?:\/\/)/, "ws")}`
            },
            stats: awaitAll({
                user_count: userCount,
                status_count: noteCount,
                domain_count: instanceCount
            }),
            max_toot_chars: MAX_NOTE_TEXT_LENGTH,
            thumbnail: meta.bannerUrl || "/static-assets/transparent.png",
            languages: meta.langs,
            registrations: !meta.disableRegistration,
            approval_required: meta.disableRegistration,
            invites_enabled: meta.disableRegistration,
            configuration: {
                accounts: {
                    max_featured_tags: 20
                },
                statuses: {
                    supported_mime_types: [
                        'text/x.misskeymarkdown'
                    ],
                    max_characters: MAX_NOTE_TEXT_LENGTH,
                    max_media_attachments: 16,
                    characters_reserved_per_url: 23
                },
                media_attachments: {
                    supported_mime_types: FILE_TYPE_BROWSERSAFE,
                    image_size_limit: 10485760,
                    image_matrix_limit: 16777216,
                    video_size_limit: 41943040,
                    video_frame_limit: 60,
                    video_matrix_limit: 2304000
                },
                polls: {
                    max_options: 10,
                    max_characters_per_option: 50,
                    min_expiration: 50,
                    max_expiration: 2629746
                },
                reactions: {
                    max_reactions: 1,
                    default_reaction: meta.defaultReaction
                }
            },
            contact_account: contact,
            rules: []
        };
        return awaitAll(res);
    }
    static async getAnnouncements(includeRead = false, ctx) {
        const user = ctx.user;
        if (includeRead) {
            const [announcements, reads] = await Promise.all([
                Announcements.createQueryBuilder("announcement").orderBy({
                    "announcement.id": "DESC"
                }).getMany(),
                AnnouncementReads.findBy({
                    userId: user.id
                }).then((p)=>p.map((x)=>x.announcementId))
            ]);
            return Promise.all(announcements.map(async (p)=>AnnouncementConverter.encode(p, reads.includes(p.id))));
        }
        const sq = AnnouncementReads.createQueryBuilder("reads").select("reads.announcementId").where("reads.userId = :userId");
        const query = Announcements.createQueryBuilder("announcement").where(`announcement.id NOT IN (${sq.getQuery()})`).orderBy({
            "announcement.id": "DESC"
        }).setParameter("userId", user.id);
        return query.getMany().then((p)=>Promise.all(p.map(async (x)=>AnnouncementConverter.encode(x, false))));
    }
    static async dismissAnnouncement(announcement, ctx) {
        const user = ctx.user;
        const exists = await AnnouncementReads.exist({
            where: {
                userId: user.id,
                announcementId: announcement.id
            }
        });
        if (!exists) {
            await AnnouncementReads.insert({
                id: genId(),
                createdAt: new Date(),
                userId: user.id,
                announcementId: announcement.id
            });
        }
    }
    static async getFollowSuggestions(limit, ctx) {
        const user = ctx.user;
        const results = [];
        const pinned = fetchMeta().then((meta)=>Promise.all(meta.pinnedUsers.map((acct)=>Acct.parse(acct)).map((acct)=>Users.findOneBy({
                    usernameLower: acct.username.toLowerCase(),
                    host: acct.host ?? IsNull()
                }))).then((p)=>p.filter((x)=>!!x)).then((p)=>UserConverter.encodeMany(p, ctx)).then((p)=>p.map((x)=>{
                    return {
                        source: "staff",
                        account: x
                    };
                })));
        const query = Users.createQueryBuilder("user").where("user.isExplorable = TRUE").andWhere("user.host IS NULL").orderBy("user.followersCount", "DESC").andWhere("user.updatedAt > :date", {
            date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5)
        });
        generateMutedUserQueryForUsers(query, user);
        generateBlockQueryForUsers(query, user);
        const global = query.take(limit).getMany().then((p)=>UserConverter.encodeMany(p, ctx)).then((p)=>p.map((x)=>{
                return {
                    source: "global",
                    account: x
                };
            }));
        results.push(pinned);
        results.push(global);
        return Promise.all(results).then((p)=>uniqBy(p.flat(), (x)=>x.account.id).slice(0, limit));
    }
    static async getCustomEmoji() {
        return Emojis.find({
            where: {
                host: IsNull()
            },
            order: {
                category: "ASC",
                name: "ASC"
            },
            cache: {
                id: "meta_emojis",
                milliseconds: 3600000
            }
        }).then((dbRes)=>populateEmojis(dbRes.map((p)=>p.name), null).then((p)=>p.map((x)=>EmojiConverter.encode(x)).map((x)=>{
                    return {
                        ...x,
                        category: dbRes.find((y)=>y.name === x.shortcode)?.category ?? undefined
                    };
                })));
    }
    static async getTrendingStatuses(limit = 20, offset = 0, ctx) {
        if (limit > 40) limit = 40;
        const query = Notes.createQueryBuilder("note").addSelect("note.score").andWhere("note.score > 0").andWhere("note.createdAt > :date", {
            date: new Date(Date.now() - 1000 * 60 * 60 * 24)
        }).andWhere("note.visibility = 'public'").andWhere("note.userHost IS NULL").orderBy("note.score", "DESC");
        return query.skip(offset).take(limit).getMany().then((result)=>NoteConverter.encodeMany(result, ctx));
    }
    static async getTrendingHashtags(limit = 10, offset = 0) {
        if (limit > 20) limit = 20;
        return [];
    //FIXME: This was already implemented in api/endpoints/hashtags/trend.ts, but the implementation is sketchy at best. Rewrite from scratch.
    }
    static getPreferences(ctx) {
        const user = ctx.user;
        const profile = UserProfiles.findOneByOrFail({
            userId: user.id
        });
        const sensitive = profile.then((p)=>p.alwaysMarkNsfw);
        const language = profile.then((p)=>p.lang);
        const privacy = UserHelpers.getDefaultNoteVisibility(ctx).then((p)=>VisibilityConverter.encode(p));
        const res = {
            "posting:default:visibility": privacy,
            "posting:default:sensitive": sensitive,
            "posting:default:language": language,
            "reading:expand:media": "default",
            "reading:expand:spoilers": false //FIXME: store this on server instead of client
        };
        return awaitAll(res);
    }
}
