import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { Component } from 'react';
import { getTemplate, getUiOptions, orderProperties, TranslatableString, ADDITIONAL_PROPERTY_FLAG, PROPERTIES_KEY, REF_KEY, ANY_OF_KEY, ONE_OF_KEY, } from '@rjsf/utils';
import Markdown from 'markdown-to-jsx';
import get from 'lodash/get';
import has from 'lodash/has';
import isObject from 'lodash/isObject';
import set from 'lodash/set';
import unset from 'lodash/unset';
/** The `ObjectField` component is used to render a field in the schema that is of type `object`. It tracks whether an
 * additional property key was modified and what it was modified to
 *
 * @param props - The `FieldProps` for this template
 */
class ObjectField extends Component {
    constructor() {
        super(...arguments);
        /** Set up the initial state */
        this.state = {
            wasPropertyKeyModified: false,
            additionalProperties: {},
        };
        /** Returns the `onPropertyChange` handler for the `name` field. Handles the special case where a user is attempting
         * to clear the data for a field added as an additional property. Calls the `onChange()` handler with the updated
         * formData.
         *
         * @param name - The name of the property
         * @param addedByAdditionalProperties - Flag indicating whether this property is an additional property
         * @returns - The onPropertyChange callback for the `name` property
         */
        this.onPropertyChange = (name, addedByAdditionalProperties = false) => {
            return (value, newErrorSchema, id) => {
                const { formData, onChange, errorSchema } = this.props;
                if (value === undefined && addedByAdditionalProperties) {
                    // Don't set value = undefined for fields added by
                    // additionalProperties. Doing so removes them from the
                    // formData, which causes them to completely disappear
                    // (including the input field for the property name). Unlike
                    // fields which are "mandated" by the schema, these fields can
                    // be set to undefined by clicking a "delete field" button, so
                    // set empty values to the empty string.
                    value = '';
                }
                const newFormData = { ...formData, [name]: value };
                onChange(newFormData, errorSchema &&
                    errorSchema && {
                    ...errorSchema,
                    [name]: newErrorSchema,
                }, id);
            };
        };
        /** Returns a callback to handle the onDropPropertyClick event for the given `key` which removes the old `key` data
         * and calls the `onChange` callback with it
         *
         * @param key - The key for which the drop callback is desired
         * @returns - The drop property click callback
         */
        this.onDropPropertyClick = (key) => {
            return (event) => {
                event.preventDefault();
                const { onChange, formData } = this.props;
                const copiedFormData = { ...formData };
                unset(copiedFormData, key);
                onChange(copiedFormData);
            };
        };
        /** Computes the next available key name from the `preferredKey`, indexing through the already existing keys until one
         * that is already not assigned is found.
         *
         * @param preferredKey - The preferred name of a new key
         * @param [formData] - The form data in which to check if the desired key already exists
         * @returns - The name of the next available key from `preferredKey`
         */
        this.getAvailableKey = (preferredKey, formData) => {
            const { uiSchema, registry } = this.props;
            const { duplicateKeySuffixSeparator = '-' } = getUiOptions(uiSchema, registry.globalUiOptions);
            let index = 0;
            let newKey = preferredKey;
            while (has(formData, newKey)) {
                newKey = `${preferredKey}${duplicateKeySuffixSeparator}${++index}`;
            }
            return newKey;
        };
        /** Returns a callback function that deals with the rename of a key for an additional property for a schema. That
         * callback will attempt to rename the key and move the existing data to that key, calling `onChange` when it does.
         *
         * @param oldValue - The old value of a field
         * @returns - The key change callback function
         */
        this.onKeyChange = (oldValue) => {
            return (value, newErrorSchema) => {
                if (oldValue === value) {
                    return;
                }
                const { formData, onChange, errorSchema } = this.props;
                value = this.getAvailableKey(value, formData);
                const newFormData = {
                    ...formData,
                };
                const newKeys = { [oldValue]: value };
                const keyValues = Object.keys(newFormData).map((key) => {
                    const newKey = newKeys[key] || key;
                    return { [newKey]: newFormData[key] };
                });
                const renamedObj = Object.assign({}, ...keyValues);
                this.setState({ wasPropertyKeyModified: true });
                onChange(renamedObj, errorSchema &&
                    errorSchema && {
                    ...errorSchema,
                    [value]: newErrorSchema,
                });
            };
        };
        /** Handles the adding of a new additional property on the given `schema`. Calls the `onChange` callback once the new
         * default data for that field has been added to the formData.
         *
         * @param schema - The schema element to which the new property is being added
         */
        this.handleAddClick = (schema) => () => {
            var _a;
            if (!schema.additionalProperties) {
                return;
            }
            const { formData, onChange, registry } = this.props;
            const newFormData = { ...formData };
            let type = undefined;
            let constValue = undefined;
            let defaultValue = undefined;
            if (isObject(schema.additionalProperties)) {
                type = schema.additionalProperties.type;
                constValue = schema.additionalProperties.const;
                defaultValue = schema.additionalProperties.default;
                let apSchema = schema.additionalProperties;
                if (REF_KEY in apSchema) {
                    const { schemaUtils } = registry;
                    apSchema = schemaUtils.retrieveSchema({ $ref: apSchema[REF_KEY] }, formData);
                    type = apSchema.type;
                    constValue = apSchema.const;
                    defaultValue = apSchema.default;
                }
                if (!type && (ANY_OF_KEY in apSchema || ONE_OF_KEY in apSchema)) {
                    type = 'object';
                }
            }
            const newKey = this.getAvailableKey('newKey', newFormData);
            const newValue = (_a = constValue !== null && constValue !== void 0 ? constValue : defaultValue) !== null && _a !== void 0 ? _a : this.getDefaultValue(type);
            // Cast this to make the `set` work properly
            set(newFormData, newKey, newValue);
            onChange(newFormData);
        };
    }
    /** Returns a flag indicating whether the `name` field is required in the object schema
     *
     * @param name - The name of the field to check for required-ness
     * @returns - True if the field `name` is required, false otherwise
     */
    isRequired(name) {
        const { schema } = this.props;
        return Array.isArray(schema.required) && schema.required.indexOf(name) !== -1;
    }
    /** Returns a default value to be used for a new additional schema property of the given `type`
     *
     * @param type - The type of the new additional schema property
     */
    getDefaultValue(type) {
        const { registry: { translateString }, } = this.props;
        switch (type) {
            case 'array':
                return [];
            case 'boolean':
                return false;
            case 'null':
                return null;
            case 'number':
                return 0;
            case 'object':
                return {};
            case 'string':
            default:
                // We don't have a datatype for some reason (perhaps additionalProperties was true)
                return translateString(TranslatableString.NewStringDefault);
        }
    }
    /** Renders the `ObjectField` from the given props
     */
    render() {
        var _a, _b, _c, _d;
        const { schema: rawSchema, uiSchema = {}, formData, errorSchema, idSchema, name, required = false, disabled, readonly, hideError, idPrefix, idSeparator, onBlur, onFocus, registry, title, } = this.props;
        const { fields, formContext, schemaUtils, translateString, globalUiOptions } = registry;
        const { SchemaField } = fields;
        const schema = schemaUtils.retrieveSchema(rawSchema, formData);
        const uiOptions = getUiOptions(uiSchema, globalUiOptions);
        const { properties: schemaProperties = {} } = schema;
        const templateTitle = (_c = (_b = (_a = uiOptions.title) !== null && _a !== void 0 ? _a : schema.title) !== null && _b !== void 0 ? _b : title) !== null && _c !== void 0 ? _c : name;
        const description = (_d = uiOptions.description) !== null && _d !== void 0 ? _d : schema.description;
        let orderedProperties;
        try {
            const properties = Object.keys(schemaProperties);
            orderedProperties = orderProperties(properties, uiOptions.order);
        }
        catch (err) {
            return (_jsxs("div", { children: [_jsx("p", { className: 'config-error', style: { color: 'red' }, children: _jsx(Markdown, { options: { disableParsingRawHTML: true }, children: translateString(TranslatableString.InvalidObjectField, [name || 'root', err.message]) }) }), _jsx("pre", { children: JSON.stringify(schema) })] }));
        }
        const Template = getTemplate('ObjectFieldTemplate', registry, uiOptions);
        const templateProps = {
            // getDisplayLabel() always returns false for object types, so just check the `uiOptions.label`
            title: uiOptions.label === false ? '' : templateTitle,
            description: uiOptions.label === false ? undefined : description,
            properties: orderedProperties.map((name) => {
                const addedByAdditionalProperties = has(schema, [PROPERTIES_KEY, name, ADDITIONAL_PROPERTY_FLAG]);
                const fieldUiSchema = addedByAdditionalProperties ? uiSchema.additionalProperties : uiSchema[name];
                const hidden = getUiOptions(fieldUiSchema).widget === 'hidden';
                const fieldIdSchema = get(idSchema, [name], {});
                return {
                    content: (_jsx(SchemaField, { name: name, required: this.isRequired(name), schema: get(schema, [PROPERTIES_KEY, name], {}), uiSchema: fieldUiSchema, errorSchema: get(errorSchema, name), idSchema: fieldIdSchema, idPrefix: idPrefix, idSeparator: idSeparator, formData: get(formData, name), formContext: formContext, wasPropertyKeyModified: this.state.wasPropertyKeyModified, onKeyChange: this.onKeyChange(name), onChange: this.onPropertyChange(name, addedByAdditionalProperties), onBlur: onBlur, onFocus: onFocus, registry: registry, disabled: disabled, readonly: readonly, hideError: hideError, onDropPropertyClick: this.onDropPropertyClick }, name)),
                    name,
                    readonly,
                    disabled,
                    required,
                    hidden,
                };
            }),
            readonly,
            disabled,
            required,
            idSchema,
            uiSchema,
            errorSchema,
            schema,
            formData,
            formContext,
            registry,
        };
        return _jsx(Template, { ...templateProps, onAddClick: this.handleAddClick });
    }
}
export default ObjectField;
//# sourceMappingURL=ObjectField.js.map