import { ObjectDataDefinition } from "./Definitions/ObjectDataDefinition";
import { StringDataDefinition } from "./Definitions/StringDataDefinition";
import { NumberDataDefinition } from "./Definitions/NumberDataDefinition";
import { BooleanDataDefinition } from "./Definitions/BooleanDataDefinition";
import { XDataType } from "./XDataType";
import { FileDataDefinition } from "./Definitions/FileDataDefinition";
import { DateDataDefinition } from "./Definitions/DateDataDefinition";
import { ConfigStorageUtility } from "../ConfigStorageUtility";
import { JSONDummyData } from "./Constants/XDataDefConstants";
export class XDataDefinition {
    constructor() {
        this.fields = [];
        this.value = {};
    }
    // returns valid json schema from object definition
    toJSONSchema(fields) {
        let schema = {
            type: "object",
            properties: {},
        };
        if (!fields) {
            fields = this.fields;
        }
        for (let def of fields) {
            if (def.list) {
                if (["string", "number", "boolean"].includes(def.type)) {
                    Reflect.set(schema.properties, def.name, {
                        type: "array",
                        items: { type: def.type },
                    });
                }
                else if (def.type === "object") {
                    Reflect.set(schema.properties, def.name, {
                        type: "array",
                        items: JSON.parse(this.toJSONSchema(def.fields)),
                    });
                }
            }
            else {
                if (["string", "number", "boolean"].includes(def.type)) {
                    Reflect.set(schema.properties, def.name, {
                        type: def.type,
                    });
                }
                else if (def.type === "object") {
                    Reflect.set(schema.properties, def.name, JSON.parse(this.toJSONSchema(def.fields)));
                }
            }
        }
        return JSON.stringify(schema, null, 2);
    }
    // returns valid json schema from object definition
    toJSONObject(dataObj, fields) {
        let schema = {};
        if (!fields) {
            fields = this.fields;
        }
        if (!dataObj) {
            dataObj = this.value;
        }
        for (let def of fields) {
            let value = Reflect.get(dataObj, def.name);
            if (["string", "number", "boolean", "date"].includes(def.type)) {
                if (value === undefined || value === "" || value === null) {
                    switch (def.type) {
                        case "string":
                            value = JSONDummyData.STRING;
                            break;
                        case "number":
                            value = JSONDummyData.NUMBER;
                            break;
                        case "boolean":
                            value = false;
                            break;
                        case "date":
                            value = JSONDummyData.DATE;
                            break;
                        default:
                            break;
                    }
                    if (def.list === true) {
                        value = [value];
                    }
                }
                Reflect.set(schema, def.name, value);
            }
            else if (def.type === "object") {
                if (def.list === true) {
                    let objListJson = [];
                    if (value === undefined || !Array.isArray(value)) {
                        value = [];
                    }
                    for (let val of value) {
                        objListJson.push(JSON.parse(this.toJSONObject(val, def.fields)));
                    }
                    Reflect.set(schema, def.name, objListJson);
                }
                else {
                    Reflect.set(schema, def.name, JSON.parse(this.toJSONObject(Reflect.get(dataObj, def.name), def.fields)));
                }
            }
        }
        return JSON.stringify(schema, null, 2);
    }
    // parses json schema into the object definitions.
    // - remove items that are not in schema
    // - adds items that are in schema
    // - adjust items that the schema changes values of
    // ** Throw error if schema is not valid
    // to add:
    //  - title
    //  - description
    //  - sub type on number
    // resolve url schema
    // resolve internal refs
    // one of?
    mergeJSONSchema(schema, removeItemsNotInSchema = false, reorderElementsToMatchSchema = true) {
        XDataDefinition.validateSchema(schema);
        let schemaObj = JSON.parse(schema);
        this.fields = XDataDefinition.parseSchemaNode(schemaObj, schemaObj, this, this.fields, removeItemsNotInSchema, reorderElementsToMatchSchema);
    }
    static validateSchema(schema) {
        let schemaObj = {};
        try {
            schemaObj = JSON.parse(schema);
        }
        catch (error) {
            throw new Error("Invalid json schema");
        }
        if (Reflect.get(schemaObj, "type") !== "object")
            throw new Error("Invalid JSON Schema");
        if (Reflect.get(schemaObj, "allOf"))
            throw new Error("allOf is not supported");
        if (Reflect.get(schemaObj, "anyOf"))
            throw new Error("anyOf is not supported");
        if (Reflect.get(schemaObj, "oneOf"))
            throw new Error("oneOf is not supported");
        if (Reflect.get(schemaObj, "not"))
            throw new Error("not is not supported");
        let properties = Reflect.get(schemaObj, "properties");
        for (let property in properties) {
            let propDescription = properties[property];
            if (propDescription.type === "#ref") {
                // Throw error if still have #ref
                throw new Error("Ref should not be there anymore!");
            }
        }
    }
    static parseSchemaNode(fullSchema, schemaNode, owner, existingFields, removeItemsNotInSchema, reorderElementsToMatchSchema) {
        //https://json-schema.org/understanding-json-schema/index.html
        let newFieldsMap = {};
        let newFieldList = [];
        let oldFieldsMap = {};
        for (let oField of existingFields) {
            if (oField.name) {
                oldFieldsMap[oField.name] = oField;
            }
        }
        // root might be array, not
        let properties = Reflect.get(schemaNode, "properties");
        for (let property in properties) {
            let propDescription = properties[property];
            let isList = false;
            if (propDescription.type === "array") {
                // https://json-schema.org/understanding-json-schema/reference/array.html
                // handle array
                // check items property
                // call recursively??
                // unique
                // single value
                // objects
                // tuples
                isList = true;
            }
            let def = this.parseProperty(propDescription, property, owner, oldFieldsMap, fullSchema, removeItemsNotInSchema, reorderElementsToMatchSchema, isList);
            if (def && def.name) {
                newFieldList.push(def);
                newFieldsMap[def.name] = def;
            }
        }
        return XDataDefinition.reconcileFieldList(removeItemsNotInSchema, reorderElementsToMatchSchema, newFieldList, newFieldsMap, existingFields);
    }
    static parseProperty(propDescription, property, owner, oldFieldsMap, fullSchema, removeItemsNotInSchema, reorderElementsToMatchSchema, isList) {
        var _a, _b, _c, _d, _e, _f;
        let def = undefined;
        if (isList)
            propDescription = propDescription.items;
        if (propDescription.type === "string") {
            // todo: emum
            let sdef = new StringDataDefinition();
            if (((_a = oldFieldsMap[property]) === null || _a === void 0 ? void 0 : _a.type) === "string") {
                sdef = oldFieldsMap[property];
            }
            sdef.name = property;
            sdef.list = isList;
            sdef.owner = owner;
            let pattern = Reflect.get(propDescription, "pattern");
            if (pattern)
                sdef.pattern = pattern;
            def = sdef;
        }
        else if (propDescription.type === "boolean") {
            let sdef = new BooleanDataDefinition();
            if (((_b = oldFieldsMap[property]) === null || _b === void 0 ? void 0 : _b.type) === "boolean") {
                sdef = oldFieldsMap[property];
            }
            sdef.name = property;
            sdef.list = isList;
            sdef.owner = owner;
            def = sdef;
        }
        else if (propDescription.type === "number" || propDescription.type === "integer") {
            let sdef = new NumberDataDefinition();
            if (((_c = oldFieldsMap[property]) === null || _c === void 0 ? void 0 : _c.type) === "number") {
                sdef = oldFieldsMap[property];
            }
            sdef.name = property;
            sdef.list = isList;
            sdef.owner = owner;
            // check upper and lower bounds
            let upperBound = Reflect.get(propDescription, "maximum");
            if (upperBound) {
                sdef.upperBounds = +upperBound;
            }
            let lowerBound = Reflect.get(propDescription, "minimum");
            if (lowerBound) {
                sdef.lowerBounds = +lowerBound;
            }
            sdef.allowDecimals = propDescription.type === "number";
            def = sdef;
        }
        else if (propDescription.type === "null") {
            // ignore
        }
        else if (propDescription.type === "object") {
            let odef = new ObjectDataDefinition();
            if (((_d = oldFieldsMap[property]) === null || _d === void 0 ? void 0 : _d.type) === "object") {
                odef = oldFieldsMap[property];
            }
            odef.name = property;
            odef.list = isList;
            odef.owner = owner;
            odef.fields = this.parseSchemaNode(fullSchema, propDescription, owner, (_f = (_e = oldFieldsMap[property]) === null || _e === void 0 ? void 0 : _e.fields) !== null && _f !== void 0 ? _f : [], removeItemsNotInSchema, reorderElementsToMatchSchema);
            def = odef;
        }
        return def;
    }
    static reconcileFieldList(removeItemsNotInSchema, reorderElementsToMatch, newFieldList, newFieldsMap, existingFields) {
        if (removeItemsNotInSchema && reorderElementsToMatch) {
            return newFieldList;
        }
        else if (reorderElementsToMatch && !removeItemsNotInSchema) {
            for (let oldField of existingFields) {
                if (oldField.name && !newFieldsMap[oldField.name]) {
                    newFieldList.push(oldField);
                }
            }
            return newFieldList;
        }
        else if (!reorderElementsToMatch && !removeItemsNotInSchema) {
            let toSet = [];
            for (let oldField of existingFields) {
                if (oldField.name) {
                    if (!newFieldsMap[oldField.name]) {
                        toSet.push(oldField);
                    }
                    else {
                        toSet.push(newFieldsMap[oldField.name]);
                    }
                }
            }
            return toSet;
        }
        else {
            // if (!reorderElementsToMatch && removeItemsNotInSchema)
            let toSet = [];
            for (let oldField of existingFields) {
                if (oldField.name) {
                    if (!newFieldsMap[oldField.name]) {
                        // this field is being removed
                    }
                    else {
                        toSet.push(newFieldsMap[oldField.name]);
                    }
                }
            }
            return toSet;
        }
    }
    // parses json schema into the object definitions.
    // ** JSON is evaluated to see to determine the type of each element **
    // - remove items that are not in schema
    // - adds items that are in schema
    // - adjust items that the schema changes values of
    // ** Throw error if json is not valid
    // ** change order of elements to match json
    mergeJSON(json, removeItemsNotInSchema = false, reorderElementsToMatch = true) {
        try {
            this.mergeObject(JSON.parse(json), removeItemsNotInSchema, reorderElementsToMatch);
        }
        catch (error) {
            throw new Error(error);
        }
    }
    // updates data definition with values from object
    mergeObject(data, removeItemsNotInSchema = false, reorderElementsToMatch = true) {
        this.fields = ObjectDataDefinition.parseFieldsForObject(this, data, this.fields, removeItemsNotInSchema, reorderElementsToMatch);
    }
    // returns json as string
    toSampleJson() {
        return JSON.stringify(this.toSampleObject(), null, 2);
    }
    // build object from data definition
    toSampleObject() {
        return this.value;
    }
    getFieldByDataPath(dataPath) {
        const dataPathItems = dataPath.split(".");
        let fields = this.fields;
        let index = 0;
        for (const dataPathItem of dataPathItems) {
            const matchedField = fields.find((field) => field.name === dataPathItem);
            if (index === dataPathItems.length - 1) {
                return matchedField;
            }
            else if ((matchedField === null || matchedField === void 0 ? void 0 : matchedField.type) !== XDataType.OBJECT) {
                throw new Error("Datapath is not valid");
            }
            else {
                fields = matchedField.fields;
                index++;
            }
        }
        return undefined;
    }
    getDataValues(type, isList) {
        return [];
    }
    static fillTypeMap() {
        XDataDefinition.TYPEMAPFILLED = true;
        XDataDefinition._TYPEMAP[XDataType.STRING] = new StringDataDefinition();
        XDataDefinition._TYPEMAP[XDataType.NUMBER] = new NumberDataDefinition();
        XDataDefinition._TYPEMAP[XDataType.OBJECT] = new ObjectDataDefinition();
        XDataDefinition._TYPEMAP[XDataType.BOOLEAN] = new BooleanDataDefinition();
        XDataDefinition._TYPEMAP[XDataType.FILE] = new FileDataDefinition();
        XDataDefinition._TYPEMAP[XDataType.DATE] = new DateDataDefinition();
    }
    static get TYPEMAP() {
        if (!this.TYPEMAPFILLED)
            this.fillTypeMap();
        return this._TYPEMAP;
    }
    getValueObject() {
        return this.value;
    }
    getStorageData() {
        let storage = {};
        let fields = [];
        for (let f of this.fields) {
            let d = f.getStorageData();
            ConfigStorageUtility.verifyTypeField(d);
            fields.push(d);
        }
        Reflect.set(storage, "__fields", fields);
        Reflect.set(storage, "value", this.value);
        return storage;
    }
    setStorageData(data) {
        let defArray = Reflect.get(data, "__fields");
        if (defArray && defArray instanceof Array && defArray.length !== 0) {
            for (let defData of defArray) {
                let dd = ConfigStorageUtility.getDataDefinitionFromData(defData);
                if (dd) {
                    this.fields.push(dd);
                }
            }
        }
        this.value = Reflect.get(data, "value");
    }
}
XDataDefinition._TYPEMAP = {};
XDataDefinition.TYPEMAPFILLED = false;
//# sourceMappingURL=XDataDefinition.js.map