/**
 * @typedef {Object} DataLoaderInterface~Link
 * @property {string} dataType - Type of data to which the url is generated
 * @property {string} loaderUri - Uri identifying the target in the system (e.g. node://{uuid} )
 * @property {string} label - Label / name of the target
 * @property {string} identifier - Global identifier / uuid for the data
 */
/**
 * @typedef {Object} DataLoaderInterface~Value
 * @property {string} icon
 * @property {string} loaderUri
 * @property {string} label
 */

/**
 * @interface DataLoaderInterface
 */

/**
 * @function
 * @name DataLoaderInterface#search
 * @param {Object} options
 * @param {string} searchTerm
 * @returns {Promise<LinkHandlerDataLoader~Link[]>}
 */

/**
 * @function
 * @name DataLoaderInterface#resolveValue
 * @param {Object} options
 * @param {string} identifier
 * @returns {Promise<LinkHandlerDataLoader~Value[]>}
 */

/**
 * @param {Object} object
 * @param {string} methodName
 * @param {function} callback
 */
function wrapMethod(object, methodName, callback) {
    const original = object[methodName];
    object[methodName] = function(...args) {
        const bound = original.bind(this, ...args);
        return callback(bound, ...args);
    };
}

export class DataLoaderHelper {

    /**
     * Registers a new data loader with the neos backend.
     *
     * @param globalRegistry
     * @param prefix
     * @param dataLoader
     */
    static registerDataLoader(globalRegistry, prefix, dataLoader) {
        wrapMethod(
            globalRegistry.get('dataLoaders').get('LinkLookup'),
            'dataLoaders',
            original => original().concat({ prefix, dataLoader })
        );
    }

    /**
     * Registers a data loader with a work around that allows the `resolveValue`
     * of the data loaders to be executed correctly.
     *
     * Currently the `NodeLookup` data loader is the only data loader where the
     * `resolveValue` method is executed in order to get custom previews for the
     * linked data.
     *
     * This registration method works around this issue by transforming the URIs
     * provided by the dataLoader to fit into the node query scheme. If your data
     * loader provides URIs such as `myLoader://abc-def` then this registration method
     * will transform them to `node://myLoader:abc-def`.
     *
     * WARNING: Only using this method itself will break your logic as it then has to
     * account for the new `node://` based format as well.
     *
     * Once all dataLoaders are allowed to use `resolveValue` then this method can
     * be removed in favour of the simpler `registerDataLoader` method.
     *
     * @see LinkInput#refreshState (In Neos.Neos.Ui/packages/neos-ui-editors/src/Library/LinkInput.js)
     *      for the place where `NodeLookup` / `node://` is hard coded into the react component.
     *
     * @see ConvertUrisImplementation::handleNodeUriWorkaround (PHP Class)
     *      for a preview of how the backend must handle the new URLs.
     *
     * @param globalRegistry
     * @param prefix
     * @param dataLoader
     */
    static registerDataLoaderWithValueResolveWorkaround(globalRegistry, prefix, dataLoader) {
        const prependNode = results => {
            results.forEach(res => res.loaderUri = `node://${res.loaderUri.replace('://', ':')}`);
            return results;
        };

        wrapMethod(
            globalRegistry.get('dataLoaders').get('NodeLookup'),
            'search',
            (original, ...args) => {
                const promise = dataLoader.search(...args).then(results => prependNode(results));

                return Promise.all([ original(), promise ])
                    .then(([...results]) => Array.prototype.concat(...results));
            }
        );

        wrapMethod(
            globalRegistry.get('dataLoaders').get('NodeLookup'),
            'resolveValue',
            (original, options, identifier) => {
                if (identifier.indexOf(`${prefix}:`) === 0) {
                    return dataLoader.resolveValue(
                        options,
                        identifier.replace(`${prefix}:`, '')
                    );
                }
                return original();
            }
        );
    }
}