function _check_private_redeclaration(obj, privateCollection) {
    if (privateCollection.has(obj)) {
        throw new TypeError("Cannot initialize the same private elements twice on an object");
    }
}
function _class_apply_descriptor_get(receiver, descriptor) {
    if (descriptor.get) {
        return descriptor.get.call(receiver);
    }
    return descriptor.value;
}
function _class_apply_descriptor_set(receiver, descriptor, value) {
    if (descriptor.set) {
        descriptor.set.call(receiver, value);
    } else {
        if (!descriptor.writable) {
            throw new TypeError("attempted to set read only private field");
        }
        descriptor.value = value;
    }
}
function _class_extract_field_descriptor(receiver, privateMap, action) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to " + action + " private field on non-instance");
    }
    return privateMap.get(receiver);
}
function _class_private_field_get(receiver, privateMap) {
    var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
    return _class_apply_descriptor_get(receiver, descriptor);
}
function _class_private_field_init(obj, privateMap, value) {
    _check_private_redeclaration(obj, privateMap);
    privateMap.set(obj, value);
}
function _class_private_field_set(receiver, privateMap, value) {
    var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
    _class_apply_descriptor_set(receiver, descriptor, value);
    return value;
}
function _class_private_method_get(receiver, privateSet, fn) {
    if (!privateSet.has(receiver)) {
        throw new TypeError("attempted to get private field on non-instance");
    }
    return fn;
}
function _class_private_method_init(obj, privateSet) {
    _check_private_redeclaration(obj, privateSet);
    privateSet.add(obj);
}
import { defineGetter, objectEach, isObject, assignObjectDefaults, getProperty } from '../../helpers/object.mjs';
import { throwWithCause } from '../../helpers/errors.mjs';
import { arrayEach } from '../../helpers/array.mjs';
import { getPluginsNames, hasPlugin } from '../registry.mjs';
import { hasCellType } from '../../cellTypes/registry.mjs';
import { hasEditor } from '../../editors/registry.mjs';
import { hasRenderer } from '../../renderers/registry.mjs';
import { hasValidator } from '../../validators/registry.mjs';
import EventManager from '../../eventManager.mjs';
import { warn } from '../../helpers/console.mjs';
import { toSingleLine } from '../../helpers/templateLiteralTag.mjs';
import { getHardConflict } from './conflictRegistry.mjs';
const DEPS_TYPE_CHECKERS = new Map([
    [
        'plugin',
        hasPlugin
    ],
    [
        'cell-type',
        hasCellType
    ],
    [
        'editor',
        hasEditor
    ],
    [
        'renderer',
        hasRenderer
    ],
    [
        'validator',
        hasValidator
    ]
]);
export const defaultMainSettingSymbol = Symbol('mainSetting');
export const PLUGIN_KEY = 'base';
const missingDepsMsgs = [];
let initializedPlugins = null;
var /**
   * Plugin settings.
   *
   * @type {object|null}
   */ _pluginSettings = /*#__PURE__*/ new WeakMap(), /**
   * Collection of the reference to the plugins hooks.
   */ _hooks = /*#__PURE__*/ new WeakMap(), /**
   * Check if any of the keys defined in `SETTING_KEYS` configuration of the plugin is present in the provided
   * config object, or if the `SETTING_KEYS` configuration states that the plugin is relevant to the config object
   * regardless of its contents.
   *
   * @private
   * @param {Handsontable.DefaultSettings} settings The config object passed to `updateSettings`.
   * @returns {boolean}
   */ _isRelevantToSettings = /*#__PURE__*/ new WeakSet();
