import BaseModel from "../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
import DatabaseProperty from "./Database/DatabaseProperty";
import OneUptimeDate from "./Date";
import DiskSize from "./DiskSize";
import { ObjectType } from "./JSON";
import SerializableObject from "./SerializableObject";
import SerializableObjectDictionary from "./SerializableObjectDictionary";
import Typeof from "./Typeof";
import JSON5 from "json5";
export default class JSONFunctions {
    static getSizeOfJSONinGB(obj) {
        const sizeInBytes = Buffer.byteLength(JSON.stringify(obj));
        const sizeToGb = DiskSize.byteSizeToGB(sizeInBytes);
        return sizeToGb;
    }
    static isJSONObjectDifferent(obj1, obj2) {
        return !JSONFunctions.deepEqual(obj1, obj2);
    }
    /*
     * Structural deep-equal that short-circuits at the first mismatch. The
     * dashboard widget hot path was previously running JSON.stringify on
     * both sides of nested metricQueryConfig objects every render, which
     * dominated CPU when many widgets were on screen. This visits the
     * tree once and bails on the first divergence.
     */
    static deepEqual(a, b) {
        if (a === b) {
            return true;
        }
        if (a === null ||
            b === null ||
            typeof a !== "object" ||
            typeof b !== "object") {
            return false;
        }
        if (a instanceof Date || b instanceof Date) {
            return (a instanceof Date && b instanceof Date && a.getTime() === b.getTime());
        }
        if (Array.isArray(a) || Array.isArray(b)) {
            if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
                return false;
            }
            for (let i = 0; i < a.length; i++) {
                if (!JSONFunctions.deepEqual(a[i], b[i])) {
                    return false;
                }
            }
            return true;
        }
        const keysA = Object.keys(a);
        const keysB = Object.keys(b);
        if (keysA.length !== keysB.length) {
            return false;
        }
        for (const key of keysA) {
            if (!Object.prototype.hasOwnProperty.call(b, key)) {
                return false;
            }
            if (!JSONFunctions.deepEqual(a[key], b[key])) {
                return false;
            }
        }
        return true;
    }
    static nestJson(obj) {
        // obj could be in this format:
        /**
         * {
         *"http.url.protocol": "http",
         *"http.url.hostname": "localhost",
         *"http.host": "localhost",
         */
        // we want to convert it to this format:
        /**
         * {
         *
         * "http": {
         *     "url": {
         *        "protocol": "http",
         *       "hostname": "localhost"
         *   },
         *    "host": "localhost",
         *   "method": "POST",
         *  "scheme": "http",
         * "client_ip": "
         * ...
         *
         * },
         */
        const result = {};
        for (const key in obj) {
            const keys = key.split(".");
            let currentObj = result;
            for (let i = 0; i < keys.length; i++) {
                const k = keys[i];
                if (!k) {
                    continue;
                }
                if (i === keys.length - 1) {
                    currentObj[k] = obj[key];
                }
                else {
                    if (!currentObj[k]) {
                        currentObj[k] = {};
                    }
                    currentObj = currentObj[k];
                }
            }
        }
        return result;
    }
    static isEmptyObject(obj) {
        if (!obj) {
            return true;
        }
        return Object.keys(obj).length === 0;
    }
    static removeCircularReferences(obj) {
        const cache = [];
        const returnValue = JSON.stringify(obj, (_key, value) => {
            if (typeof value === "object" && value !== null) {
                if (cache.includes(value)) {
                    return;
                }
                cache.push(value);
            }
            return value;
        });
        return JSON.parse(returnValue);
    }
    static isEqualObject(obj1, obj2) {
        // check if all the keys are the same
        if (!obj1 && !obj2) {
            return true;
        }
        if (!obj1 || !obj2) {
            return false;
        }
        const keys1 = Object.keys(obj1);
        const keys2 = Object.keys(obj2);
        if (keys1.length !== keys2.length) {
            return false;
        }
        for (const key of keys1) {
            if (!keys2.includes(key)) {
                return false;
            }
        }
        // check if all the values are the same
        for (const key of keys1) {
            if (obj1[key] !== obj2[key]) {
                return false;
            }
        }
        return true;
    }
    static toCompressedString(val) {
        return JSON.stringify(val, null, 2);
    }
    static toString(val) {
        if (typeof val === Typeof.String) {
            return val;
        }
        return JSON.stringify(val);
    }
    static getJSONValueInPath(obj, path) {
        const paths = path.split(".");
        let returnValue = obj;
        for (const p of paths) {
            if (!p) {
                continue;
            }
            if (returnValue && returnValue[p]) {
                returnValue = returnValue[p];
            }
            else {
                return null;
            }
        }
        return returnValue;
    }
    // this function serializes JSON with Common Objects to JSON that can be stringified.
    static serialize(val) {
        const newVal = {};
        for (const key in val) {
            if (val[key] === undefined) {
                continue;
            }
            if (val[key] === null) {
                newVal[key] = val[key];
            }
            if (Array.isArray(val[key])) {
                const arraySerialize = [];
                for (const arrVal of val[key]) {
                    arraySerialize.push(this.serializeValue(arrVal));
                }
                newVal[key] = arraySerialize;
            }
            else {
                newVal[key] = this.serializeValue(val[key]);
            }
        }
        return newVal;
    }
    static serializeValue(val) {
        if (val === null || val === undefined) {
            return val;
        }
        else if (typeof val === Typeof.String && val.toString().trim() === "") {
            return val;
        }
        else if (val instanceof BaseModel) {
            return BaseModel.toJSON(val, BaseModel);
        }
        else if (typeof val === Typeof.Number) {
            return val;
        }
        else if (ArrayBuffer.isView(val)) {
            return {
                _type: ObjectType.Buffer,
                value: val,
            };
        }
        else if (val && val instanceof SerializableObject) {
            return val.toJSON();
        }
        else if (val && val instanceof Date) {
            return {
                _type: ObjectType.DateTime,
                value: OneUptimeDate.toString(val).toString(),
            };
        }
        else if (typeof val === Typeof.Object &&
            val["_type"] &&
            Object.keys(ObjectType).includes(val["_type"])) {
            return val;
        }
        else if (typeof val === Typeof.Object) {
            return this.serialize(val);
        }
        return val;
    }
    static deserializeValue(val) {
        if (val === null || val === undefined) {
            return val;
        }
        else if (typeof val === Typeof.String && val.toString().trim() === "") {
            return val;
        }
        else if (val &&
            typeof val === Typeof.Object &&
            val["_type"] &&
            val["value"] &&
            val["value"]["data"] &&
            val["value"]["type"] &&
            val["value"]["type"] ===
                ObjectType.Buffer &&
            val["_type"] === ObjectType.Buffer) {
            return Buffer.from(val["value"]["data"]);
        }
        else if (val && ArrayBuffer.isView(val)) {
            return Buffer.from(val);
        }
        else if (typeof val === Typeof.Number) {
            return val;
        }
        else if (val instanceof DatabaseProperty) {
            return val;
        }
        else if (val instanceof SerializableObject) {
            return val;
        }
        else if (val &&
            typeof val === Typeof.Object &&
            val["_type"] &&
            SerializableObjectDictionary[val["_type"]]) {
            return SerializableObjectDictionary[val["_type"]].fromJSON(val);
        }
        else if (val instanceof Date) {
            return val;
        }
        else if (typeof val === Typeof.Object) {
            return this.deserialize(val);
        }
        else if (Array.isArray(val)) {
            const arr = [];
            for (const v of val) {
                arr.push(this.deserializeValue(v));
            }
            return arr;
        }
        return val;
    }
    static deserializeArray(array) {
        const returnArr = [];
        for (const obj of array) {
            returnArr.push(this.deserialize(obj));
        }
        return returnArr;
    }
    static serializeArray(array) {
        const returnArr = [];
        for (const obj of array) {
            returnArr.push(this.serialize(obj));
        }
        return returnArr;
    }
    static parse(val) {
        return JSON5.parse(val);
    }
    static parseJSONObject(val) {
        const result = this.parse(val);
        if (Array.isArray(result)) {
            throw new Error("Expected JSONObject, but got JSONArray");
        }
        return result;
    }
    static parseJSONArray(val) {
        const result = this.parse(val);
        if (!Array.isArray(result)) {
            throw new Error("Expected JSONArray, but got JSONObject");
        }
        return result;
    }
    static deserialize(val) {
        const newVal = {};
        for (const key in val) {
            if (val[key] === null || val[key] === undefined) {
                newVal[key] = val[key];
            }
            if (Array.isArray(val[key])) {
                const arraySerialize = [];
                for (const arrVal of val[key]) {
                    arraySerialize.push(this.deserializeValue(arrVal));
                }
                newVal[key] = arraySerialize;
            }
            else {
                newVal[key] = this.deserializeValue(val[key]);
            }
        }
        return newVal;
    }
    static toFormattedString(val) {
        return JSON.stringify(val, null, 4);
    }
    static anyObjectToJSONObject(val) {
        return JSON.parse(JSON.stringify(val));
    }
    static unflattenArray(val) {
        const returnArr = [];
        for (const obj of val) {
            returnArr.push(this.unflattenObject(obj));
        }
        return returnArr;
    }
    static unflattenObject(val) {
        const returnObj = {};
        for (const key in val) {
            const keys = key.split(".");
            let currentObj = returnObj;
            for (let i = 0; i < keys.length; i++) {
                const k = keys[i];
                if (!k) {
                    continue;
                }
                if (i === keys.length - 1) {
                    currentObj[k] = val[key];
                }
                else {
                    if (!currentObj[k]) {
                        currentObj[k] = {};
                    }
                    currentObj = currentObj[k];
                }
            }
        }
        return returnObj;
    }
    static flattenObject(val) {
        const returnObj = {};
        const flatten = (obj, prefix) => {
            for (const key in obj) {
                if (typeof obj[key] === Typeof.Object) {
                    flatten(obj[key], `${prefix}${key}.`);
                }
                else {
                    returnObj[`${prefix}${key}`] = obj[key];
                }
            }
        };
        flatten(val, "");
        return returnObj;
    }
    static flattenArray(val) {
        const returnArr = [];
        for (const obj of val) {
            returnArr.push(this.flattenObject(obj));
        }
        return returnArr;
    }
}
//# sourceMappingURL=JSONFunctions.js.map