/*!
 * Copyright (C) Microsoft Corporation. All rights reserved.
 */
import { readRepoConfig, writeRepoConfig } from '../Config/ConfigManager.js';
import { stringifyError } from '../ErrorUtils.js';
import { initializePlayerActions } from '../initializePlayerActions.js';
import { createApp, endSession, generateResourceStorage, getPlayerServiceConfig, makeAppSolutionAware, publishApp, saveApp, startSession, } from '../services/index.js';
export async function pushApp(context) {
    const { actionsParams, authProvider, environmentName, region, vfs, localFilePaths, logger, httpClient, } = context;
    const { appSubtype, solutionId } = actionsParams;
    const { powerConfigPath } = localFilePaths;
    initializePlayerActions({
        environmentName,
        vfs,
        authProvider,
        logger,
        region,
        httpClient,
    });
    const pushAppScenario = logger.trackScenario('PushApp');
    try {
        const powerConfig = await readRepoConfig(powerConfigPath);
        const buildPath = powerConfig.buildPath;
        if (!buildPath || !(await vfs.exists(buildPath))) {
            throw new Error(`Build path ${buildPath} does not exist`);
        }
        let attempts = 0;
        const maxAttempts = 5;
        let appName = actionsParams.appName;
        let codeAppResponse;
        const saveAppScenario = logger.trackScenario('PushApp.SaveApp');
        while (attempts < maxAttempts) {
            try {
                const blobUri = await generateResourceStorage();
                const documentUri = await uploadFilesToBlob(powerConfig, blobUri, vfs, logger);
                const codeAppMetadata = createAppMetadata(powerConfig, environmentName, documentUri, appSubtype);
                // Create app in a solution
                if (!appName) {
                    codeAppResponse = await createApp(codeAppMetadata);
                    appName = codeAppResponse.name;
                    if (solutionId) {
                        await makeAppSolutionAware(appName, solutionId);
                    }
                }
                else {
                    const session = await startSession(appName);
                    codeAppResponse = await saveApp(appName, codeAppMetadata);
                    await endSession(appName, session.currentSession.sessionId);
                }
                break;
            }
            catch (error) {
                attempts++;
                if (attempts >= maxAttempts) {
                    saveAppScenario.failure({ error: stringifyError(error) });
                    throw error;
                }
            }
        }
        if (!appName || !codeAppResponse) {
            throw new Error('Failed to upload and save app');
        }
        await publishApp(appName);
        await writeRepoConfig(powerConfigPath, {
            ...powerConfig,
            appId: codeAppResponse.name,
        });
        saveAppScenario.complete();
        return codeAppResponse;
    }
    catch (error) {
        pushAppScenario.failure({
            error: stringifyError(error),
        });
        throw error;
    }
}
async function uploadFilesToBlob(powerConfig, blobUri, vfs, logger) {
    const httpClient = getPlayerServiceConfig().httpClient;
    // The blob URL has query params. So we need to ensure those are at the end.
    const pos = blobUri.indexOf('?');
    const baseUrl = blobUri.substring(0, pos);
    const restUrl = blobUri.substring(pos);
    const { buildEntryPoint, buildPath } = powerConfig;
    if (!buildEntryPoint || !buildPath) {
        throw new Error('Invalid power config');
    }
    const uploadFilesScenario = logger.trackScenario('PushApp.SaveApp.UploadFiles', {
        buildPath,
        buildEntryPoint,
    });
    try {
        // Recursively collect all files from buildPath, handling nested directories
        const allFiles = await collectAllFiles(buildPath, vfs);
        let totalBytes = 0;
        for (const fullFilePath of allFiles) {
            const fileContent = await vfs.readFile(fullFilePath, 'utf-8');
            totalBytes += Buffer.byteLength(fileContent, 'utf-8');
            // Compute the blob-relative URL segment, normalising OS path separators cross-platform.
            const relativePath = computeRelativeBlobPath(fullFilePath, buildPath);
            const url = `${baseUrl}/${relativePath}${restUrl}`;
            await httpClient.put(url, {
                headers: {
                    'Content-Type': getMimeType(fullFilePath),
                    'x-ms-blob-type': 'BlockBlob',
                },
                body: fileContent,
            });
        }
        logger.trackActivityEvent('PushApp.SaveApp.UploadFiles.FileSize', {
            fileCount: allFiles.length,
            totalBytes,
        });
        uploadFilesScenario.complete();
        return `${baseUrl}/${computeRelativeBlobPath(buildEntryPoint, buildPath)}${restUrl}`;
    }
    catch (error) {
        uploadFilesScenario.failure({ error: stringifyError(error) });
        throw error;
    }
}
/**
 * Converts a full file path returned by the VFS (which may use OS-native separators,
 * e.g. backslashes on Windows) into a relative URL segment suitable for blob storage.
 *
 * Steps:
 *  1. Normalise `buildPath` to forward slashes and strip the leading `./` and any
 *     trailing slash, so the prefix comparison is separator-agnostic.
 *  2. Normalise `fullFilePath` to forward slashes.
 *  3. Remove the build-directory prefix and any remaining leading slash.
 *
 * @internal Exported for unit-testing only.
 */
