import * as utils from '../../../lib/utils.js';
import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';

// Track in-flight requests to avoid duplicate backend calls
// Each entry stores: { promise, timestamp }
const inflightRequests = new Map();

// Time window (in ms) to group duplicate requests together
// Requests made within this window will share the same backend call
const DEDUPLICATION_WINDOW_MS = 2000; // 2 seconds

const stat = async function (...args) {
    let options;

    // If first argument is an object, it's the options
    if ( typeof args[0] === 'object' && args[0] !== null ) {
        options = args[0];
    } else {
        // Otherwise, we assume separate arguments are provided
        options = {
            path: args[0],
            options: typeof args[1] === 'object' ? args[1] : {},
            success: typeof args[1] === 'object' ? args[2] : args[1],
            error: typeof args[1] === 'object' ? args[3] : args[2],
            // Add more if needed...
        };
    }

    return new Promise(async (resolve, reject) => {
        // consistency levels
        if ( ! options.consistency ) {
            options.consistency = 'strong';
        }

        // Generate cache key based on path or uid
        let cacheKey;
        if ( options.path ) {
            cacheKey = `item:${ options.path}`;
        }

        if ( options.consistency === 'eventual' && !options.returnSubdomains && !options.returnPermissions && !options.returnVersions && !options.returnSize ) {
            // Check cache
            const cachedResult = await puter._cache.get(cacheKey);
            if ( cachedResult ) {
                resolve(cachedResult);
                return;
            }
        }

        // Generate deduplication key based on all request parameters
        const deduplicationKey = JSON.stringify({
            path: options.path,
            uid: options.uid,
            returnSubdomains: options.returnSubdomains,
            returnPermissions: options.returnPermissions,
            returnVersions: options.returnVersions,
            returnSize: options.returnSize,
            consistency: options.consistency,
        });

        // Check if there's already an in-flight request for the same parameters
        const existingEntry = inflightRequests.get(deduplicationKey);
        const now = Date.now();

        if ( existingEntry ) {
            const timeSinceRequest = now - existingEntry.timestamp;

            // Only reuse the request if it's within the deduplication window
            if ( timeSinceRequest < DEDUPLICATION_WINDOW_MS ) {
                // Wait for the existing request and return its result
                try {
                    const result = await existingEntry.promise;
                    resolve(result);
                } catch ( error ) {
                    reject(error);
                }
                return;
            } else {
                // Request is too old, remove it from the tracker
                inflightRequests.delete(deduplicationKey);
            }
        }

        // Create a promise for this request and store it to deduplicate concurrent calls
        const requestPromise = new Promise(async (resolveRequest, rejectRequest) => {
            // If auth token is not provided and we are in the web environment,
            // try to authenticate with Puter
            if ( !puter.authToken && puter.env === 'web' ) {
                try {
                    await puter.ui.authenticateWithPuter();
                } catch (e) {
                    // if authentication fails, throw an error
                    rejectRequest('Authentication failed.');
                    return;
                }
            }

            // create xhr object
            const xhr = utils.initXhr('/stat', this.APIOrigin, undefined, 'post', 'text/plain;actually=json');

            // set up event handlers for load and error events
            utils.setupXhrEventHandlers(xhr, options.success, options.error, async (result) => {
                // Calculate the size of the result for cache eligibility check
                const resultSize = JSON.stringify(result).length;

                // Cache the result if it's not bigger than MAX_CACHE_SIZE
                const MAX_CACHE_SIZE = 20 * 1024 * 1024;

                if ( resultSize <= MAX_CACHE_SIZE ) {
                    // UPSERT the cache
                    puter._cache.set(cacheKey, result);
                }

                resolveRequest(result);
            }, rejectRequest);

            let dataToSend = {};
            if ( options.uid !== undefined ) {
                dataToSend.uid = options.uid;
            } else if ( options.path !== undefined ) {
                // If dirPath is not provided or it's not starting with a slash, it means it's a relative path
                // in that case, we need to prepend the app's root directory to it
                dataToSend.path = getAbsolutePathForApp(options.path);
            }

            dataToSend.return_subdomains = options.returnSubdomains;
            dataToSend.return_permissions = options.returnPermissions;
            dataToSend.return_versions = options.returnVersions;
            dataToSend.return_size = options.returnSize;
            dataToSend.auth_token = this.authToken;

            xhr.send(JSON.stringify(dataToSend));
        });

        // Store the promise and timestamp in the in-flight tracker
        inflightRequests.set(deduplicationKey, {
            promise: requestPromise,
            timestamp: now,
        });

        // Wait for the request to complete and clean up
        try {
            const result = await requestPromise;
            inflightRequests.delete(deduplicationKey);
            resolve(result);
        } catch ( error ) {
            inflightRequests.delete(deduplicationKey);
            reject(error);
        }
    });
};

export default stat;