"use strict";
/**
 * Remove Generator
 *
 * This generator removes a Unisphere element (package, runtime, or application) by:
 * 1. Validating the element exists in .unisphere configuration
 * 2. Using Nx's built-in remove generator to delete the project
 * 3. Performing Unisphere-specific cleanup:
 *    - Removing from .unisphere configuration
 *    - For runtimes: removing types from types/core package and updating index.ts exports
 *    - Updating tsconfig.base.json path mappings
 *    - Updating package-lock.json
 *
 * Usage: nx g @unisphere/nx:remove runtime:admin
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeGenerator = removeGenerator;
const devkit_1 = require("@nx/devkit");
require('chalk').__esModule = true;
const generators_1 = require("@nx/workspace/generators");
const utils_1 = require("../utils");
const remove_utils_1 = require("./remove-utils");
const VALID_ELEMENT_TYPES = ['package', 'runtime', 'application'];
function validateOptions(options) {
    if (!options.element || options.element.trim() === '') {
        throw new Error(`Missing required option: 'element'\n` +
            `  Description: The element to remove in format 'type:name'\n` +
            `  Type: string\n` +
            `  Format: type:name (e.g., runtime:admin, package:shared, application:my-app)\n` +
            `  Example: nx g @unisphere/nx:remove runtime:admin`);
    }
}
/**
 * Parse the element string in format "type:name"
 */
function parseElement(element) {
    const parts = element.split(':');
    if (parts.length !== 2) {
        throw new Error(`Invalid element format: "${element}"\n` +
            `Expected format: type:name (e.g., runtime:admin, package:shared, application:my-app)`);
    }
    const [type, name] = parts;
    if (!VALID_ELEMENT_TYPES.includes(type)) {
        throw new Error(`Invalid element type: "${type}"\n` +
            `Valid types: ${VALID_ELEMENT_TYPES.join(', ')}`);
    }
    if (!name || name.trim() === '') {
        throw new Error(`Element name is required.\n` +
            `Expected format: type:name (e.g., runtime:admin)`);
    }
    return {
        elementType: type,
        name: name.trim(),
    };
}
/**
 * Get the configuration key for element type in .unisphere
 */
function getElementsConfigKey(elementType) {
    const mapping = {
        package: 'packages',
        runtime: 'runtimes',
        application: 'applications',
    };
    return mapping[elementType];
}
/**
 * Get the Nx project name prefix for element type
 */
function getNxProjectPrefix(elementType) {
    const mapping = {
        package: 'unisphere-package',
        runtime: 'unisphere-runtime',
        application: 'unisphere-application',
    };
    return mapping[elementType];
}
/**
 * Validates that the element exists in .unisphere configuration
 */
function validateElementExists(tree, elementType, elementName) {
    const unisphereConfig = (0, devkit_1.readJson)(tree, '.unisphere');
    const normalizedName = (0, devkit_1.names)(elementName).fileName;
    const configKey = getElementsConfigKey(elementType);
    const elementConfig = unisphereConfig.elements?.[configKey]?.[normalizedName];
    if (!elementConfig) {
        const availableElements = Object.keys(unisphereConfig.elements?.[configKey] || {});
        throw new Error(`${elementType.charAt(0).toUpperCase() + elementType.slice(1)} "${normalizedName}" not found in .unisphere configuration.\n` +
            `Available ${configKey}: ${availableElements.length > 0 ? availableElements.join(', ') : '(none)'}`);
    }
    if (!elementConfig.sourceRoot) {
        throw new Error(`${elementType.charAt(0).toUpperCase() + elementType.slice(1)} "${normalizedName}" exists but has no sourceRoot configured in .unisphere`);
    }
    return elementConfig.sourceRoot;
}
/**
 * Remove element from .unisphere configuration
 */
function removeFromUnisphereConfig(tree, elementType, elementName) {
    const unisphereConfig = (0, devkit_1.readJson)(tree, '.unisphere');
    const configKey = getElementsConfigKey(elementType);
    const normalizedName = (0, devkit_1.names)(elementName).fileName;
    if (unisphereConfig.elements?.[configKey]?.[normalizedName]) {
        delete unisphereConfig.elements[configKey][normalizedName];
        (0, devkit_1.writeJson)(tree, '.unisphere', unisphereConfig);
        devkit_1.logger.info(`Removed "${normalizedName}" from .unisphere configuration`);
    }
}
/**
 * Remove runtime types from types/core package
 */
