"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPropsForFields = getPropsForFields;
const React = __importStar(require("react"));
const PropTypes = __importStar(require("prop-types"));
const immutability_helper_1 = __importDefault(require("immutability-helper"));
const utils_1 = require("../../utils");
const VirtualSchemaField_1 = __importDefault(require("../VirtualSchemaField"));
const deepmerge_1 = __importDefault(require("deepmerge"));
/**
 * Makes it possible to extract fields from object schema and
 * make them act like a nested schema without touching the form data structure.
 *
 * uiSchema = {
 *  title: <string>,
 *  ui:options: {
 *    nests: {
 *      fieldName: {
 *       fields: [<string>],
 *       title: <string>,
 *      }
 *    },
 *    uiSchema: <uiSchema>
 *  }
 * }
 *
 * Example usage:
 *
 * schema = {
 *  "type": "object",
 *  "properties": {
 *    "inner_1" { "type": "string" },
 *    "inner_2": { "type": "string" },
 *    "innerOfInner_1": { "type": "string" },
 *    "innerOfInner_2": { "type": "string" },
 *    "secondInner_1" { "type": "string" },
 *    "secondInner_2": { "type": "string" },
 *    "outer_2": { "type": "string" }
 *  }
 *
 *  uiSchema = {
 *    "ui:field": "nest",
 *    "ui:options": {
 *      "nests": {
 *        "inner": {
 *          "fields": ["inner_1", "inner_2"],
 *          "title": "title of inner",
 *          "uiSchema": {
 *            "ui:field": "someField"
 *            "inner_1": {
 *              "ui:field": "someField2"
 *            }
 *          }
 *        },
 *        "secondInner": {
 *          "fields": ["secondInner_1", "secondInner_2"],
 *          "uiSchema": {
 *            "ui:field": "someField3"
 *          }
 *        }
 *      },
 *      "uiSchema": {
 *        "ui:field": "container"
 *      }
 *  }
 *
 *  would make the schemas look like this:
 *
 *  schema = {
 *    "type": "object",
 *    "properties": {
 *      "inner": {
 *        "type": "object",
 *        "title": "title of inner",
 *        "properties": {
 *          "inner_1": {
 *            "type": "string"
 *          },
 *          "inner_2": {
 *            "type": "string"
 *          }
 *        }
 *      },
 *      "inner2": {
 *        "type: "object",
 *        "properties": {
 *          "secondInner_1": {
 *            "type": "string"
 *          },
 *          "secondInner_2": {
 *            "type": "string"
 *          }
 *        }
 *      },
 *    }
 *  }
 *
 *  uiSchema = {
 *    "ui:field": "container",
 *    "inner": {
 *      "ui:field": "someField"
 *      "inner_1": {
 *        "ui:field": "someField2"
 *      }
 *    },
 *    "inner2": {
 *      "ui:field": "someField3"
 *    }
 *  }
 *
 */
