import RE2 from "re2";
import { updateUserProfileData } from "../../../../services/i/update.js";
import { Users, DriveFiles, UserProfiles, Pages } from "../../../../models/index.js";
import { notificationTypes } from "../../../../types.js";
import { langmap } from "../../../../misc/langmap.js";
import { verifyLink } from "../../../../services/fetch-rel-me.js";
import { ApiError } from "../../error.js";
import define from "../../define.js";
export const meta = {
    tags: [
        "account"
    ],
    requireCredential: true,
    kind: "write:account",
    errors: {
        noSuchAvatar: {
            message: "No such avatar file.",
            code: "NO_SUCH_AVATAR",
            id: "539f3a45-f215-4f81-a9a8-31293640207f"
        },
        noSuchBanner: {
            message: "No such banner file.",
            code: "NO_SUCH_BANNER",
            id: "0d8f5629-f210-41c2-9433-735831a58595"
        },
        avatarNotAnImage: {
            message: "The file specified as an avatar is not an image.",
            code: "AVATAR_NOT_AN_IMAGE",
            id: "f419f9f8-2f4d-46b1-9fb4-49d3a2fd7191"
        },
        bannerNotAnImage: {
            message: "The file specified as a banner is not an image.",
            code: "BANNER_NOT_AN_IMAGE",
            id: "75aedb19-2afd-4e6d-87fc-67941256fa60"
        },
        noSuchPage: {
            message: "No such page.",
            code: "NO_SUCH_PAGE",
            id: "8e01b590-7eb9-431b-a239-860e086c408e"
        },
        invalidRegexp: {
            message: "Invalid Regular Expression.",
            code: "INVALID_REGEXP",
            id: "0d786918-10df-41cd-8f33-8dec7d9a89a5"
        },
        invalidFieldName: {
            message: "Invalid field name.",
            code: "INVALID_FIELD_NAME",
            id: "8f81972e-8b53-4d30-b0d2-efb026dda673"
        },
        invalidFieldValue: {
            message: "Invalid field value.",
            code: "INVALID_FIELD_VALUE",
            id: "aede7444-244b-11ee-be56-0242ac120002"
        }
    },
    res: {
        type: "object",
        optional: false,
        nullable: false,
        ref: "MeDetailed"
    }
};
export const paramDef = {
    type: "object",
    properties: {
        name: {
            ...Users.nameSchema,
            nullable: true
        },
        description: {
            ...Users.descriptionSchema,
            nullable: true
        },
        location: {
            ...Users.locationSchema,
            nullable: true
        },
        birthday: {
            ...Users.birthdaySchema,
            nullable: true
        },
        lang: {
            type: "string",
            enum: [
                null,
                ...Object.keys(langmap)
            ],
            nullable: true
        },
        avatarId: {
            type: "string",
            format: "misskey:id",
            nullable: true
        },
        bannerId: {
            type: "string",
            format: "misskey:id",
            nullable: true
        },
        fields: {
            type: "array",
            minItems: 0,
            maxItems: 16,
            items: {
                type: "object",
                properties: {
                    name: {
                        type: "string"
                    },
                    value: {
                        type: "string"
                    }
                },
                required: [
                    "name",
                    "value"
                ]
            }
        },
        isLocked: {
            type: "boolean"
        },
        isExplorable: {
            type: "boolean"
        },
        hideOnlineStatus: {
            type: "boolean"
        },
        publicReactions: {
            type: "boolean"
        },
        allowCalls: {
            type: "boolean"
        },
        symbolFileId: {
            type: "string",
            format: "misskey:id",
            nullable: true
        },
        carefulBot: {
            type: "boolean"
        },
        autoAcceptFollowed: {
            type: "boolean"
        },
        noCrawle: {
            type: "boolean"
        },
        preventAiLearning: {
            type: "boolean"
        },
        isBot: {
            type: "boolean"
        },
        isCat: {
            type: "boolean"
        },
        speakAsCat: {
            type: "boolean"
        },
        minorBadges: {
            type: "array",
            uniqueItems: true,
            maxItems: 1,
            items: {
                type: "string",
                enum: [
                    "K",
                    "T",
                    "E"
                ]
            }
        },
        injectFeaturedNote: {
            type: "boolean"
        },
        receiveAnnouncementEmail: {
            type: "boolean"
        },
        alwaysMarkNsfw: {
            type: "boolean"
        },
        ffVisibility: {
            type: "string",
            enum: [
                "public",
                "followers",
                "private"
            ]
        },
        pinnedPageId: {
            type: "string",
            format: "misskey:id",
            nullable: true
        },
        mutedWords: {
            type: "array"
        },
        mutedInstances: {
            type: "array",
            items: {
                type: "string"
            }
        },
        mutingNotificationTypes: {
            type: "array",
            items: {
                type: "string",
                enum: notificationTypes
            }
        },
        emailNotificationTypes: {
            type: "array",
            items: {
                type: "string"
            }
        },
        pronouns: {
            type: "object",
            nullable: true
        },
        canBite: {
            type: "string",
            enum: [
                "anyone",
                "followers",
                "nobody"
            ],
            nullable: true
        }
    }
};
export default define(meta, paramDef, async (ps, _user, token)=>{
    const user = await Users.findOneByOrFail({
        id: _user.id
    });
    const isSecure = token == null;
    const updates = {};
    const profileUpdates = {};
    const profile = await UserProfiles.findOneByOrFail({
        userId: user.id
    });
    if (ps.name !== undefined) updates.name = ps.name;
    if (ps.description !== undefined) profileUpdates.description = ps.description;
    if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
    if (ps.location !== undefined) profileUpdates.location = ps.location;
    if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
    if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility;
    if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
    if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
    if (ps.mutedWords !== undefined) {
        // validate regular expression syntax
        ps.mutedWords.filter((x)=>!Array.isArray(x)).forEach((x)=>{
            const regexp = x.match(/^\/(.+)\/(.*)$/);
            if (!regexp) throw new ApiError(meta.errors.invalidRegexp);
            try {
                new RE2(regexp[1], regexp[2]);
            } catch (err) {
                throw new ApiError(meta.errors.invalidRegexp);
            }
        });
        profileUpdates.mutedWords = ps.mutedWords;
        profileUpdates.enableWordMute = ps.mutedWords.length > 0;
    }
    if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances;
    if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes;
    if (typeof ps.isLocked === "boolean") updates.isLocked = ps.isLocked;
    if (typeof ps.isExplorable === "boolean") updates.isExplorable = ps.isExplorable;
    if (typeof ps.hideOnlineStatus === "boolean") updates.hideOnlineStatus = ps.hideOnlineStatus;
    if (typeof ps.publicReactions === "boolean") profileUpdates.publicReactions = ps.publicReactions;
    if (typeof ps.allowCalls === "boolean") profileUpdates.allowCalls = ps.allowCalls;
    if (ps.symbolFileId !== undefined) profileUpdates.symbolFileId = ps.symbolFileId;
    if (typeof ps.isBot === "boolean") updates.isBot = ps.isBot;
    if (ps.minorBadges !== undefined) updates.minorBadges = ps.minorBadges.slice(0, 1);
    if (typeof ps.carefulBot === "boolean") profileUpdates.carefulBot = ps.carefulBot;
    if (typeof ps.autoAcceptFollowed === "boolean") profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
    if (typeof ps.noCrawle === "boolean") profileUpdates.noCrawle = ps.noCrawle;
    if (typeof ps.preventAiLearning === "boolean") profileUpdates.preventAiLearning = ps.preventAiLearning;
    if (typeof ps.isCat === "boolean") updates.isCat = ps.isCat;
    if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat;
    if (typeof ps.injectFeaturedNote === "boolean") profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
    if (typeof ps.receiveAnnouncementEmail === "boolean") profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
    if (typeof ps.alwaysMarkNsfw === "boolean") profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
    if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
    if (typeof ps.pronouns === "object") {
        if (ps.pronouns === null) ps.pronouns = {};
        for (const key of Object.keys(ps.pronouns)){
            if (key.length !== 2 || typeof ps.pronouns[key] !== "string" || ps.pronouns[key].length === 0) {
                delete ps.pronouns[key];
            }
        }
        profileUpdates.pronouns = ps.pronouns;
    }
    const avatar = ps.avatarId ? await DriveFiles.findOneBy({
        id: ps.avatarId
    }) : null;
    const banner = ps.bannerId ? await DriveFiles.findOneBy({
        id: ps.bannerId
    }) : null;
    if (ps.avatarId) {
        if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
        if (!avatar.type.startsWith("image/")) throw new ApiError(meta.errors.avatarNotAnImage);
        updates.avatarUrl = DriveFiles.getDatabasePrefetchUrl(avatar, true);
        updates.avatarBlurhash = avatar.blurhash;
    }
    if (ps.bannerId) {
        if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
        if (!banner.type.startsWith("image/")) throw new ApiError(meta.errors.bannerNotAnImage);
        updates.bannerUrl = DriveFiles.getDatabasePrefetchUrl(banner, false);
        updates.bannerBlurhash = banner.blurhash;
    }
    if (ps.pinnedPageId) {
        const page = await Pages.findOneBy({
            id: ps.pinnedPageId
        });
        if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage);
        profileUpdates.pinnedPageId = page.id;
    } else if (ps.pinnedPageId === null) {
        profileUpdates.pinnedPageId = null;
    }
    if (ps.fields) {
        for (const field of ps.fields){
            if (!field || field.name === "" || field.value === "") {
                continue;
            }
            if (typeof field.name !== "string" || field.name === "") {
                throw new ApiError(meta.errors.invalidFieldName);
            }
            if (typeof field.value !== "string" || field.value === "") {
                throw new ApiError(meta.errors.invalidFieldValue);
            }
            if (field.value.startsWith("http")) {
                field.verified = await verifyLink(field.value, user.username);
            }
        }
        profileUpdates.fields = ps.fields.filter((x)=>Object.keys(x).length !== 0).map((x)=>{
            return {
                name: x.name,
                value: x.value,
                verified: x.verified
            };
        });
    }
    if (ps.canBite) {
        updates.canBite = ps.canBite;
    }
    return updateUserProfileData(user, profile, updates, profileUpdates, isSecure);
});