function removeRuntimeTypes(tree, runtimeName) {
    const typesPackageInfo = (0, utils_1.findTypesOrCorePackageInfo)(tree);
    const normalizedName = (0, devkit_1.names)(runtimeName).fileName;
    // Remove the runtime types directory
    const runtimeTypesDir = `${typesPackageInfo.basePath}/src/lib/${normalizedName}-runtime`;
    if (tree.exists(runtimeTypesDir)) {
        // Delete all files in the directory
        tree.children(runtimeTypesDir).forEach(child => {
            tree.delete(`${runtimeTypesDir}/${child}`);
        });
        devkit_1.logger.info(`Removed runtime types directory: ${runtimeTypesDir}`);
    }
    // Update the index.ts to remove the export
    const indexPath = `${typesPackageInfo.basePath}/src/index.ts`;
    if (tree.exists(indexPath)) {
        const content = tree.read(indexPath, 'utf-8');
        if (content) {
            const exportLine = `export * from './lib/${normalizedName}-runtime';`;
            const exportLineWithNewline = `\n${exportLine}`;
            let updatedContent = content;
            // Remove the export line (handle both with and without leading newline)
            if (content.includes(exportLineWithNewline)) {
                updatedContent = content.replace(exportLineWithNewline, '');
            }
            else if (content.includes(exportLine)) {
                updatedContent = content.replace(exportLine + '\n', '');
                updatedContent = updatedContent.replace(exportLine, '');
            }
            if (updatedContent !== content) {
                tree.write(indexPath, updatedContent);
                devkit_1.logger.info(`Removed runtime export from ${indexPath}`);
            }
        }
    }
}
/**
 * Remove path mapping from tsconfig.base.json
 */
function removeTsConfigPath(tree, elementType, elementName) {
    const tsconfigPath = 'tsconfig.base.json';
    if (!tree.exists(tsconfigPath)) {
        return;
    }
    const tsconfig = (0, devkit_1.readJson)(tree, tsconfigPath);
    const normalizedName = (0, devkit_1.names)(elementName).fileName;
    if (!tsconfig.compilerOptions?.paths) {
        return;
    }
    // Determine the path key based on element type
    let pathKeyPattern;
    switch (elementType) {
        case 'runtime':
            pathKeyPattern = `unisphere-runtime-${normalizedName}`;
            break;
        case 'package':
            // Packages may have various path patterns, we'll search for matching paths
            pathKeyPattern = normalizedName;
            break;
        case 'application':
            pathKeyPattern = `unisphere-application-${normalizedName}`;
            break;
    }
    let removed = false;
    const pathsToRemove = [];
    // Find all matching paths
    for (const [key, paths] of Object.entries(tsconfig.compilerOptions.paths)) {
        if (Array.isArray(paths)) {
            const matchesPath = paths.some((p) => p.includes(`/${normalizedName}/`) || p.includes(`/${normalizedName}-`));
            if (key.includes(pathKeyPattern) || matchesPath) {
                pathsToRemove.push(key);
            }
        }
    }
    // Remove the paths
    for (const key of pathsToRemove) {
        delete tsconfig.compilerOptions.paths[key];
        removed = true;
    }
    if (removed) {
        (0, devkit_1.writeJson)(tree, tsconfigPath, tsconfig);
        devkit_1.logger.info(`Removed path mapping(s) from tsconfig.base.json`);
    }
}
/**
 * Remove entry from package-lock.json
 */