let NestField = class NestField extends React.Component {
    constructor() {
        super(...arguments);
        this.onChange = (formData = {}) => {
            const { nests } = this.getUiOptions();
            Object.keys(nests).reverse().forEach(nestName => {
                if (formData[nestName]) {
                    Object.keys(formData[nestName]).forEach(prop => {
                        if (prop in formData[nestName])
                            formData = Object.assign(Object.assign({}, formData), { [prop]: formData[nestName][prop] });
                    });
                    formData = (0, utils_1.immutableDelete)(formData, nestName);
                }
            });
            this.props.onChange(formData);
        };
    }
    static getName() { return "NestField"; }
    getStateFromProps(props, origProps) {
        let { schema, uiSchema, idSchema, errorSchema, formData = {} } = props;
        const { nests, buttonsNest, buttons } = (0, utils_1.getUiOptions)(origProps.uiSchema);
        const uiButtons = uiSchema["ui:buttons"];
        const nestedPropsMap = {};
        const nestNames = Object.keys(nests).reduce((names, name) => {
            names[name] = true;
            return names;
        }, {});
        Object.keys(nests).forEach((wrapperFieldName) => {
            const nest = nests[wrapperFieldName];
            const nestedProps = getPropsForFields({ schema, uiSchema, idSchema, errorSchema, formData }, nests[wrapperFieldName].fields, nest.title);
            nestedPropsMap[wrapperFieldName] = nestedProps;
            schema = Object.assign(Object.assign({}, schema), { properties: Object.assign(Object.assign({}, schema.properties), { [wrapperFieldName]: nestedProps.schema }) });
            let nestedIdSchema = nestedProps.idSchema;
            nest.fields.forEach(field => {
                if (nestNames[field]) {
                    nestedIdSchema[field] = nestedPropsMap[field].idSchema;
                }
            });
            idSchema = Object.assign(Object.assign({}, idSchema), { [wrapperFieldName]: nestedIdSchema });
            errorSchema = Object.assign(Object.assign({}, errorSchema), { [wrapperFieldName]: nestedProps.errorSchema });
            formData = Object.assign(Object.assign({}, formData), { [wrapperFieldName]: nestedProps.formData });
            uiSchema = Object.assign(Object.assign({}, uiSchema), { [wrapperFieldName]: Object.assign(Object.assign(Object.assign({}, nestedProps.uiSchema), (nests[wrapperFieldName] ? nests[wrapperFieldName].rootUiSchema : {} || {})), (uiSchema[wrapperFieldName] || {})) });
            if (nest.rootUiSchema) {
                uiSchema = Object.assign(Object.assign({}, uiSchema), { [wrapperFieldName]: Object.assign(Object.assign({}, (nest.rootUiSchema || {})), (uiSchema[wrapperFieldName] || {})) });
            }
            nest.fields.forEach(fieldName => {
                if (uiSchema[fieldName]) {
                    uiSchema[wrapperFieldName][fieldName] = (0, deepmerge_1.default)(uiSchema[wrapperFieldName][fieldName] || {}, uiSchema[fieldName], {
                        arrayMerge: (a1, a2) => a2
                    });
                }
                [schema.properties, errorSchema, idSchema, formData, uiSchema].forEach(container => {
                    delete container[fieldName];
                });
                if (schema.required)
                    schema.required = schema.required.filter(requiredField => requiredField !== fieldName);
            });
        });
        if (buttonsNest && nests[buttonsNest]) {
            if (uiButtons) {
                uiSchema = {
                    "ui:buttons": undefined,
                    [buttonsNest]: Object.assign(Object.assign({}, (uiSchema[buttonsNest] || [])), { "ui:buttons": [
                            ...uiSchema["ui:buttons"]
                        ] })
                };
            }
            if (buttons) {
                let nestOptions = (0, utils_1.getUiOptions)(uiSchema[buttonsNest]);
                let _buttons = nestOptions.buttons || [];
                uiSchema = Object.assign(Object.assign({}, uiSchema), { "ui:options": Object.assign(Object.assign({}, uiSchema["ui:options"]), { buttons: undefined }), [buttonsNest]: Object.assign(Object.assign({}, uiSchema[buttonsNest]), { "ui:options": Object.assign(Object.assign({}, nestOptions), { buttons: [..._buttons, ...buttons] }) }) });
            }
        }
        const { "ui:order": order = [] } = uiSchema;
        const dictionarifiedOrder = order.reduce((dict, field, idx) => {
            dict[field] = idx;
            return dict;
        }, {});
        const splices = [];
        if (order) {
            Object.keys(nests).forEach(nestName => {
                const { fields } = nests[nestName];
                fields.forEach(field => {
                    const idx = dictionarifiedOrder[field];
                    if (idx !== undefined) {
                        splices.push([idx, 1]);
                    }
                });
            });
            if (splices.length)
                uiSchema = (0, immutability_helper_1.default)(uiSchema, { "ui:order": { $splice: splices.sort(([idx], [_idx]) => _idx - idx) } });
        }
        return { schema, uiSchema, idSchema, errorSchema, formData, onChange: this.onChange };
    }
};
NestField.propTypes = {
    uiSchema: PropTypes.shape({
        "ui:options": PropTypes.shape({
            "nests": function (props, propName, componentName) {
                for (let optionProp in props["ui:options"]) {
                    const prop = props["ui:options"][optionProp];
                    if (!prop.fields || !Array.isArray(prop.fields)) {
                        return new Error("Required prop '" + propName + "." + optionProp + ".fields' was not specified in '" + componentName + "'");
                    }
                }
            }
        }),
    }).isRequired,
    schema: PropTypes.shape({
        type: PropTypes.oneOf(["object"])
    }).isRequired,
    formData: PropTypes.object
};
NestField = __decorate([
    VirtualSchemaField_1.default
], NestField);
exports.default = NestField;
function getPropsForFields({ schema, uiSchema, idSchema, errorSchema, formData, onChange }, fields, title) {
    const newSchema = { type: "object", properties: {}, title };
    const newErrorSchema = {};
    const newFormData = {};
    const newUiSchema = uiSchema
        ? ["ui:classNames", "classNames"].reduce((_uiSchema, prop) => {
            if (prop in uiSchema)
                _uiSchema[prop] = uiSchema[prop];
            return _uiSchema;
        }, {})
        : {};
    const newIdSchema = { $id: idSchema.$id };
    const fieldsDictionarified = {};
    // For keeping field names compatible with idSchema.
    const flattenPointerName = fieldName => fieldName[0] === "/" ? fieldName.replace(/(?!^)\//g, "_").substr(1) : fieldName;
    fields.forEach(fieldName => {
        [
            [schema.properties, newSchema.properties, (0, utils_1.schemaJSONPointer)(schema.properties, fieldName)],
            [uiSchema, newUiSchema, (0, utils_1.uiSchemaJSONPointer)(schema.properties, fieldName)],
            [errorSchema, newErrorSchema, fieldName],
            [formData, newFormData, fieldName],
            [idSchema, newIdSchema, fieldName]
        ].forEach(([originalPropContainer, newPropContainer, _fieldName]) => {
            if (_fieldName !== undefined && (0, utils_1.checkJSONPointer)(originalPropContainer, _fieldName)) {
                newPropContainer[flattenPointerName(fieldName)] = (0, utils_1.parseJSONPointer)(originalPropContainer, _fieldName);
            }
        });
        fieldsDictionarified[fieldName] = true;
    });
    // TODO Doesn't work for JSON Pointer fields.
    if (uiSchema["ui:order"])
        newUiSchema["ui:order"] = uiSchema["ui:order"].filter(ord => fieldsDictionarified[ord] || ord === "*");
    if (schema.required)
        newSchema.required = schema.required.filter(req => fieldsDictionarified[req]);
    const newOnChange = formData => {
        let newFormData = fields.reduce((_formData, field) => {
            _formData = (0, utils_1.updateSafelyWithJSONPointer)(_formData, _formData[flattenPointerName[field]], field, !!"immutably", (__formData, path) => {
                const _schema = (0, utils_1.parseJSONPointer)(schema, (0, utils_1.schemaJSONPointer)(schema, path));
                return (0, utils_1.getDefaultFormState)(_schema);
            });
            _formData = (0, utils_1.immutableDelete)(_formData, flattenPointerName(field));
            return _formData;
        }, formData);
        return onChange(newFormData);
    };
    return {
        schema: newSchema,
        uiSchema: newUiSchema,
        idSchema: newIdSchema,
        errorSchema: newErrorSchema,
        formData: newFormData,
        onChange: onChange ? newOnChange : undefined
    };
}
