/* eslint-disable @typescript-eslint/no-extraneous-class */
import { hasBlob, hasBuffer, hasFile, hasFormData } from '../../Platform.js';
export const SupportedPayloadTypes = [
    'string',
    'file',
    'blob',
    'buffer',
    'arraybuffer',
    'formdata',
    'x-www-form-urlencoded',
];
export class PayloadSerializer {
    /**
     * Checked whether the passed payload can be safely stored in the data store.
     * @param payload The value to test.
     */
    static isSafePayload(payload) {
        if (payload === undefined || payload === null) {
            // both values should be stored correctly
            return true;
        }
        if (typeof payload === 'string') {
            return true;
        }
        // checks whether the payload is already serialized./
        const typed = payload;
        if (typed.type && SupportedPayloadTypes.includes(typed.type)) {
            return true;
        }
        return false;
    }
    /**
     * Tests whether the given input should be processed by the `serialize()`.
     */
    static needsSerialization(input) {
        const typedSerialized = input;
        if (typedSerialized.type && SupportedPayloadTypes.includes(typedSerialized.type)) {
            return false;
        }
        return true;
    }
    /**
     * Transforms the payload into a data store safe object.
     */
    static async serialize(payload) {
        if (PayloadSerializer.isSafePayload(payload)) {
            if (payload === null) {
                return undefined;
            }
            return payload;
        }
        // if (payload === undefined || payload === null) {
        //   return undefined;
        // }
        // if (typeof payload === 'string') {
        //   return payload;
        // }
        if (hasFile && payload instanceof File) {
            return PayloadSerializer.stringifyFile(payload);
        }
        if (hasBlob && payload instanceof Blob) {
            return PayloadSerializer.stringifyBlob(payload);
        }
        if (hasBuffer && payload instanceof Buffer) {
            return PayloadSerializer.stringifyBuffer(payload);
        }
        if (payload instanceof ArrayBuffer) {
            return PayloadSerializer.stringifyArrayBuffer(payload);
        }
        if (hasFormData && payload instanceof FormData) {
            try {
                const result = await PayloadSerializer.stringifyFormData(payload);
                return result;
            }
            catch (e) {
                // eslint-disable-next-line no-console
                console.warn(`Unable to transform FormData: ${e.message}`);
            }
        }
        return '';
    }
    /**
     * Stringifies a file object.
     */
    static async stringifyFile(file) {
        const buffer = await file.arrayBuffer();
        const view = new Uint8Array(buffer);
        const meta = {
            mime: file.type,
            name: file.name,
        };
        const result = {
            type: 'file',
            data: [...view],
            meta,
        };
        return result;
    }
    /**
     * Stringifies a blob object.
     *
     * @param blob Blob object to be translated to string
     */
    static async stringifyBlob(blob) {
        const buffer = await blob.arrayBuffer();
        const view = new Uint8Array(buffer);
        const meta = {
            mime: blob.type,
        };
        const result = {
            type: 'blob',
            data: [...view],
            meta,
        };
        return result;
    }
    /**
     * When the passed argument is a NodeJS buffer it creates an object describing the buffer
     * in a safe to store object.
     *
     * @returns The buffer metadata or undefined if the passed argument is not a Buffer.
     */
    static stringifyBuffer(payload) {
        if (typeof payload.copy === 'function') {
            return {
                type: 'buffer',
                data: [...payload],
            };
        }
        return undefined;
    }
    /**
     * When the passed argument is an ArrayBuffer it creates an object describing the object in a safe to store object.
     *
     * @param payload
     * @returns The buffer metadata or undefined if the passed argument is not an ArrayBuffer.
     */
    static stringifyArrayBuffer(payload) {
        const view = new Uint8Array(payload);
        return {
            type: 'arraybuffer',
            data: Array.from(view),
        };
    }
    /**
     * Transforms the FormData object to a serialized object describing the data.
     *
     * @param payload A `FormData` object
     * @return A promise resolved to a datastore safe entries.
     */
    static async stringifyFormData(payload) {
        // TS apparently doesn't know that FormData is iterable.
        const iterable = payload;
        const promises = [];
        for (const part of iterable) {
            promises.push(PayloadSerializer.serializeFormDataEntry(part[0], part[1]));
        }
        const items = await Promise.all(promises);
        return {
            type: 'formdata',
            data: items,
        };
    }
    /**
     * Transforms a FormData entry into a safe-to-store text entry
     *
     * @param name The part name
     * @param file The part value
     * @returns Transformed FormData part to a datastore safe entry.
     */
    static async serializeFormDataEntry(name, file) {
        if (typeof file === 'string') {
            return {
                name,
                value: file,
                enabled: true,
            };
        }
        let value;
        // API Client adds the "blob" when adding a text value with a mime type.
        // This is recognized by the UI to restore the entry as the text and not a file.
        if (hasFile && file instanceof File && file.name !== 'blob') {
            value = await PayloadSerializer.stringifyFile(file);
        }
        else {
            value = await PayloadSerializer.stringifyBlob(file);
        }
        const part = {
            name,
            value,
            enabled: true,
        };
        return part;
    }
    /**
     * Restores the payload into its original format.
     */
    static async deserialize(payload, as) {
        if (payload === undefined || payload === null) {
            return undefined;
        }
        if (typeof payload === 'string') {
            return payload;
        }
        if (as === 'buffer') {
            // we are in the nodejs environment.
            // We mostly gonna return a Buffer here.
            switch (payload.type) {
                case 'string':
                    return payload.data;
                case 'file':
                    return PayloadSerializer.deserializeFileBuffer(payload);
                case 'blob':
                    return PayloadSerializer.deserializeBlobBuffer(payload);
                case 'buffer':
                    return PayloadSerializer.deserializeBuffer(payload.data);
                case 'arraybuffer':
                    return PayloadSerializer.deserializeArrayBufferBuffer(payload.data);
                // generally speaking in this ecosystem we don't ever need to read FormData from a request/response in Node.
                case 'formdata':
                    return undefined;
                default:
                    return undefined;
            }
        }
        switch (payload.type) {
            case 'string':
                return payload.data;
            case 'file':
                return PayloadSerializer.deserializeFile(payload);
            case 'blob':
                return PayloadSerializer.deserializeBlob(payload);
            case 'buffer':
                return PayloadSerializer.deserializeArrayBuffer(payload.data);
            case 'arraybuffer':
                return PayloadSerializer.deserializeArrayBuffer(payload.data);
            case 'formdata':
                return PayloadSerializer.deserializeFormData(payload.data);
            default:
                return undefined;
        }
    }
    /**
     * Deserializes previously serialized file object.
     *
     * @param payload The serialized payload with a file.
     */
    static deserializeFile(payload) {
        const data = payload.data;
        const meta = payload.meta;
        const { mime, name } = meta;
        const { buffer } = new Uint8Array(data);
        return new File([buffer], name, {
            type: mime,
        });
    }
    /**
     * Deserializes previously serialized blob object.
     *
     * In previous versions of ARC the data was a string as data URL. In API client this is a buffer.
     *
     * @param payload The serialized payload.
     * @return Restored blob value
     */
    static deserializeBlob(payload) {
        if (typeof payload.data === 'string') {
            return this.deserializeBlobLegacy(payload.data);
        }
        const data = payload.data;
        const meta = payload.meta;
        const { mime } = meta;
        const { buffer } = new Uint8Array(data);
        return new Blob([buffer], { type: mime });
    }
    /**
     * The old implementation of the blob deserializer.
     *
     * @deprecated
     * @param dataUrl Data url from blob value.
     * @return Restored blob value
     */
    static deserializeBlobLegacy(dataUrl) {
        const arr = dataUrl.split(',');
        const matchedMime = arr[0].match(/:(.*?);/);
        if (!matchedMime) {
            return undefined;
        }
        const mime = matchedMime[1];
        const bstr = atob(arr[1]);
        let n = bstr.length;
        const u8arr = new Uint8Array(n);
        while (n--) {
            u8arr[n] = bstr.charCodeAt(n);
        }
        return new Blob([u8arr], { type: mime });
    }
    /**
     * Converts previously serialized File to a Buffer.
     *
     * @param payload The serialized payload.
     * @return Restored File value as Buffer
     */
    static deserializeFileBuffer(payload) {
        const data = payload.data;
        const ab = this.deserializeArrayBuffer(data);
        return Buffer.from(ab);
    }
    /**
     * Converts data-url string to buffer
     *
     * @param payload The serialized payload.
     * @return Restored blob value
     */
    static deserializeBlobBuffer(payload) {
        if (typeof payload.data === 'string') {
            return this.deserializeBlobBufferLegacy(payload.data);
        }
        const data = payload.data;
        const ab = this.deserializeArrayBuffer(data);
        return Buffer.from(ab);
    }
    /**
     * Converts data-url string to buffer
     *
     * @deprecated
     * @param dataUrl Data url from blob value.
     * @return Restored blob value
     */
    static deserializeBlobBufferLegacy(dataUrl) {
        const arr = dataUrl.split(',');
        const value = arr[1];
        return Buffer.from(value, 'base64url');
    }
    /**
     * Converts UInt8Array to a Buffer.
     *
     * @param data Previously serialized buffer.
     * @return Restored blob value
     */
    static deserializeBuffer(data) {
        return Buffer.from(data);
    }
    /**
     * Converts UInt8Array to an ArrayBuffer.
     *
     * @param data Previously serialized buffer.
     */
    static deserializeArrayBuffer(data) {
        const { buffer } = new Uint8Array(data);
        return buffer;
    }
    /**
     * Converts UInt8Array to a Buffer.
     *
     * @param data Previously serialized buffer.
     */
    static deserializeArrayBufferBuffer(data) {
        const ab = this.deserializeArrayBuffer(data);
        return Buffer.from(ab);
    }
    /**
     * Deserializes FormData from API Client data model.
     *
     * @param parts API Client model for multipart.
     * @return {FormData} Restored form data
     */
    static deserializeFormData(parts) {
        const fd = new FormData();
        if (!Array.isArray(parts) || !parts.length) {
            return fd;
        }
        parts.forEach((part) => this.deserializeFormDataPart(fd, part));
        return fd;
    }
    static deserializeFormDataPart(form, part) {
        if (part.enabled === false) {
            return;
        }
        // the compatibility with old ARC.
        if (typeof part.isFile === 'boolean') {
            this.deserializeFormDataLegacy(form, part);
            return;
        }
        const { name, value } = part;
        if (typeof value === 'string') {
            form.append(name, value);
            return;
        }
        if (value.type === 'string') {
            form.append(name, value.data);
            return;
        }
        if (value.type === 'file') {
            const file = this.deserializeFile(value);
            form.append(name, file);
            return;
        }
        const blob = this.deserializeBlob(value);
        form.append(name, blob);
    }
    /**
     * @deprecated This is only for compatibility with ARC.
     */
    static deserializeFormDataLegacy(form, part) {
        let blob;
        if (part.isFile) {
            blob = PayloadSerializer.deserializeBlobLegacy(part.value);
            if (blob) {
                form.append(part.name, blob, part.fileName);
            }
        }
        else if (part.type) {
            blob = PayloadSerializer.deserializeBlobLegacy(part.value);
            if (blob) {
                form.append(part.name, blob, 'blob');
            }
        }
        else {
            form.append(part.name, part.value);
        }
    }
}
//# sourceMappingURL=PayloadSerializer.js.map