import { db } from "../../db/postgre.js";
import { DriveFile } from "../entities/drive-file.js";
import { toPuny } from "../../misc/convert-host.js";
import { awaitAll } from "../../prelude/await-all.js";
import config from "../../config/index.js";
import { appendQuery, query } from "../../prelude/url.js";
import { DriveFolders, Users } from "../index.js";
import { deepClone } from "../../misc/clone.js";
import { fetchMetaSync } from "../../misc/fetch-meta.js";
export const DriveFileRepository = db.getRepository(DriveFile).extend({
    validateFileName (name) {
        return name.trim().length > 0 && name.length <= 200 && name.indexOf("\\") === -1 && name.indexOf("/") === -1 && name.indexOf("..") === -1;
    },
    getPublicProperties (file) {
        if (file.properties.orientation != null) {
            const properties = deepClone(file.properties);
            if (file.properties.orientation >= 5) {
                [properties.width, properties.height] = [
                    properties.height,
                    properties.width
                ];
            }
            properties.orientation = undefined;
            return properties;
        }
        return file.properties;
    },
    isImage (file) {
        return !!file.type && [
            "image/png",
            "image/apng",
            "image/gif",
            "image/jpeg",
            "image/webp",
            "image/svg+xml",
            "image/avif"
        ].includes(file.type);
    },
    isStreamableMedia (file) {
        return file.type.startsWith("video/") || file.type.startsWith("audio/");
    },
    withStreamQuery (file, url) {
        if (url == null || file.allowDownload || !this.isStreamableMedia(file)) return url;
        return appendQuery(url, query({
            stream: "1"
        }));
    },
    getPublicUrl (file, thumbnail = false) {
        // リモートかつメディアプロキシ
        if (file.uri != null && file.userHost != null && config.mediaProxy != null) {
            return appendQuery(config.mediaProxy, query({
                url: file.uri,
                thumbnail: thumbnail ? "1" : undefined
            }));
        }
        if (file.isLink && config.proxyRemoteFiles) {
            const url = this.getDatabasePrefetchUrl(file, thumbnail);
            if (url != null) return `${config.url}/proxy/${encodeURIComponent(new URL(url).pathname)}?${query({
                url: url
            })}`;
        }
        const url = thumbnail ? file.thumbnailUrl || (this.isImage(file) ? file.webpublicUrl || file.url : null) : file.webpublicUrl || file.url;
        return thumbnail ? url : this.withStreamQuery(file, url);
    },
    getDatabasePrefetchUrl (file, thumbnail = false) {
        return thumbnail ? file.thumbnailUrl ?? file.webpublicUrl ?? file.url : file.webpublicUrl ?? file.url;
    },
    getFinalUrl (url) {
        if (!config.proxyRemoteFiles) return url;
        if (!url.startsWith('https://') && !url.startsWith('http://')) return url;
        if (url.startsWith(`${config.url}/files`)) return url;
        if (url.startsWith(`${config.url}/static-assets`)) return url;
        if (url.startsWith(`${config.url}/identicon`)) return url;
        if (url.startsWith(`${config.url}/avatar`)) return url;
        const meta = fetchMetaSync();
        const baseUrl = meta ? meta.objectStorageBaseUrl ?? `${meta.objectStorageUseSSL ? "https" : "http"}://${meta.objectStorageEndpoint}${meta.objectStoragePort ? `:${meta.objectStoragePort}` : ""}/${meta.objectStorageBucket}` : null;
        if (baseUrl !== null && url.startsWith(baseUrl)) return url;
        return `${config.url}/proxy/${encodeURIComponent(new URL(url).pathname)}?${query({
            url: url
        })}`;
    },
    getFinalUrlMaybe (url) {
        if (url == null) return null;
        return this.getFinalUrl(url);
    },
    async calcDriveUsageOf (user) {
        const id = typeof user === "object" ? user.id : user;
        const { sum } = await this.createQueryBuilder("file").where("file.userId = :id", {
            id: id
        }).andWhere("file.isLink = FALSE").andWhere("file.isDatabase = FALSE").select("SUM(file.size)", "sum").getRawOne();
        return parseInt(sum, 10) || 0;
    },
    async calcDatabaseUsageOf (user) {
        const id = typeof user === "object" ? user.id : user;
        const { sum } = await this.createQueryBuilder("file").where("file.userId = :id", {
            id: id
        }).andWhere("file.isLink = FALSE").andWhere("file.isDatabase = TRUE").select("SUM(file.size)", "sum").getRawOne();
        return parseInt(sum, 10) || 0;
    },
    async calcDriveUsageOfHost (host) {
        const { sum } = await this.createQueryBuilder("file").where("file.userHost = :host", {
            host: toPuny(host)
        }).andWhere("file.isLink = FALSE").andWhere("file.isDatabase = FALSE").select("SUM(file.size)", "sum").getRawOne();
        return parseInt(sum, 10) || 0;
    },
    async calcDriveUsageOfLocal () {
        const { sum } = await this.createQueryBuilder("file").where("file.userHost IS NULL").andWhere("file.isLink = FALSE").andWhere("file.isDatabase = FALSE").select("SUM(file.size)", "sum").getRawOne();
        return parseInt(sum, 10) || 0;
    },
    async calcDriveUsageOfRemote () {
        const { sum } = await this.createQueryBuilder("file").where("file.userHost IS NOT NULL").andWhere("file.isLink = FALSE").andWhere("file.isDatabase = FALSE").select("SUM(file.size)", "sum").getRawOne();
        return parseInt(sum, 10) || 0;
    },
    async pack (src, options) {
        const opts = Object.assign({
            detail: false,
            self: false
        }, options);
        const file = typeof src === "object" ? src : await this.findOneByOrFail({
            id: src
        });
        return await awaitAll({
            id: file.id,
            createdAt: file.createdAt.toISOString(),
            name: file.name,
            type: file.type,
            md5: file.md5,
            size: file.size,
            isSensitive: file.isSensitive,
            allowDownload: file.allowDownload,
            isDatabase: file.isDatabase,
            blurhash: file.blurhash,
            properties: opts.self ? file.properties : this.getPublicProperties(file),
            url: opts.self ? file.url : this.getPublicUrl(file, false),
            thumbnailUrl: this.getPublicUrl(file, true),
            comment: file.comment,
            folderId: file.folderId,
            folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
                detail: true
            }) : null,
            userId: opts.withUser ? file.userId : null,
            user: opts.withUser && file.userId ? Users.pack(file.userId) : null
        });
    },
    async packNullable (src, options) {
        const opts = Object.assign({
            detail: false,
            self: false
        }, options);
        const file = typeof src === "object" ? src : await this.findOneBy({
            id: src
        });
        if (file == null) return null;
        return await awaitAll({
            id: file.id,
            createdAt: file.createdAt.toISOString(),
            name: file.name,
            type: file.type,
            md5: file.md5,
            size: file.size,
            isSensitive: file.isSensitive,
            allowDownload: file.allowDownload,
            isDatabase: file.isDatabase,
            blurhash: file.blurhash,
            properties: opts.self ? file.properties : this.getPublicProperties(file),
            url: opts.self ? file.url : this.getPublicUrl(file, false),
            thumbnailUrl: this.getPublicUrl(file, true),
            comment: file.comment,
            folderId: file.folderId,
            folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
                detail: true
            }) : null,
            userId: opts.withUser ? file.userId : null,
            user: opts.withUser && file.userId ? Users.pack(file.userId) : null
        });
    },
    async packMany (files, options) {
        const items = await Promise.all(files.map((f)=>this.packNullable(f, options)));
        return items.filter((x)=>x != null);
    }
});
