// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import path from 'node:path';
import { Async, FileSystem, Path } from '@rushstack/node-core-library';
import { CommandLineAction } from '@rushstack/ts-command-line';
import { makeBinLinksAsync } from '../../../../Utils';
import { getExtractorMetadataAsync } from '../../utilities/CreateLinksUtilities';
import { TARGET_ROOT_FOLDER, REALIZE_FILES_PARAMETER_NAME, LINK_BINS_PARAMETER_NAME, MAX_CONCURRENCY } from '../../utilities/constants';
import { removeLinksAsync } from './RemoveLinksAction';
async function createLinksAsync(terminal, targetRootFolder, extractorMetadataObject) {
    await Async.forEachAsync(extractorMetadataObject.links, async (linkInfo) => {
        // Link to the relative path for symlinks
        const newLinkPath = path.join(targetRootFolder, linkInfo.linkPath);
        const linkTargetPath = path.join(targetRootFolder, linkInfo.targetPath);
        // Make sure the containing folder exists
        await FileSystem.ensureFolderAsync(path.dirname(newLinkPath));
        // NOTE: This logic is based on NpmLinkManager._createSymlink()
        if (linkInfo.kind === 'folderLink') {
            terminal.writeVerboseLine(`Creating linked folder at path "${newLinkPath}"`);
            await FileSystem.createSymbolicLinkJunctionAsync({ newLinkPath, linkTargetPath });
        }
        else if (linkInfo.kind === 'fileLink') {
            // Use hardlinks for Windows and symlinks for other platforms since creating a symbolic link
            // requires administrator permission on Windows. This may cause unexpected behaviour for consumers
            // of the hardlinked files. If this becomes an issue, we may need to revisit this.
            terminal.writeVerboseLine(`Creating linked file at path "${newLinkPath}"`);
            if (process.platform === 'win32') {
                await FileSystem.createHardLinkAsync({ newLinkPath, linkTargetPath });
            }
            else {
                await FileSystem.createSymbolicLinkFileAsync({ newLinkPath, linkTargetPath });
            }
        }
    }, { concurrency: MAX_CONCURRENCY });
}
async function realizeFilesAsync(terminal, targetRootFolder, extractorMetadataObject) {
    await Async.forEachAsync(extractorMetadataObject.files, async (relativeFilePath) => {
        const filePath = `${targetRootFolder}/${relativeFilePath}`;
        const realFilePath = await FileSystem.getRealPathAsync(filePath);
        if (!Path.isEqual(realFilePath, filePath)) {
            // Delete the existing symlink and create a hardlink to the real file, since creating hardlinks
            // is less overhead than copying the file.
            terminal.writeVerboseLine(`Realizing file at path "${filePath}"`);
            await FileSystem.deleteFileAsync(filePath);
            await FileSystem.createHardLinkAsync({ newLinkPath: filePath, linkTargetPath: realFilePath });
        }
    }, { concurrency: MAX_CONCURRENCY });
}
export class CreateLinksAction extends CommandLineAction {
    constructor(terminal) {
        super({
            actionName: 'create',
            summary: 'Create symlinks for extraction',
            documentation: 'This action creates symlinks for the extraction process.'
        });
        this._terminal = terminal;
        this._realizeFilesParameter = this.defineFlagParameter({
            parameterLongName: REALIZE_FILES_PARAMETER_NAME,
            description: 'Realize files instead of creating symlinks'
        });
        this._linkBinsParameter = this.defineFlagParameter({
            parameterLongName: LINK_BINS_PARAMETER_NAME,
            description: 'Create the .bin files for extracted packages'
        });
    }
    async onExecuteAsync() {
        const extractorMetadataObject = await getExtractorMetadataAsync();
        const realizeFiles = this._realizeFilesParameter.value;
        const linkBins = this._linkBinsParameter.value;
        this._terminal.writeLine(`Creating links for extraction at path "${TARGET_ROOT_FOLDER}"`);
        await removeLinksAsync(this._terminal, TARGET_ROOT_FOLDER, extractorMetadataObject);
        await createLinksAsync(this._terminal, TARGET_ROOT_FOLDER, extractorMetadataObject);
        if (realizeFiles) {
            this._terminal.writeLine(`Realizing files for extraction at path "${TARGET_ROOT_FOLDER}"`);
            await realizeFilesAsync(this._terminal, TARGET_ROOT_FOLDER, extractorMetadataObject);
        }
        if (linkBins) {
            this._terminal.writeLine(`Linking bins for extraction at path "${TARGET_ROOT_FOLDER}"`);
            const extractedProjectFolderPaths = extractorMetadataObject.projects.map((project) => path.join(TARGET_ROOT_FOLDER, project.path));
            await makeBinLinksAsync(this._terminal, extractedProjectFolderPaths);
        }
    }
}
//# sourceMappingURL=CreateLinksAction.js.map