"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 __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 __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.arrayItemKeyFunctions = exports.arrayKeyFunctions = exports.ArrayFieldTemplateWithoutKeyHandling = void 0;
exports.beforeAdd = beforeAdd;
exports.getButton = getButton;
exports.getButtons = getButtons;
exports.getButtonElems = getButtonElems;
exports.getButtonsForPosition = getButtonsForPosition;
exports.handlesArrayKeys = handlesArrayKeys;
const React = __importStar(require("react"));
const components_1 = require("../components");
const deepmerge_1 = __importDefault(require("deepmerge"));
const utils_1 = require("../../utils");
const Context_1 = __importDefault(require("../../Context"));
const ReactContext_1 = __importDefault(require("../../ReactContext"));
const react_sortable_hoc_1 = require("react-sortable-hoc");
const utils_2 = require("@rjsf/utils");
function onAdd(e, props) {
    if (!(0, utils_1.canAdd)(props))
        return;
    props.onAddClick(e);
}
function beforeAdd(props, addedIdx) {
    if (!(0, utils_1.canAdd)(props))
        return;
    const { contextId } = props.formContext;
    const startIdx = props.startIdx || (0, utils_1.getUiOptions)(props.uiSchema).startIdx;
    let { idToScrollAfterAdd = `${props.idSchema.$id}-add`, idxOffsets, totalOffset } = (0, utils_1.getUiOptions)(props.uiSchema || {});
    let idx = addedIdx !== null && addedIdx !== void 0 ? addedIdx : (props.items || props.formData || []).length;
    const offset = startIdx !== undefined
        ? startIdx
        : idxOffsets
            ? (0, utils_1.getIdxWithOffset)(idx, idxOffsets, totalOffset) - idx
            : 0;
    idx = offset + idx;
    let idToFocus = `${props.idSchema.$id}_${idx}`;
    (0, Context_1.default)(contextId).idToFocus = idToFocus;
    (0, Context_1.default)(contextId).idToScroll = idToScrollAfterAdd;
}
const buttonDefinitions = {
    add: {
        glyph: "plus",
        fn: (e) => (props) => {
            onAdd(e, props);
        },
        beforeFn: beforeAdd
    }
};
function getButton(button, props = {}) {
    function handleButton(button) {
        function rulesSatisfied(rules = {}) {
            return Object.keys(rules || {}).every(ruleName => {
                const ruleVal = rules[ruleName];
                if (ruleName === "minLength") {
                    return (props.formData || []).length >= ruleVal;
                }
                else if (ruleName === "canAdd") {
                    return (button.id && button.id !== props.idSchema.$id) || (0, utils_1.canAdd)(props);
                }
            });
        }
        const fnName = button.fnName || button.fn;
        const _buttonDefinitions = props.uiSchema && (0, utils_1.getUiOptions)(props.uiSchema).buttonDefinitions
            ? (0, deepmerge_1.default)(buttonDefinitions, (0, utils_1.getUiOptions)(props.uiSchema).buttonDefinitions)
            : buttonDefinitions;
        const definition = _buttonDefinitions[fnName];
        const _button = Object.assign(Object.assign({}, (definition || {})), button);
        if (!rulesSatisfied(_button.rules))
            return;
        if (!_button.fnName)
            _button.fnName = fnName;
        if (definition && typeof _button.fn !== "function")
            _button.fn = _buttonDefinitions[fnName].fn;
        if (fnName !== "add" || ((0, utils_1.getUiOptions)(props.uiSchema).renderAdd !== false && ((button.id && button.id !== props.idSchema.$id) || (0, utils_1.canAdd)(props))))
            return _button;
    }
    button = handleButton(button);
    if (!button)
        return;
    const id = button.id || (props.idSchema || {}).$id;
    const buttonId = `${id}-${button.fnName}${button.key ? `-${button.key}` : ""}`;
    return React.createElement(_Button, { key: buttonId, buttonId: buttonId, button: button, props: props });
}
function _Button({ button, props, getProps, buttonId }) {
    let { fn, fnName, glyph, label, className, callforward, beforeFn, callback, render, variant, tooltip, tooltipPlacement, tooltipClass, changesFormData, disabled, help, active } = button, options = __rest(button, ["fn", "fnName", "glyph", "label", "className", "callforward", "beforeFn", "callback", "render", "variant", "tooltip", "tooltipPlacement", "tooltipClass", "changesFormData", "disabled", "help", "active"]);
    label = label !== undefined
        ? (glyph ? ` ${label}` : label)
        : "";
    const onClick = React.useCallback(e => {
        const onClickProps = getProps ? getProps() : props;
        let _fn = () => fn(e)(onClickProps, options);
        const __fn = () => {
            beforeFn && beforeFn(onClickProps);
            _fn();
            callback && callback();
        };
        if (callforward) {
            e.persist();
            callforward(__fn);
        }
        else {
            __fn();
        }
    }, [props, getProps, options, callforward, beforeFn, callback, fn]);
    return render ? render(onClick, button) : (React.createElement(components_1.Button, { id: buttonId, className: className, onClick: onClick, variant: variant, active: active, tooltip: tooltip || help, tooltipPlacement: tooltipPlacement, tooltipClass: tooltipClass, disabled: disabled || ((fnName === "add" || changesFormData) && (props.disabled || props.readonly)), style: button.style },
        glyph && React.createElement(ReactContext_1.default.Consumer, null, ({ theme: { Glyphicon } }) => React.createElement(Glyphicon, { glyph: glyph })),
        glyph ? ` ${label}` : label,
        help && React.createElement(components_1.Help, null)));
}
function getButtons(buttons = [], props = {}) {
    const addBtnAdded = buttons.some(button => button.fn === "add");
    if (!addBtnAdded && (!props || (0, utils_1.canAdd)(props)))
        buttons = [{ fn: "add", id: props.idSchema.$id }, ...buttons];
    return buttons;
}
function getButtonElems(buttons = [], props = {}) {
    buttons = getButtons(buttons, props);
    let buttonElems = buttons.map(button => getButton(button, props));
    if (props.uiSchema["ui:buttons"]) {
        buttonElems = [...buttonElems, ...props.uiSchema["ui:buttons"]];
    }
    return getButtonsElem(buttonElems, props);
}
function getButtonsElem(buttonElems = [], props = {}) {
    if (!Array.isArray(buttonElems) || buttonElems.length === 0) {
        return null;
    }
    return (React.createElement(ReactContext_1.default.Consumer, null, ({ theme: { ButtonToolbar } }) => React.createElement(ButtonToolbar, { className: (0, utils_1.getUiOptions)(props.uiSchema)["ui:buttonsDesktopLayout"] ? "desktop-layout" : undefined, key: "buttons" }, buttonElems)));
}
function getButtonsForPosition(props, buttonDescriptions = [], position, defaultPosition = "bottom") {
    buttonDescriptions = buttonDescriptions.filter(button => button.position === position || (!button.position && position === defaultPosition));
    return (buttonDescriptions && buttonDescriptions.length) ?
        buttonDescriptions.map(buttonDescription => getButton(buttonDescription, props)).filter(button => button) :
        null;
}
function handlesArrayKeys(ComposedComponent) {
    var _a;
    return _a = class ArrayFieldTemplateField extends ComposedComponent {
            constructor() {
                super(...arguments);
                this.onFocus = target => {
                    const context = (0, Context_1.default)(this.props.formContext.contextId);
                    if (target === "last") {
                        context.idToFocus = `${this.props.idSchema.$id}_${this.props.formData.length - 1}`;
                        context.idToScroll = `_laji-form_${this.props.idSchema.$id}_${this.props.formData.length - 2}`;
                    }
                    else {
                        console.warn(`custom event "focus" has only "last" implemented. Target value was: ${target}`);
                    }
                };
                this.onCopy = (options = {}) => {
                    const { type = "blacklist", filter = [] } = options;
                    const { buttonDefinitions = {} } = (0, utils_1.getUiOptions)(this.props.uiSchema);
                    const { copy } = buttonDefinitions;
                    if (copy) {
                        copy.fn()(this.props, { type, filter });
                        if (copy.callback) {
                            copy.callback();
                        }
                    }
                };
            }
            componentDidMount() {
                this.addKeyHandlers(this.props);
                this.addChildKeyHandlers(this.props);
                this.addCustomEventListeners(this.props);
                if (super.componentDidMount)
                    super.componentDidMount();
            }
            componentDidUpdate(prevProps, prevState) {
                this.removeKeyHandlers(prevProps);
                this.addKeyHandlers(this.props);
                this.removeChildKeyHandlers(prevProps);
                this.addChildKeyHandlers(this.props);
                this.removeCustomEventListeners(prevProps);
                this.addCustomEventListeners(this.props);
                if (super.componentDidUpdate)
                    super.componentDidUpdate(prevProps, prevState);
            }
            componentWillUnmount() {
                this.removeKeyHandlers(this.props);
                this.removeChildKeyHandlers(this.props);
                this.removeCustomEventListeners(this.props);
                if (super.componentWillUnmount)
                    super.componentWillUnmount();
            }
            addKeyHandlers() {
                const [keys, options] = (super.getKeyHandlers || this.getKeyHandlers).call(this, this.props);
                this.arrayKeyFunctions = keys;
                this.props.formContext.services.keyHandler.addKeyHandler(this.props.idSchema.$id, keys, options);
            }
            removeKeyHandlers(props) {
                this.props.formContext.services.keyHandler.removeKeyHandler(props.idSchema.$id, this.arrayKeyFunctions);
            }
            getKeyHandlers(props) {
                const { arrayKeyFunctions: _arrayKeyFunctions } = (0, utils_1.getUiOptions)(props.uiSchema);
                return [_arrayKeyFunctions ? Object.assign({}, _arrayKeyFunctions) : Object.assign({}, exports.arrayKeyFunctions), {
                        getProps: () => this.props,
                    }];
            }
            addChildKeyHandlers(props) {
                this.childKeyHandlers = (super.getChildKeyHandlers || this.getChildKeyHandlers).call(this, props);
                this.childKeyHandlers.forEach(handler => {
                    this.props.formContext.services.keyHandler.addKeyHandler(...handler);
                });
            }
            removeChildKeyHandlers() {
                this.childKeyHandlers.forEach(handler => {
                    this.props.formContext.services.keyHandler.removeKeyHandler(...handler);
                });
            }
            getChildKeyHandlers(props) {
                return props.items.map((item, i) => {
                    const id = `${props.idSchema.$id}_${i}`;
                    return [id, exports.arrayItemKeyFunctions, { getProps: () => this.props, id, getDeleteButton: () => {
                                return this.deleteButtonRefs[i];
                            } }];
                });
            }
            getCustomEventListeners() {
                return [
                    ["focus", this.onFocus],
                    ["copy", this.onCopy]
                ];
            }
            addCustomEventListeners(props) {
                const { customEvents } = props.formContext.services;
                const customEventListeners = (super.getCustomEventListeners || this.getCustomEventListeners).call(this, props);
                this.customEventListeners = customEventListeners;
                customEventListeners.forEach(params => customEvents.add(props.idSchema.$id, ...params));
            }
            removeCustomEventListeners(props) {
                const { customEvents } = props.formContext.services;
                this.customEventListeners.forEach(params => customEvents.remove(props.idSchema.$id, ...params));
            }
        },
        _a.displayName = (0, utils_1.getReactComponentName)(ComposedComponent) + "_handlesArrayKeys",
        _a;
}
const SortableList = (0, react_sortable_hoc_1.SortableContainer)(({ items, itemProps, nonOrderables, formData }) => (React.createElement("div", null, items.map((item, i) => {
    return React.createElement(SortableItem, { key: (0, utils_1.isObject)(formData[i]) ? (0, utils_1.getUUID)(formData[i]) : i, index: i, item: item, disabled: (!itemProps[i].hasMoveDown && !itemProps[i].hasMoveUp) || nonOrderables.includes(i) });
}))));
const SortableItem = (0, react_sortable_hoc_1.SortableElement)(({ item }) => item);
class ArrayFieldTemplateWithoutKeyHandling extends React.Component {
    constructor() {
        super(...arguments);
        this.onSort = ({ oldIndex, newIndex }) => {
            this.props.items[oldIndex].onReorderClick(oldIndex, newIndex)();
        };
        this.onFocuses = [];
        this.getOnFocus = (i) => () => {
            (0, Context_1.default)(this.props.formContext.contextId)[`${this.props.idSchema.$id}.activeIdx`] = i + ((0, utils_1.getUiOptions)(this.props.uiSchema).startIdx || 0);
        };
    }
    render() {
        const { props } = this;
        const { readonly, disabled } = this.props;
        const { Label, uiSchemaContext } = this.props.formContext;
        const { confirmDelete = uiSchemaContext.confirmDelete, renderTitleAsLabel, deleteCorner, removable = true, orderable, nonRemovables = [], nonOrderables = [], "ui:deleteHelp": deleteHelp, buttons = [] } = (0, utils_1.getUiOptions)(props.uiSchema);
        const Title = renderTitleAsLabel ? Label : (0, utils_2.getTemplate)("TitleFieldTemplate", props.registry, (0, utils_1.getUiOptions)(props.uiSchema));
        const Description = (0, utils_2.getTemplate)("DescriptionFieldTemplate", this.props.registry, (0, utils_1.getUiOptions)(props.uiSchema));
        if (!this.deleteButtonRefs)
            this.deleteButtonRefs = [];
        const _buttons = getButtons(buttons, props);
        const topButtons = getButtonsElem(getButtonsForPosition(props, _buttons, "top"), props);
        const bottomButtons = getButtonsElem(getButtonsForPosition(props, _buttons, "bottom"), props);
        const getRefFor = i => elem => { this.deleteButtonRefs[i] = elem; };
        const items = props.items.map((item, i) => {
            const getDeleteButton = () => (React.createElement("div", { className: "laji-form-field-template-buttons" },
                React.createElement(components_1.DeleteButton, { id: `${props.idSchema.$id}_${i}`, disabled: disabled || readonly, ref: getRefFor(i), onClick: item.onDropIndexClick(item.index), confirm: confirmDelete, corner: deleteCorner, tooltip: deleteHelp, translations: props.formContext.translations })));
            if (!this.onFocuses[i]) {
                this.onFocuses[i] = this.getOnFocus(i);
            }
            return (React.createElement("div", { key: (0, utils_1.getUUID)(props.formData[i]) || item.key, className: "laji-form-field-template-item keep-vertical field-array-row", onFocus: this.onFocuses[i] },
                React.createElement("div", { className: "laji-form-field-template-schema" }, item.children),
                item.hasRemove && !nonRemovables.includes(item.index) && removable && getDeleteButton()));
        });
        const title = "ui:title" in props.uiSchema ? props.uiSchema["ui:title"] : props.title;
        return (React.createElement("div", { className: props.className },
            React.createElement(Title, { title: title, label: title, uiSchema: this.props.uiSchema, registry: this.props.registry, id: this.props.idSchema.$id }),
            topButtons,
            props.description && React.createElement(Description, { description: props.description }),
            orderable ?
                React.createElement(SortableList, { helperClass: "laji-form reorder-active", distance: 5, items: items, formData: props.formData, onSortEnd: this.onSort, itemProps: props.items, nonOrderables: nonOrderables }) :
                items,
            bottomButtons));
    }
}
exports.ArrayFieldTemplateWithoutKeyHandling = ArrayFieldTemplateWithoutKeyHandling;
exports.default = handlesArrayKeys(ArrayFieldTemplateWithoutKeyHandling);
exports.arrayKeyFunctions = {
    navigateArray: function (e, { reverse, getProps, navigateCallforward, getCurrentIdx, focusByIdx, getIdToScrollAfterNavigate }) {
        var _a;
        function focusIdx(idx, prop) {
            function callback() {
                const options = (0, utils_1.getUiOptions)(getProps().uiSchema);
                const { focusOnNavigate = true, idToFocusAfterNavigate, keepPropFocusOnNavigate = false } = options;
                const idByIdx = `${getProps().idSchema.$id}_${idx}`;
                const _idToFocusAfterNavigate = idToFocusAfterNavigate ||
                    prop && keepPropFocusOnNavigate
                    ? `${idByIdx}_${prop}`
                    : idByIdx;
                const idToScrollAfterNavigate = options.idToScrollAfterNavigate
                    ? options.idToScrollAfterNavigate
                    : getIdToScrollAfterNavigate
                        ? getIdToScrollAfterNavigate()
                        : undefined;
                getProps().formContext.utils.focusAndScroll(_idToFocusAfterNavigate, idToScrollAfterNavigate, focusOnNavigate);
            }
            if (focusByIdx) {
                focusByIdx(idx, prop, callback);
            }
            else {
                callback();
            }
        }
        const nearestSchemaElemId = getProps().formContext.utils.findNearestParentSchemaElemId(document.activeElement);
        // Should contain all nested array item ids. We want the last one, which is focused.
        const activeItemQuery = nearestSchemaElemId.match(new RegExp(`${getProps().idSchema.$id}_\\d+`, "g"));
        const focusedIdx = activeItemQuery ? (0, utils_1.getReversedFormDataIndex)(+activeItemQuery[0].replace(/^.*_(\d+)$/, "$1"), getProps().uiSchema) : undefined;
        const focusedPropMaybe = (_a = nearestSchemaElemId.match(new RegExp(`${getProps().idSchema.$id}_${focusedIdx}_(.+)`))) === null || _a === void 0 ? void 0 : _a[1];
        const focusedProp = isNaN(focusedPropMaybe) ? focusedPropMaybe : undefined;
        const currentIdx = getCurrentIdx ? getCurrentIdx() : focusedIdx;
        const arrayLength = getProps().formData ? getProps().formData.length : 0;
        if ((0, utils_1.isNullOrUndefined)(currentIdx) && arrayLength > 0) {
            focusIdx(reverse ? arrayLength - 1 : 0, focusedProp);
            return true;
        }
        else {
            let amount = 0;
            if (focusedIdx === currentIdx)
                amount = reverse ? -1 : 1;
            const nextIdx = currentIdx + amount;
            if (amount === 0 || amount < 0 && nextIdx >= 0 || amount > 0 && nextIdx < arrayLength) {
                if (navigateCallforward) {
                    e.persist();
                    navigateCallforward(() => focusIdx(nextIdx, focusedProp), nextIdx);
                }
                else {
                    focusIdx(nextIdx, focusedProp);
                }
                return true;
            }
        }
        return false;
    },
    insert: function (e, _props) {
        const { getProps, insertCallforward } = _props;
        const props = getProps();
        function afterInsert() {
            onAdd(e, props);
        }
        if (!props.disabled && !props.readonly && (0, utils_1.canAdd)(props) && (0, utils_1.getUiOptions)(props.uiSchema).canAddByKey !== false) {
            if (e.target) {
                e.target.blur();
            }
            if (insertCallforward) {
                e.persist();
                beforeAdd(props);
                insertCallforward(() => {
                    afterInsert();
                });
            }
            else {
                beforeAdd(props);
                afterInsert();
            }
            return true;
        }
        return false;
    }
};
exports.arrayItemKeyFunctions = {
    delete: function (e, { getDeleteButton, id, getProps }) {
        const { items, idSchema, formContext, readonly, disabled } = getProps();
        if (readonly || disabled || !(0, utils_1.isDescendant)(getProps().formContext.utils.getSchemaElementById(id), e.target)) {
            return;
        }
        if (!getDeleteButton)
            return;
        const deleteButton = getDeleteButton();
        if (!deleteButton || !deleteButton.onClick)
            return;
        const activeId = formContext.utils.findNearestParentSchemaElemId(document.activeElement);
        const idxsMatch = activeId.match(/_\d+/g);
        const idx = +idxsMatch[idxsMatch.length - 1].replace("_", "");
        const elem = getProps().formContext.utils.getSchemaElementById(`${idSchema.$id}_${idx}`);
        const prevElem = elem ? formContext.utils.getNextInput((0, utils_1.getTabbableFields)(elem)[0], !!"reverse") : null;
        deleteButton.onClick(e, (deleted) => {
            if (deleted) {
                const idxToFocus = idx === items.length - 1 ? idx - 1 : idx;
                if (idxToFocus >= 0) {
                    getProps().formContext.utils.focusById(`${idSchema.$id}_${idxToFocus}`);
                }
                else if (prevElem) {
                    prevElem.focus();
                }
            }
            else {
                getProps().formContext.utils.focusById(`${activeId}`);
            }
        });
        return true;
    }
};
