// Copyright (c) 2009-2026 SAP SE, All Rights Reserved

sap.ui.define([
    "sap/base/Log",
    "sap/ushell/utils"
], (
    Log,
    ushellUtils
) => {
    "use strict";

    /**
     * @alias sap.ushell.services.PersonalizationV2.Operation
     * @class
     * @classdesc A utility class to delay function calls with the ability to run immediately or cancel
     *
     * @since 1.144.0
     * @private
     */
    class Operation {
        #bCallbackIsRunning = false;
        #oCallbackResultDeferred;
        #fnCallback;
        #iTimeout;
        #iDelayMs;

        static Canceled = Symbol("Canceled");

        /**
         * Creates an instance of Operation.
         * The operation is delayed and only called when required.
         * @param {function} fnCallback The function to delay.
         * @param {int} iDelayMs The delay in milliseconds.
         *
         * @since 1.144.0
         * @private
         */
        constructor (fnCallback, iDelayMs = 0) {
            this.#fnCallback = fnCallback;
            this.#iDelayMs = iDelayMs;

            this.#bCallbackIsRunning = false;
            this.#oCallbackResultDeferred = Promise.withResolvers();
        }

        /**
         * Calls the operation.
         * The function is only called once.
         * @returns {Promise} A promise that resolves with the function's result.
         *
         * @since 1.144.0
         * @private
         */
        async #runCallback () {
            if (this.#bCallbackIsRunning) {
                return this.awaitResult();
            }

            this.#bCallbackIsRunning = true;
            clearTimeout(this.#iTimeout);

            try {
                const vResult = await ushellUtils.promisify(this.#fnCallback());
                this.#oCallbackResultDeferred.resolve(vResult);
                return vResult;
            } catch (oError) {
                this.#oCallbackResultDeferred.reject(oError);
                throw oError;
            }
        }

        /**
         * Starts the delay and runs the function afterwards.
         * @returns {Promise} A promise that resolves with the function's result.
         *
         * @since 1.144.0
         * @private
         */
        async run () {
            if (this.#iTimeout) {
                throw new Error("Delay already started");
            }

            const { promise, resolve, reject } = Promise.withResolvers();

            if (this.#iDelayMs < 0) { // get close to sync jQuery.Deferred behavior
                this.#runCallback()
                    .then(resolve)
                    .catch(reject);
            } else {
                this.#iTimeout = setTimeout(() => {
                    this.#runCallback()
                        .then(resolve)
                        .catch(reject);
                }, this.#iDelayMs);
            }

            return promise;
        }

        /**
         * Runs the operation immediately.
         * Stops the delay.
         * @returns {Promise} A promise that resolves with the function's result.
         *
         * @since 1.144.0
         * @private
         */
        async runImmediately () {
            clearTimeout(this.#iTimeout);

            return this.#runCallback();
        }

        /**
         * Checks if the function call can be canceled.
         * @returns {boolean} True if the function call is cancelable, false otherwise.
         *
         * @since 1.144.0
         * @private
         */
        isCancelable () {
            return !this.#bCallbackIsRunning;
        }

        /**
         * Awaits the result of the operation callback.
         * @returns {Promise} A promise that resolves with the function's result.
         *
         * @since 1.144.0
         * @private
         */
        async awaitResult () {
            return this.#oCallbackResultDeferred.promise;
        }

        /**
         * Cancels the operation callback.
         * Stops the delay.
         * It will not be called if it wasn't called before.
         * If the function was already called it returns the previous result.
         * @returns {Promise} A promise that resolves with the function's result.
         *
         * @since 1.144.0
         * @private
         */
        async cancel () {
            if (!this.isCancelable()) {
                // cannot cancel running call
                Log.warning("Function call is not cancelable. Waiting for actual result.", null, "sap.ushell.services.PersonalizationV2");

                // here we could implement some actual cancelation logic (e.g. for pending requests)
                return this.awaitResult();
            }

            clearTimeout(this.#iTimeout);

            this.#oCallbackResultDeferred.resolve(Operation.Canceled);
        }
    }

    return Operation;
});
