import $ from 'jquery';

export default {

    /**
     * This mock shows the _minimum required_ and _maximum supported_ options
     * for rest requests in shared components. If you add additional functionality to a
     * shared component rest request, be sure to add it to this mock too!
     * Please also add tests to show how any additional options you introduce are expected to work.
     *
     * The rest method assumes JSON request _and_ response data.
     *
     * @param {Object} options - The $.ajax-ish options for this request
     * @returns {Promise}
     */
    rest(options) {
        return this._internalAjax(options);
    },

    /**
     * This function closely resembles the jQuery.ajax() function with a few notable exceptions.
     *
     * - it only accepts the options signature - all options including `url` must be included on the options object.
     * - it overrides the `statusCode` option to allow you to specify your own error handling per-HTTP code.
     *
     * @param {Object} options - The $.ajax-ish options for this request
     * @returns {Promise}
     */
    _internalAjax(options) {
        const statusHandlers = options.statusCode || {};

        const done = (response, textStatus, jqXhr) => {
            const customHandler = this._getStatusHandler(jqXhr.status, statusHandlers);
            const customHandlerFn = customHandler ? customHandler.bind(this, response, textStatus, jqXhr) : null;

            return this._xhrPipe(response, textStatus, jqXhr, null, customHandlerFn, () => {
                return $.Deferred().resolveWith(this, [response, textStatus, jqXhr]);
            });
        };

        const fail = (jqXhr, textStatus, errorThrown) => {
            const response = jqXhr.responseJSON || jqXhr.responseText;

            const customHandler = this._getStatusHandler(jqXhr.status, statusHandlers);
            const customHandlerFn = customHandler ? customHandler.bind(this, response, textStatus, errorThrown, response) : null;

            return this._xhrPipe(response, textStatus, jqXhr, errorThrown, customHandlerFn, () => {
                return $.Deferred().rejectWith(this, [jqXhr, textStatus, errorThrown, response]);
            });
        };

        return $.ajax(options.url, {
            method: options.type,
            data: options.data,
        }).then(done, fail);
    },

    /**
     * We use a slightly enhanced statusCode object. In order to make it work with jQuery.ajax(), we need to transform it.
     * Our enhancements are:
     *
     * - we accept a non-functions and wrap them to be returned in a function to suppress global errors
     * - we accept a '*' to apply a status handler (or false) to all status codes
     *
     * @param {number} status - The status code to get a status handler for
     * @param {Object} statusHandlers - The status handlers to search
     * @returns {function|boolean}
     */
    _getStatusHandler(status, statusHandlers) {
        let customHandler = statusHandlers[status];
        if (customHandler === undefined || customHandler === null) {
            customHandler = statusHandlers['*'];
        }
        if (typeof customHandler === 'function') {
            return customHandler;
        }

        // Allow status handlers to be non-functions (ie false), which should always be returned
        return function() {
            return customHandler;
        };
    },

    /**
     * This function should handle AJAX errors consistently with how the implementor usually does.
     * For the purposes of this mock, we just rethrow them.
     * Note that this will only ever be called if:
     *
     * - no custom handler is specified
     * - the custom handler returns a replacement error object with which to call this function
     * - the custom handler does not return false.
     *
     * @param {Error} error - The error to be thrown
     */
    _handleError(error) {
        throw error;
    },

    _xhrPipe(response, textStatus, jqXhr, errorThrown, customHandler, fallback) {

        let error = response ? response.errors : null;
        let handleErrors = true;
        if (customHandler) {
            const ret = customHandler(error);

            // custom handler can return a deferred which will be piped through. We won't handle errors
            if (ret && typeof ret.promise === 'function') {
                return ret.promise(jqXhr);
            }

            // custom handler can return a replacement error object which will replace the one we generate
            if (ret && typeof ret === 'object') {
                error = ret;
            }

            // if the custom handler returns false, we won't handle errors,
            // and will simply fallback to normal behavior
            handleErrors = ret !== false;
        }

        if (handleErrors && error) {
            return this._handleError(response, textStatus, jqXhr, errorThrown);
        }

        return fallback();
    },
};