function removeFromPackageLock(tree, sourceRoot) {
    const packageLockPath = 'package-lock.json';
    if (!tree.exists(packageLockPath)) {
        return;
    }
    const packageLock = (0, devkit_1.readJson)(tree, packageLockPath);
    let updated = false;
    if (packageLock.packages) {
        // Remove the source entry
        if (packageLock.packages[sourceRoot]) {
            delete packageLock.packages[sourceRoot];
            updated = true;
        }
        // Remove the node_modules symlink entry that points to this sourceRoot
        for (const key of Object.keys(packageLock.packages)) {
            if (key.startsWith('node_modules/') &&
                packageLock.packages[key].resolved === sourceRoot &&
                packageLock.packages[key].link === true) {
                delete packageLock.packages[key];
                updated = true;
            }
        }
    }
    if (updated) {
        (0, devkit_1.writeJson)(tree, packageLockPath, packageLock);
        devkit_1.logger.info(`Removed entry from package-lock.json`);
    }
}
async function removeGenerator(tree, options) {
    // Validate options
    validateOptions(options);
    // Validate .unisphere exists
    (0, utils_1.validateUnisphereConfig)(tree);
    // Parse element string (format: "type:name")
    const { elementType, name } = parseElement(options.element);
    const normalizedName = (0, devkit_1.names)(name).fileName;
    // Validate element exists and get sourceRoot
    const sourceRoot = validateElementExists(tree, elementType, normalizedName);
    // Step 0: For packages, check for dependents before removing
    if (elementType === 'package' && !options.forceRemove) {
        const { packageJsonDependents, importDependents } = (0, remove_utils_1.checkPackageDependents)(tree, normalizedName);
        if (packageJsonDependents.length > 0 || importDependents.length > 0) {
            throw new Error((0, remove_utils_1.formatDependentsError)(normalizedName, packageJsonDependents, importDependents));
        }
    }
    // Verify the directory exists
    if (!tree.exists(sourceRoot)) {
        devkit_1.logger.warn(`Directory not found at ${sourceRoot}. The .unisphere configuration may be out of sync.`);
    }
    const nxProjectName = `${getNxProjectPrefix(elementType)}-${normalizedName}`;
    devkit_1.logger.info('');
    devkit_1.logger.info(`Removing ${elementType}: ${normalizedName}`);
    devkit_1.logger.info(`  Location: ${sourceRoot}`);
    devkit_1.logger.info('');
    // Step 1: For runtimes, remove types first (before directory is deleted)
    if (elementType === 'runtime') {
        removeRuntimeTypes(tree, normalizedName);
    }
    // Step 2: Run Nx's built-in remove generator
    try {
        await (0, generators_1.removeGenerator)(tree, {
            projectName: nxProjectName,
            forceRemove: options.forceRemove || false,
            skipFormat: false,
        });
        devkit_1.logger.info(`Removed Nx project "${nxProjectName}"`);
    }
    catch (error) {
        // If Nx remove fails (e.g., project not in project.json), continue with cleanup
        devkit_1.logger.warn(`Nx remove failed: ${error}`);
        devkit_1.logger.info('Continuing with Unisphere cleanup...');
        // Manually delete the directory if it exists
        if (tree.exists(sourceRoot)) {
            // Delete all files recursively
            const deleteRecursively = (dirPath) => {
                if (tree.isFile(dirPath)) {
                    tree.delete(dirPath);
                }
                else {
                    tree.children(dirPath).forEach(child => {
                        deleteRecursively(`${dirPath}/${child}`);
                    });
                }
            };
            deleteRecursively(sourceRoot);
            devkit_1.logger.info(`Manually deleted ${sourceRoot}`);
        }
    }
    // Step 3: Remove from .unisphere configuration
    removeFromUnisphereConfig(tree, elementType, normalizedName);
    // Step 4: Remove path mapping from tsconfig.base.json
    removeTsConfigPath(tree, elementType, normalizedName);
    // Step 5: Remove entry from package-lock.json
    removeFromPackageLock(tree, sourceRoot);
    devkit_1.logger.info('');
    devkit_1.logger.info(`${elementType.charAt(0).toUpperCase() + elementType.slice(1)} "${normalizedName}" removed successfully!`);
    devkit_1.logger.info('');
    devkit_1.logger.info('Next steps:');
    devkit_1.logger.info('  1. Run: npm install');
    devkit_1.logger.info('  2. Run: npm run build');
    devkit_1.logger.info('');
}
exports.default = removeGenerator;
