/*
 * Copyright (c) 2023-2025 Datalayer, Inc.
 * Distributed under the terms of the Modified BSD License.
 */
/**
 * Runtimes APIs.
 */
import { URLExt } from '@jupyterlab/coreutils';
import { PromiseDelegate } from '@lumino/coreutils';
import { Upload } from 'tus-js-client';
import { requestDatalayerAPI, } from '../../api/index.js';
import { asRuntimeSnapshot } from '../../models/index.js';
import { iamStore, runtimesStore } from '../../state/index.js';
import { sleep } from '../../utils/index.js';
/**
 * Get available Environments.
 */
export async function getEnvironments() {
    const data = await requestDatalayerAPI({
        url: URLExt.join(runtimesStore.getState().runtimesRunUrl, 'api/runtimes/v1/environments'),
        token: iamStore.getState().token,
    });
    if (!data.success) {
        console.error('Failed to fetch available environments.', data);
        return [];
    }
    return data.environments ?? [];
}
/**
 * Create a Runtime.
 */
export async function createRuntime(options) {
    const { externalToken, token } = iamStore.getState();
    const body = {
        environment_name: options.environmentName,
        type: options.type ?? 'notebook',
        given_name: options.givenName,
        credits_limit: options.creditsLimit,
    };
    if (options.capabilities) {
        body['capabilities'] = options.capabilities;
    }
    if (options.snapshot) {
        body['from'] = options.snapshot;
    }
    const data = await requestDatalayerAPI({
        url: URLExt.join(runtimesStore.getState().runtimesRunUrl, `api/runtimes/v1/runtimes`),
        method: 'POST',
        body,
        token: token,
        // externalToken may be needed for addons (like credits...).
        headers: externalToken
            ? {
                'X-External-Token': externalToken,
            }
            : undefined,
    });
    if (!data.success || !data.runtime) {
        const msg = `Failed to create a kernel for the environment ${options.environmentName}.`;
        console.error(msg, data);
        throw new Error(msg);
    }
    return data.runtime;
}
/**
 * Get the Runtimes.
 */
export async function getRuntimes() {
    const data = await requestDatalayerAPI({
        url: URLExt.join(runtimesStore.getState().runtimesRunUrl, 'api/runtimes/v1/runtimes'),
        token: iamStore.getState().token,
    });
    if (!data.success) {
        const msg = 'Failed to list the running kernels.';
        console.error(msg, data);
        throw new Error(msg);
    }
    return data.runtimes ?? [];
}
/**
 * Delete a Runtime
 */
export async function deleteRuntime(options) {
    const externalToken = iamStore.getState().externalToken;
    await requestDatalayerAPI({
        url: URLExt.join(runtimesStore.getState().runtimesRunUrl, `api/runtimes/v1/runtimes/${options.id}`) +
            URLExt.objectToQueryString(options.reason ? { reason: options.reason } : {}),
        method: 'DELETE',
        token: iamStore.getState().token,
        // externalToken may be needed for addons (like credits...).
        headers: externalToken
            ? {
                'X-External-Token': externalToken,
            }
            : undefined,
    });
}
/**
 * Snapshot a Runtime.
 */
export async function snapshotRuntime(options) {
    const data = await requestDatalayerAPI({
        url: URLExt.join(runtimesStore.getState().runtimesRunUrl, 'api/runtimes/v1/runtime-snapshots'),
        method: 'POST',
        body: {
            pod_name: options.id,
            name: options.name,
            description: options.description,
            stop: options.stop,
        },
        token: iamStore.getState().token,
    });
    if (!data.success || !data.snapshot) {
        throw new Error(`Failed to take the runtime snapshot ${options.id} - ${data}`);
    }
    return asRuntimeSnapshot(data.snapshot);
}
/**
 * Get Runtime Snapshots.
 */
export async function getRuntimeSnapshots() {
    const data = await requestDatalayerAPI({
        url: URLExt.join(runtimesStore.getState().runtimesRunUrl, 'api/runtimes/v1/runtime-snapshots'),
        token: iamStore.getState().token,
    });
    if (!data.success) {
        console.error('Failed to fetch runtime snapshots.', data);
        return [];
    }
    return (data.snapshots ?? []).map(asRuntimeSnapshot);
}
/**
 * Load a Runtime Snapshot within a Runtime.
 */