export function computeRelativeBlobPath(fullFilePath, buildPath) {
    const normalizedBuildPath = buildPath.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '');
    return fullFilePath.replace(/\\/g, '/').replace(normalizedBuildPath, '').replace(/^\//, '');
}
function getMimeType(filePath) {
    if (filePath.endsWith('.html')) {
        return 'text/html';
    }
    if (filePath.endsWith('.js')) {
        return 'application/javascript';
    }
    if (filePath.endsWith('.css')) {
        return 'text/css';
    }
    if (filePath.endsWith('.json')) {
        return 'application/json';
    }
    return 'application/octet-stream';
}
async function collectAllFiles(dirPath, vfs) {
    const allFiles = [];
    const entries = await vfs.readdir(dirPath);
    for (const entry of entries) {
        const fullPath = vfs.join(dirPath, entry);
        const isDir = await vfs.isDirectory(fullPath);
        if (isDir) {
            // Recursively collect files from subdirectories
            const nestedFiles = await collectAllFiles(fullPath, vfs);
            allFiles.push(...nestedFiles);
        }
        else {
            // Add file to collection
            allFiles.push(fullPath);
        }
    }
    return allFiles;
}
function createAppMetadata(powerConfig, environmentName, documentUri, appSubtype) {
    const appMetadata = {
        appType: powerConfig.appType || 'CodeApp',
        appSubtype: 'BYOCApp',
        properties: {
            lifeCycleId: 'Draft',
            displayName: powerConfig.appDisplayName || 'New App' + Date.now(),
            description: powerConfig.description || '',
            backgroundColor: 'RGBA(255,255,255,1)',
            backgroundImageUri: '',
            createdByClientVersion: {
                major: 1,
            },
            appUris: {
                codeAppPackageUri: {
                    value: documentUri,
                    readonlyValue: documentUri,
                },
            },
            environment: {
                name: environmentName,
                id: `/providers/Microsoft.PowerApps/environments/${environmentName}`,
            },
            connectionReferences: constructConnectionReferences(powerConfig.connectionReferences),
            databaseReferences: constructDatabaseReferences(powerConfig.databaseReferences),
        },
    };
    if (appSubtype) {
        appMetadata.appSubtype = appSubtype;
    }
    return appMetadata;
}
export function constructConnectionReferences(configConnectionReferences) {
    const result = {};
    if (!configConnectionReferences) {
        return result;
    }
    // First pass: build base RP connection references
    for (const [key, ref] of Object.entries(configConnectionReferences)) {
        result[key] = {
            id: ref.id,
            displayName: ref.displayName,
            dataSources: ref.dataSources ?? [],
            nestedActions: [],
            actions: [],
            parameterHints: {},
            parameterHintsV2: {},
            dependents: [],
            dependencies: [],
            dataSets: ref.dataSets ?? {},
            sharedConnectionId: ref.sharedConnectionId ?? null,
            authenticationType: ref.authenticationType ?? null,
        };
    }
    // Second pass: process workflowDetails to populate parameterHintsV2, dependencies, and dependents
    for (const [key, ref] of Object.entries(configConnectionReferences)) {
        if (!ref.workflowDetails)
            continue;
        const { workflowEntityId, workflowDisplayName, dependencies, workflowName } = ref.workflowDetails;
        const parameterHintsV2 = {
            workflowEntityId: { value: workflowEntityId },
            workflowDisplayName: { value: workflowDisplayName },
            workflowName: { value: workflowName },
        };
        const parameterHints = {
            workflowEntityId: { value: workflowEntityId },
            workflowDisplayName: { value: workflowDisplayName },
            workflowName: { value: workflowName },
        };
        const dependencyUuids = [];
        if (dependencies) {
            for (const [flowConnRefKey, dependencyUuid] of Object.entries(dependencies)) {
                parameterHintsV2[flowConnRefKey] = { value: dependencyUuid };
                dependencyUuids.push(dependencyUuid);
                // Add this flow connection reference as a dependent on the dependency
                const depRef = result[dependencyUuid];
                if (depRef) {
                    depRef.dependents.push(key);
                }
            }
        }
        result[key].parameterHintsV2 = parameterHintsV2;
        // Set parameterHints. Disambiguation still uses this.
        result[key].parameterHints = parameterHints;
        result[key].dependencies = dependencyUuids;
    }
    return result;
}
function constructDatabaseReferences(databaseReferences) {
    const constructedDatabaseReferences = {};
    if (!databaseReferences) {
        return constructedDatabaseReferences;
    }
    for (const key of Object.keys(databaseReferences)) {
        const value = databaseReferences[key];
        constructedDatabaseReferences[key] = {
            databaseDetails: {
                environmentName: key,
                overrideValues: {
                    environmentVariableName: value?.environmentVariableName ?? '',
                },
            },
            dataSources: value?.dataSources,
        };
    }
    return constructedDatabaseReferences;
}
//# sourceMappingURL=PushApp.js.map