"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const equals = require("deep-equal");
/**
 * Handles parsing settings from uiSchema. Settings can be either global or form specific (it's up to the client using LajiForm to pass the global or form specific settings).
 */
class SettingsService {
    constructor(onSettingsChange, settings = {}) {
        this.settings = {};
        this.settingSavers = {};
        this.addSettingSaver = (key, fn) => {
            const settingSavers = this.settingSavers;
            settingSavers[key] = fn;
        };
        this.removeSettingSaver = (key) => {
            const settingSavers = this.settingSavers;
            delete settingSavers[key];
        };
        this.onSettingsChange = () => {
            const settings = this.getSettings();
            if (!equals(this.settings, settings)) {
                this.settings = settings;
                this._onSettingsChange(settings);
            }
        };
        this._onSettingsChange = onSettingsChange;
        this.settings = settings;
    }
    setSettings(settings = {}) {
        this.settings = settings;
    }
    getSettings() {
        const settingSavers = this.settingSavers;
        return Object.keys(settingSavers).reduce((settings, key) => {
            try {
                const value = settingSavers[key]();
                if (value === undefined) { // skip, or JSON parse will error.
                    return settings;
                }
                return Object.assign(Object.assign({}, settings), { [key]: value });
            }
            catch (e) {
                // Swallow failing settings parsing.
            }
            return settings;
        }, this.settings);
    }
    /**
     * Should be binded at React Class Component constructor. The component will unbind automatically when unmounted.
     * Hooks into the component React hooks componentDidMount, componentWillUnmount & componentDidUpdate.
     *
     * @param that this of the React component
     * @param props The constructor props
     */
    bind(that, props) {
        this.loadContextSettings(props, props.formContext.globals);
        that.state = this.loadStateSettings(props, that.state);
        const componentDidMount = that.componentDidMount;
        const componentWillUnmount = that.componentWillUnmount;
        const componentDidUpdate = that.componentDidUpdate;
        that.componentDidMount = () => {
            componentDidMount === null || componentDidMount === void 0 ? void 0 : componentDidMount.call(that);
            this.updateSettingSaver(that);
        };
        that.componentWillUnmount = () => {
            var _a;
            componentWillUnmount === null || componentWillUnmount === void 0 ? void 0 : componentWillUnmount.call(that);
            (((_a = that.props.uiSchema) === null || _a === void 0 ? void 0 : _a["ui:settings"]) || []).forEach((key) => {
                this.removeSettingSaver(this.getSettingsKey(that.props, key));
            });
        };
        that.componentDidUpdate = (prevProps, prevState) => {
            componentDidUpdate === null || componentDidUpdate === void 0 ? void 0 : componentDidUpdate.call(that, prevProps, prevState);
            if (that.props.uiSchema && (that.props.uiSchema["ui:settings"] || []).some((key) => {
                if (key.match(/^%/))
                    key = key.match(/^%([^/]*)/)[1];
                return (!equals(...[prevState, that.state].map(state => (0, utils_1.parseJSONPointer)(state, key, true))));
            })) {
                this.onSettingsChange();
            }
        };
    }
    loadStateSettings(props, state) {
        return this.loadSettings(props, state, /^((?!%))/);
    }
    loadContextSettings(props, context) {
        return this.loadSettings(props, context, /^%/);
    }
    loadSettings(props, target = {}, rule) {
        const { uiSchema, formContext } = props;
        if (!(uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema["ui:settings"])) {
            return target;
        }
        const { settings } = formContext.services.settings;
        uiSchema["ui:settings"].forEach((key) => {
            var _a, _b;
            if (this.getSettingsKey(props, key) in settings) {
                if (key.match(rule)) {
                    const last = (_b = (_a = key.match(/.*\/(.*)/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : "";
                    const withoutLastMatch = key.match(/^(%[^/]+)?(\/.*)\/.*$/);
                    const withoutLast = withoutLastMatch ? withoutLastMatch[2] : "/";
                    const lastContainer = (0, utils_1.parseJSONPointer)(target, withoutLast || "/", "createParents");
                    lastContainer[last] = JSON.parse(JSON.stringify(settings[this.getSettingsKey(props, key)]));
                }
            }
        });
        return target;
    }
    // JSON Parsing will throw exceptions for paths that aren't initialized. This is intentional -
    // we don't want to save undefined values for settings that are not set.
    updateSettingSaver(that) {
        var _a;
        function parseSettingSaver(target, key) {
            const splits = key.split("/");
            const last = splits.pop() || "";
            const lastContainer = (0, utils_1.parseJSONPointer)(target, splits.join("/"));
            if (lastContainer && last in lastContainer) {
                return lastContainer[last];
            }
            else {
                throw new Error("Setting saver parsing failed");
            }
        }
        (((_a = that.props.uiSchema) === null || _a === void 0 ? void 0 : _a["ui:settings"]) || []).forEach((key) => {
            this.addSettingSaver(this.getSettingsKey(that.props, key), () => {
                if (key.match(/^%/)) {
                    return parseSettingSaver(that.props.formContext.globals, key.replace(/^%[^/]*/, ""));
                }
                else {
                    return parseSettingSaver(that.state, key);
                }
            });
        });
    }
    // Settings are hashed with setting id, field id, field name and the options path. Settings id is optional, all the others are automatically read.
    getSettingsKey(props, key) {
        const id = this.getIdSchemaId(props);
        //const id = this.props.idSchema?.$id || this.props.id;
        const { uiSchema } = props;
        const settingsId = uiSchema && uiSchema["ui:settingsId"] !== undefined ? uiSchema["ui:settingsId"] : "";
        if (key.match(/^%/)) {
            return key;
        }
        else {
            return `${settingsId}#${id}$${uiSchema["ui:field"]}${key}`;
        }
    }
    getIdSchemaId(props) {
        return props.idSchema ? props.idSchema.$id : props.id;
    }
}
exports.default = SettingsService;