export async function loadRuntimeSnapshot(options) {
    const data = await requestDatalayerAPI({
        url: URLExt.join(runtimesStore.getState().runtimesRunUrl, 'api/runtimes/v1/runtimes', options.id),
        method: 'PUT',
        body: {
            from: options.from,
        },
        token: iamStore.getState().token,
    });
    if (!data.success) {
        throw new Error(`Failed to load the runtime snapshot; ${data.message}`);
    }
}
/**
 * Returns the Runtime Snapshot download URL.
 *
 * @param id Snapshot UID to download
 * @returns The download URL
 */
export function createRuntimeSnapshotDownloadURL(id) {
    return (URLExt.join(runtimesStore.getState().runtimesRunUrl, `api/runtimes/v1/runtime-snapshots/${id}`) +
        URLExt.objectToQueryString({
            download: '1',
            token: iamStore.getState().token ?? '',
        }));
}
/**
 * Export a Runtime Snapshot.
 *
 * @param id Runtime snapshot UID to download
 */
export function exportRuntimeSnapshot(id) {
    const url = createRuntimeSnapshotDownloadURL(id);
    const element = document.createElement('a');
    element.href = url;
    element.download = '';
    document.body.appendChild(element);
    element.click();
    document.body.removeChild(element);
}
/**
 * Delete a Runtime Snapshot.
 */
export async function deleteRuntimeSnapshot(id) {
    await requestDatalayerAPI({
        url: URLExt.join(runtimesStore.getState().runtimesRunUrl, `api/runtimes/v1/runtime-snapshots/${id}`),
        method: 'DELETE',
        token: iamStore.getState().token,
    });
    // Poll Runtime Snapshot state up-to its deletion.
    try {
        let sleepTimeout = 1000;
        while (true) {
            await sleep(sleepTimeout);
            sleepTimeout *= 2;
            const response = await requestDatalayerAPI({
                url: URLExt.join(runtimesStore.getState().runtimesRunUrl, `api/runtimes/v1/runtime-snapshots/${id}`),
                token: iamStore.getState().token,
            });
            if (response.success === false) {
                throw new Error(response.message);
            }
        }
    }
    catch (error) {
        if (error.name === 'RunResponseError' &&
            error.response.status === 404) {
            // Expected not found
        }
        else {
            throw error;
        }
    }
}
/**
 * Update Runtime Snapshot metadata.
 */
export async function updateRuntimeSnapshot(id, metadata) {
    if (metadata.name || metadata.description) {
        await requestDatalayerAPI({
            url: URLExt.join(runtimesStore.getState().runtimesRunUrl, `api/runtimes/v1/runtime-snapshots/${id}`),
            method: 'PATCH',
            body: { ...metadata },
            token: iamStore.getState().token,
        });
    }
}
/**
 * Upload a Runtime Snapshot.
 *
 * Note: The promise will be rejected if the runtime state is empty.
 */
export async function uploadRuntimeSnapshot(options) {
    if (options.file.size === 0) {
        return Promise.reject('Empty Runtime Snapshot.');
    }
    const tracker = new PromiseDelegate();
    // Create a new tus upload.
    const upload = new Upload(options.file, {
        // Endpoint is the upload creation URL from your tus server.
        endpoint: `${runtimesStore.getState().runtimesRunUrl}/api/runtimes/v1/runtime-snapshots/upload`,
        headers: { Authorization: `Bearer ${iamStore.getState().token}` },
        // Retry delays will enable tus-js-client to automatically retry on errors.
        // retryDelays: [0, 3000, 5000, 10000, 20000],
        retryDelays: null,
        // Attach additional meta data about the file for the server.
        metadata: options.metadata,
        // Callback for errors which cannot be fixed using retries.
        onError: error => {
            console.error(`Failed to upload ${options.metadata.filename}.`, error);
            tracker.reject(error);
        },
        // Callback for reporting upload progress.
        onProgress: options.onProgress,
        // Callback for once the upload is completed.
        onSuccess: () => {
            tracker.resolve();
        },
    });
    // Check if there are any previous uploads to continue then start the upload.
    const previousUploads = await upload.findPreviousUploads();
    // Found previous uploads so we select the first one.
    if (previousUploads.length) {
        upload.resumeFromPreviousUpload(previousUploads[0]);
    }
    // Start the upload.
    upload.start();
    return tracker.promise;
}