/**
 * @util
 * @property {Core} hot Handsontable instance.
 */ export class BasePlugin {
    static get PLUGIN_KEY() {
        return PLUGIN_KEY;
    }
    /**
   * The `SETTING_KEYS` getter defines the keys that, when present in the config object, trigger the plugin update
   * after the `updateSettings` calls.
   * - When it returns `true`, the plugin updates after all `updateSettings` calls, regardless of the contents of the
   * config object.
   * - When it returns `false`, the plugin never updates on `updateSettings` calls.
   *
   * @returns {string[] | boolean}
   */ static get SETTING_KEYS() {
        return [
            this.PLUGIN_KEY
        ];
    }
    /**
   * The `DEFAULT_SETTINGS` getter defines the plugin default settings.
   *
   * @returns {object}
   */ static get DEFAULT_SETTINGS() {
        return {};
    }
    /**
   * Validators for plugin settings.
   *
   * @type {Function|object|null}
   */ static get SETTINGS_VALIDATORS() {
        return null;
    }
    init() {
        this.pluginName = this.hot.getPluginName(this);
        this.updatePluginSettings(this.hot.getSettings()[this.constructor.PLUGIN_KEY]);
        const pluginDeps = this.constructor.PLUGIN_DEPS;
        const deps = Array.isArray(pluginDeps) ? pluginDeps : [];
        if (deps.length > 0) {
            const missingDependencies = [];
            deps.forEach((dependency)=>{
                const [type, moduleName] = dependency.split(':');
                if (!DEPS_TYPE_CHECKERS.has(type)) {
                    throwWithCause(`Unknown plugin dependency type "${type}" was found.`);
                }
                if (!DEPS_TYPE_CHECKERS.get(type)(moduleName)) {
                    missingDependencies.push(` - ${moduleName} (${type})`);
                }
            });
            if (missingDependencies.length > 0) {
                const errorMsg = [
                    `The ${this.pluginName} plugin requires the following modules:\n`,
                    `${missingDependencies.join('\n')}\n`
                ].join('');
                missingDepsMsgs.push(errorMsg);
            }
        }
        if (!initializedPlugins) {
            initializedPlugins = getPluginsNames();
        }
        if (initializedPlugins.indexOf(this.pluginName) >= 0) {
            initializedPlugins.splice(initializedPlugins.indexOf(this.pluginName), 1);
        }
        this.hot.addHookOnce('afterPluginsInitialized', ()=>{
            if (this.isEnabled && this.isEnabled()) {
                if (this.isHardConflictBlocked()) {
                    this.hot.getSettings()[this.constructor.PLUGIN_KEY] = false;
                    return;
                }
                this.enablePlugin();
            }
        });
        const isAllPluginsAreInitialized = initializedPlugins.length === 0;
        if (isAllPluginsAreInitialized) {
            if (missingDepsMsgs.length > 0) {
                const errorMsg = [
                    `${missingDepsMsgs.join('\n')}\n`,
                    'You have to import and register them manually.'
                ].join('');
                missingDepsMsgs.length = 0;
                throwWithCause(errorMsg);
            }
            this.hot.runHooks('afterPluginsInitialized');
        }
        this.initialized = true;
    }
    /**
   * Whether this plugin is blocked by a registered hard conflict (another top-level setting is truthy; for example
   * `nestedRows` blocks `pagination`, or `manualRowMove` blocks `dataProvider`). Emits a console warning when blocked.
   *
   * @returns {boolean} `true` if the plugin must not enable.
   */ isHardConflictBlocked() {
        const pluginKey = this.constructor.PLUGIN_KEY;
        const conflict = getHardConflict(this.hot.getSettings(), pluginKey);
        if (conflict) {
            const { incompatibleSettingKey } = conflict;
            warn(toSingleLine`The \`${pluginKey}\` plugin cannot be used with the \`${incompatibleSettingKey}\` option.\x20
        This combination is not supported. The plugin will remain disabled.`);
            return true;
        }
        return false;
    }
    /**
   * Enable plugin for this Handsontable instance.
   */ enablePlugin() {
        this.enabled = true;
    }
    /**
   * Disable plugin for this Handsontable instance.
   */ disablePlugin() {
        this.eventManager?.clear();
        this.clearHooks();
        this.enabled = false;
    }
    /**
   * Gets the plugin settings. If there is no setting under the provided key, it returns the default setting
   * provided by the DEFAULT_SETTINGS static property of the class.
   *
   * @param {string} [settingName] The setting name. If the setting name is not provided, it returns
   * the whole plugin's settings object.
   * @returns {*}
   */ getSetting(settingName) {
        const defaultSettings = this.constructor.DEFAULT_SETTINGS;
        const settingsValidators = this.constructor.SETTINGS_VALIDATORS;
        if (settingName === undefined) {
            if (isObject(_class_private_field_get(this, _pluginSettings))) {
                return assignObjectDefaults(_class_private_field_get(this, _pluginSettings), defaultSettings);
            }
            return _class_private_field_get(this, _pluginSettings);
        }
        let settingValue;
        if ((Array.isArray(_class_private_field_get(this, _pluginSettings)) || isObject(_class_private_field_get(this, _pluginSettings))) && defaultSettings[defaultMainSettingSymbol] === settingName) {
            if (Array.isArray(_class_private_field_get(this, _pluginSettings))) {
                settingValue = _class_private_field_get(this, _pluginSettings);
            } else {
                settingValue = _class_private_field_get(this, _pluginSettings)[settingName] ?? defaultSettings[settingName];
            }
        } else if (settingName.includes('.')) {
            const pluginValue = getProperty(_class_private_field_get(this, _pluginSettings), settingName);
            const defaultValue = getProperty(defaultSettings, settingName);
            if (isObject(pluginValue)) {
                settingValue = assignObjectDefaults(pluginValue, defaultValue);
            } else {
                settingValue = pluginValue !== undefined ? pluginValue : defaultValue;
            }
        } else if (isObject(_class_private_field_get(this, _pluginSettings))) {
            settingValue = assignObjectDefaults(_class_private_field_get(this, _pluginSettings), defaultSettings)[settingName];
        } else {
            settingValue = defaultSettings[settingName];
        }
        if (typeof settingValue === 'function' && settingsValidators && typeof settingsValidators === 'object') {
            const validator = settingsValidators[settingName];
            if (validator && typeof validator === 'function') {
                return (...args)=>{
                    const result = settingValue(...args);
                    const isValid = validator(result);
                    if (isValid === false) {
                        const formattedArgs = args.map((arg)=>typeof arg === 'string' ? `"${arg}"` : '').join(', ');
                        const source = args.length > 0 ? formattedArgs : '';
                        warn(`${this.pluginName} Plugin: "${settingName}" function (${source}) result \
               is not valid and will be ignored.`);
                        return;
                    }
                    return result;
                };
            }
        }
        return settingValue;
    }
    /**
   * Update plugin settings.
   *
   * @param {*} newSettings New settings.
   * @returns {object} Updated settings object.
   */ updatePluginSettings(newSettings) {
        const settingsValidators = this.constructor.SETTINGS_VALIDATORS;
        if (settingsValidators && typeof settingsValidators === 'function' && typeof newSettings !== 'object') {
            const isValid = settingsValidators(newSettings);
            if (isValid === false) {
                warn(`${this.pluginName} Plugin: option is not valid and it will be ignored.`);
                return;
            }
            _class_private_field_set(this, _pluginSettings, newSettings);
            return _class_private_field_get(this, _pluginSettings);
        }
        if (settingsValidators && typeof settingsValidators === 'object' && typeof newSettings === 'object') {
            if (_class_private_field_get(this, _pluginSettings) === null || typeof _class_private_field_get(this, _pluginSettings) !== 'object') {
                _class_private_field_set(this, _pluginSettings, {
                    ...this.constructor.DEFAULT_SETTINGS
                });
            }
            Object.keys(settingsValidators).forEach((key)=>{
                if (!(key in newSettings)) {
                    return;
                }
                const validator = settingsValidators[key];
                const isValid = validator ? validator(newSettings[key]) : true;
                if (isValid === false) {
                    warn(`${this.pluginName} Plugin: "${key}" option is not valid and it will be ignored.`);
                    return;
                }
                _class_private_field_get(this, _pluginSettings)[key] = newSettings[key];
            });
            return _class_private_field_get(this, _pluginSettings);
        }
        _class_private_field_set(this, _pluginSettings, newSettings);
        return newSettings;
    }
    /**
   * Add listener to plugin hooks system.
   *
   * @param {string} name The hook name.
   * @param {Function} callback The listener function to add.
   * @param {number} [orderIndex] Order index of the callback.
   *                              If > 0, the callback will be added after the others, for example, with an index of 1, the callback will be added before the ones with an index of 2, 3, etc., but after the ones with an index of 0 and lower.
   *                              If < 0, the callback will be added before the others, for example, with an index of -1, the callback will be added after the ones with an index of -2, -3, etc., but before the ones with an index of 0 and higher.
   *                              If 0 or no order index is provided, the callback will be added between the "negative" and "positive" indexes.
   */ addHook(name, callback, orderIndex) {
        _class_private_field_get(this, _hooks)[name] = _class_private_field_get(this, _hooks)[name] || [];
        const hooks = _class_private_field_get(this, _hooks)[name];
        this.hot.addHook(name, callback, orderIndex);
        hooks.push(callback);
        _class_private_field_get(this, _hooks)[name] = hooks;
    }
    /**
   * Remove all hooks listeners by hook name.
   *
   * @param {string} name The hook name.
   */ removeHooks(name) {
        arrayEach(_class_private_field_get(this, _hooks)[name] || [], (callback)=>{
            this.hot.removeHook(name, callback);
        });
    }
    /**
   * Clear all hooks.
   */ clearHooks() {
        const hooks = _class_private_field_get(this, _hooks);
        objectEach(hooks, (callbacks, name)=>this.removeHooks(name));
        hooks.length = 0;
    }
    /**
   * Register function which will be immediately called after all plugins initialized.
   *
   * @param {Function} callback The listener function to call.
   */ callOnPluginsReady(callback) {
        if (this.isPluginsReady) {
            callback();
        } else {
            this.pluginsInitializedCallbacks.push(callback);
        }
    }
    /**
   * On after plugins initialized listener.
   *
   * @private
   */ onAfterPluginsInitialized() {
        arrayEach(this.pluginsInitializedCallbacks, (callback)=>callback());
        this.pluginsInitializedCallbacks.length = 0;
        this.isPluginsReady = true;
    }
    /**
   * On update settings listener. Re-applies hard conflict rules when settings change so a plugin that is already
   * enabled disables if a conflicting top-level setting becomes truthy, even when this plugin's `SETTING_KEYS` do not
   * overlap the `updateSettings` payload.
   *
   * @private
   * @param {object} newSettings New set of settings passed to the `updateSettings` method.
   */ onUpdateSettings(newSettings) {
        if (!this.isEnabled) {
            return;
        }
        const relevantToSettings = _class_private_method_get(this, _isRelevantToSettings, isRelevantToSettings).call(this, newSettings);
        if (this.enabled && !this.isEnabled()) {
            this.disablePlugin();
        }
        if (!this.enabled && this.isEnabled()) {
            if (this.isHardConflictBlocked()) {
                return;
            }
            this.enablePlugin();
        }
        if (this.enabled && this.isEnabled() && this.isHardConflictBlocked()) {
            this.disablePlugin();
            return;
        }
        if (this.enabled && this.isEnabled() && relevantToSettings) {
            this.updatePluginSettings(newSettings[this.constructor.PLUGIN_KEY]);
            this.updatePlugin(newSettings);
        }
    }
    /**
   * Updates the plugin to use the latest options you have specified.
   *
   * @private
   */ updatePlugin() {}
    /**
   * Destroy plugin.
   */ destroy() {
        _class_private_field_set(this, _pluginSettings, null);
        this.eventManager?.destroy();
        this.clearHooks();
        objectEach(this, (value, property)=>{
            if (property !== 'hot') {
                this[property] = null;
            }
        });
        delete this.t;
        delete this.hot;
    }
    /**
   * @param {object} hotInstance Handsontable instance.
   */ constructor(hotInstance){
        _class_private_method_init(this, _isRelevantToSettings);
        _class_private_field_init(this, _pluginSettings, {
            writable: true,
            value: void 0
        });
        _class_private_field_init(this, _hooks, {
            writable: true,
            value: void 0
        });
        _class_private_field_set(this, _pluginSettings, null);
        /**
   * The instance of the {@link EventManager} class.
   *
   * @type {EventManager}
   */ this.eventManager = new EventManager(this);
        /**
   * @type {string}
   */ this.pluginName = null;
        /**
   * @type {Function[]}
   */ this.pluginsInitializedCallbacks = [];
        /**
   * @type {boolean}
   */ this.isPluginsReady = false;
        /**
   * @type {boolean}
   */ this.enabled = false;
        /**
   * @type {boolean}
   */ this.initialized = false;
        _class_private_field_set(this, _hooks, {});
        /**
     * Handsontable instance.
     *
     * @type {Core}
     */ defineGetter(this, 'hot', hotInstance, {
            writable: false
        });
        initializedPlugins = null;
        this.hot.addHook('afterPluginsInitialized', ()=>this.onAfterPluginsInitialized());
        this.hot.addHook('afterUpdateSettings', (newSettings)=>this.onUpdateSettings(newSettings));
        this.hot.addHook('beforeInit', ()=>this.init());
    }
}
function isRelevantToSettings(settings) {
    if (!settings) {
        return false;
    }
    const settingKeys = this.constructor.SETTING_KEYS;
    // If SETTING_KEYS is declared as `true` -> update the plugin regardless of the settings declared in
    // `updateSettings`.
    // If SETTING_KEYS is declared as `false` -> DON'T update the plugin regardless of the settings declared in
    // `updateSettings`.
    if (typeof settingKeys === 'boolean') {
        return settingKeys;
    }
    for(let i = 0; i < settingKeys.length; i++){
        if (settings[settingKeys[i]] !== undefined) {
            return true;
        }
    }
    return false;
}
