"use strict";
/**
 * Rename Package Generator
 *
 * This generator renames a Unisphere package by:
 * 1. Using Nx's built-in move generator to handle:
 *    - Directory and file moves
 *    - Import statement updates across the workspace
 *    - TypeScript path mappings in tsconfig.base.json
 * 2. Performing Unisphere-specific cleanup:
 *    - Updating .unisphere configuration
 *    - Ensuring package.json name matches Unisphere conventions
 *    - Updating package-lock.json path keys (Nx doesn't handle this)
 *    - Updating vite.config.ts cache directory paths
 *    - Updating project.json lintFilePatterns and comments (Nx updates root/sourceRoot but not options strings)
 *    - Updating README.md with new package name
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.renamePackageGenerator = renamePackageGenerator;
const devkit_1 = require("@nx/devkit");
require('chalk').__esModule = true;
const generators_1 = require("@nx/workspace/generators");
const utils_1 = require("../utils");
function validateOptions(options) {
    if (!options.oldPackageName || options.oldPackageName.trim() === '') {
        throw new Error(`Missing required option: 'oldPackageName'\n` +
            `  Description: The current name of the package to rename\n` +
            `  Type: string\n` +
            `  Example: nx g @unisphere/nx:rename-package --oldPackageName=old-name --newPackageName=new-name`);
    }
    if (!options.newPackageName || options.newPackageName.trim() === '') {
        throw new Error(`Missing required option: 'newPackageName'\n` +
            `  Description: The new name for the package\n` +
            `  Type: string\n` +
            `  Pattern: ^[a-zA-Z][a-zA-Z0-9\\-\\s]*$\n` +
            `  Example: nx g @unisphere/nx:rename-package --oldPackageName=old-name --newPackageName=new-name`);
    }
    const namePattern = /^[a-zA-Z][a-zA-Z0-9\-\s]*$/;
    if (!namePattern.test(options.newPackageName)) {
        throw new Error(`Invalid value '${options.newPackageName}' for option 'newPackageName'\n` +
            `  Pattern: ^[a-zA-Z][a-zA-Z0-9\\-\\s]*$\n` +
            `  Received: ${options.newPackageName}`);
    }
}
/**
 * Reserved package names that cannot be renamed.
 * These packages have special meaning in Unisphere and renaming them would break functionality.
 */
const RESERVED_PACKAGE_NAMES = ['types'];
/**
 * Validates that the package is not a reserved package that cannot be renamed
 */
function validateNotReservedPackage(packageName) {
    const normalizedName = (0, devkit_1.names)(packageName).fileName;
    if (RESERVED_PACKAGE_NAMES.includes(normalizedName)) {
        throw new Error(`Cannot rename the "${normalizedName}" package.\n\n` +
            `The "${normalizedName}" package is a reserved Unisphere package with special functionality.\n` +
            'Learn more: https://unisphere.kaltura.com/docs/create/packages/overview#the-types-package');
    }
}
/**
 * Validates that the old package exists in .unisphere configuration
 */
function validateOldPackageExists(tree, oldPackageName) {
    const unisphereConfig = (0, devkit_1.readJson)(tree, '.unisphere');
    const normalizedOldName = (0, devkit_1.names)(oldPackageName).fileName;
    if (!unisphereConfig.elements?.packages?.[normalizedOldName]) {
        throw new Error(`Package "${normalizedOldName}" not found in .unisphere configuration.\n` +
            'Available packages: ' +
            Object.keys(unisphereConfig.elements?.packages || {}).join(', '));
    }
    const packageConfig = unisphereConfig.elements.packages[normalizedOldName];
    // Use the actual sourceRoot from .unisphere (supports both old and new directory structures)
    const sourceRoot = packageConfig.sourceRoot;
    if (!sourceRoot) {
        throw new Error(`Package "${normalizedOldName}" exists but has no sourceRoot configured in .unisphere`);
    }
    const oldPath = sourceRoot;
    const subdirectory = (0, utils_1.extractSubdirectoryFromSourceRoot)(sourceRoot);
    // Verify the directory exists
    if (!tree.exists(oldPath)) {
        throw new Error(`Package directory not found at ${oldPath}.\n` +
            'The .unisphere configuration references a package that does not exist.');
    }
    // Read the package.json to get the current package name
    const packageJsonPath = `${oldPath}/package.json`;
    if (!tree.exists(packageJsonPath)) {
        throw new Error(`package.json not found at ${packageJsonPath}.\n` +
            'The package directory exists but is missing package.json.');
    }
    const packageJson = (0, devkit_1.readJson)(tree, packageJsonPath);
    const packageJsonName = packageJson.name;
    return {
        packageJsonName,
        scope: packageConfig.scope || 'widget',
        sourceRoot,
        subdirectory,
        oldPath,
    };
}
/**
 * Validates that the new package name doesn't already exist
 * Checks in the same subdirectory as the old package (rename keeps the package in the same location)
 */
function validateNewPackageDoesNotExist(tree, newPackageName, subdirectory) {
    const unisphereConfig = (0, devkit_1.readJson)(tree, '.unisphere');
    const normalizedNewName = (0, devkit_1.names)(newPackageName).fileName;
    if (unisphereConfig.elements?.packages?.[normalizedNewName]) {
        throw new Error(`Package "${normalizedNewName}" already exists in .unisphere configuration.\n` +
            'Please choose a different package name.');
    }
    // Build the new path using the same subdirectory as the old package
    const newPath = subdirectory
        ? `unisphere/packages/${subdirectory}/${normalizedNewName}`
        : `unisphere/packages/${normalizedNewName}`;
    if (tree.exists(newPath)) {
        throw new Error(`Package directory already exists at ${newPath}.\n` +
            'Please choose a different package name or remove the existing directory.');
    }
}
/**
 * Calculate the new package.json name based on scope and experience name
 */
function calculateNewPackageJsonName(tree, newPackageName, oldPackageJsonName) {
    const unisphereConfig = (0, utils_1.validateUnisphereConfig)(tree);
    const experienceName = unisphereConfig.name;
    const normalizedNewName = (0, devkit_1.names)(newPackageName).fileName;
    // Determine the prefix from the old package name
    // Order matters - check more specific prefixes first
    let prefix;
    if (oldPackageJsonName.startsWith('@unisphere-corp/')) {
        prefix = '@unisphere-corp/';
    }
    else if (oldPackageJsonName.startsWith('@local/')) {
        prefix = '@local/';
    }
    else if (oldPackageJsonName.startsWith('@unisphere/')) {
        prefix = '@unisphere/';
    }
    else if (oldPackageJsonName.startsWith('@kaltura/unisphere-')) {
        prefix = '@kaltura/unisphere-';
    }
    else if (oldPackageJsonName.startsWith('@kaltura-corp/unisphere-')) {
        prefix = '@kaltura-corp/unisphere-';
    }
    else if (oldPackageJsonName.startsWith('@kaltura-ui/')) {
        prefix = '@kaltura-ui/';
    }
    else if (oldPackageJsonName.startsWith('@kaltura-apps/')) {
        prefix = '@kaltura-apps/';
    }
    else if (oldPackageJsonName.startsWith('@kaltura-sdk/')) {
        prefix = '@kaltura-sdk/';
    }
    else {
        // Truly private package with no standard prefix
        return normalizedNewName;
    }
    // Calculate new package name
    let packageJsonName;
    if (experienceName === normalizedNewName) {
        packageJsonName = normalizedNewName;
    }
    else {
        packageJsonName = `${experienceName}-${normalizedNewName}`;
    }
    return prefix + packageJsonName;
}
/**
 * Update .unisphere configuration
 * Preserves the subdirectory structure (package stays in same subdirectory after rename)
 */
function updateUnisphereConfiguration(tree, oldPackageName, newPackageName, packageInfo) {
    const unisphereConfig = (0, devkit_1.readJson)(tree, '.unisphere');
    const normalizedNewName = (0, devkit_1.names)(newPackageName).fileName;
    const normalizedOldName = (0, devkit_1.names)(oldPackageName).fileName;
    // Get the old package config
    const oldPackageConfig = unisphereConfig.elements.packages[normalizedOldName];
    // Build new sourceRoot preserving the subdirectory structure
    const newSourceRoot = packageInfo.subdirectory
        ? `unisphere/packages/${packageInfo.subdirectory}/${normalizedNewName}`
        : `unisphere/packages/${normalizedNewName}`;
    // Preserve package order by rebuilding the packages object
    const newPackages = {};
    Object.keys(unisphereConfig.elements.packages).forEach((key) => {
        if (key === normalizedOldName) {
            // Replace old package with new one at the same position
            newPackages[normalizedNewName] = {
                ...oldPackageConfig,
                sourceRoot: newSourceRoot,
            };
        }
        else {
            newPackages[key] = unisphereConfig.elements.packages[key];
        }
    });
    unisphereConfig.elements.packages = newPackages;
    (0, devkit_1.writeJson)(tree, '.unisphere', unisphereConfig);
    devkit_1.logger.info(`✅ Updated .unisphere configuration`);
}
/**
 * Update package.json name to ensure it matches Unisphere naming conventions
 * Nx move generator sets the package name, but we verify it matches our conventions
 */
function updatePackageJson(tree, packagePath, newPackageJsonName) {
    const packageJsonPath = `${packagePath}/package.json`;
    if (!tree.exists(packageJsonPath)) {
        devkit_1.logger.warn(`⚠️  package.json not found at ${packageJsonPath}`);
        return;
    }
    const packageJson = (0, devkit_1.readJson)(tree, packageJsonPath);
    // Only update if Nx didn't set it to our expected name
    if (packageJson.name !== newPackageJsonName) {
        packageJson.name = newPackageJsonName;
        (0, devkit_1.writeJson)(tree, packageJsonPath, packageJson);
        devkit_1.logger.info(`✅ Updated package.json name to "${newPackageJsonName}"`);
    }
}
/**
 * Update vite.config.ts to rename cache directory paths
 * Nx's move generator doesn't update arbitrary string literals in code files
 */
function updateViteConfig(tree, newPath, oldNormalizedName, newNormalizedName) {
    const viteConfigPath = `${newPath}/vite.config.ts`;
    if (!tree.exists(viteConfigPath)) {
        return;
    }
    let content = tree.read(viteConfigPath, 'utf-8');
    if (!content) {
        return;
    }
    const oldPattern = `package-${oldNormalizedName}`;
    const newPattern = `package-${newNormalizedName}`;
    if (content.includes(oldPattern)) {
        content = content.replace(new RegExp(oldPattern, 'g'), newPattern);
        tree.write(viteConfigPath, content);
        devkit_1.logger.info(`✅ Updated vite.config.ts cache directories`);
    }
}
/**
 * Update project.json to fix lintFilePatterns and comments
 * Nx's move generator updates root/sourceRoot but not arbitrary strings in options
 */
function updateProjectJson(tree, newPath, oldPath, oldNxProjectName, newNxProjectName) {
    const projectJsonPath = `${newPath}/project.json`;
    if (!tree.exists(projectJsonPath)) {
        return;
    }
    let content = tree.read(projectJsonPath, 'utf-8');
    if (!content) {
        return;
    }
    let updated = false;
    // Replace old path/name avoiding substring collisions (e.g. renaming "tempo" → "tempopo"
    // where oldPath is a prefix of newPath — a naive replace would match within already-updated
    // values and produce "tempopopo").
    if (content.includes(oldPath) && oldPath !== newPath) {
        const result = safeReplace(content, oldPath, newPath);
        if (result !== content) {
            content = result;
            updated = true;
        }
    }
    if (content.includes(oldNxProjectName) && oldNxProjectName !== newNxProjectName) {
        const result = safeReplace(content, oldNxProjectName, newNxProjectName);
        if (result !== content) {
            content = result;
            updated = true;
        }
    }
    if (updated) {
        tree.write(projectJsonPath, content);
        devkit_1.logger.info(`✅ Updated project.json paths and comments`);
    }
}
/**
 * Replace oldVal with newVal, handling the case where oldVal is a substring of newVal.
 * Shields already-correct newVal occurrences from being matched.
 */
function safeReplace(text, oldVal, newVal) {
    if (oldVal === newVal)
        return text;
    if (!newVal.includes(oldVal)) {
        return text.replace(new RegExp(escapeRegExp(oldVal), 'g'), newVal);
    }
    // oldVal is a substring of newVal — use placeholder to protect existing correct values
    const placeholder = `\x00__SAFE_REPLACE_${Date.now()}__\x00`;
    let result = text.replace(new RegExp(escapeRegExp(newVal), 'g'), placeholder);
    result = result.replace(new RegExp(escapeRegExp(oldVal), 'g'), newVal);
    result = result.replace(new RegExp(escapeRegExp(placeholder), 'g'), newVal);
    return result;
}
/**
 * Update README.md to reflect the new package name
 * Nx's move generator doesn't update documentation files
 */
function updateReadme(tree, newPath, oldNormalizedName, newNormalizedName) {
    const readmePath = `${newPath}/README.md`;
    if (!tree.exists(readmePath)) {
        return;
    }
    let content = tree.read(readmePath, 'utf-8');
    if (!content) {
        return;
    }
    let updated = false;
    // Convert to human readable format (capitalize first letter of each word)
    const toHumanReadable = (name) => name
        .replace(/[-_]/g, ' ')
        .replace(/\b\w/g, (l) => l.toUpperCase());
    const oldHumanReadable = toHumanReadable(oldNormalizedName);
    const newHumanReadable = toHumanReadable(newNormalizedName);
    // Update human readable name (e.g., "Package My Package" -> "Package New Name")
    if (content.includes(oldHumanReadable)) {
        content = content.replace(new RegExp(escapeRegExp(oldHumanReadable), 'g'), newHumanReadable);
        updated = true;
    }
    // Update kebab-case name references
    if (content.includes(oldNormalizedName)) {
        content = content.replace(new RegExp(escapeRegExp(oldNormalizedName), 'g'), newNormalizedName);
        updated = true;
    }
    if (updated) {
        tree.write(readmePath, content);
        devkit_1.logger.info(`✅ Updated README.md`);
    }
}
/**
 * Escape special regex characters in a string
 */
function escapeRegExp(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
 * Update package-lock.json to rename the package path keys
 * Nx's move generator doesn't handle package-lock.json updates
 * Uses actual paths from packageInfo to support subdirectories
 */
function updatePackageLock(tree, oldPath, newPath) {
    const packageLockPath = 'package-lock.json';
    if (!tree.exists(packageLockPath)) {
        return;
    }
    const packageLock = (0, devkit_1.readJson)(tree, packageLockPath);
    let updated = false;
    if (packageLock.packages) {
        const packagesEntries = Object.entries(packageLock.packages);
        const newPackages = {};
        for (const [key, value] of packagesEntries) {
            // Check if this key is exactly the old package path
            if (key === oldPath) {
                newPackages[newPath] = value;
                updated = true;
            }
            else {
                newPackages[key] = value;
            }
        }
        packageLock.packages = newPackages;
    }
    // Update resolved paths if they exist
    if (packageLock.packages) {
        for (const [, value] of Object.entries(packageLock.packages)) {
            if (value && typeof value === 'object' && 'resolved' in value) {
                const pkg = value;
                if (pkg.resolved === oldPath) {
                    pkg.resolved = newPath;
                    updated = true;
                }
            }
        }
    }
    if (updated) {
        (0, devkit_1.writeJson)(tree, packageLockPath, packageLock);
        devkit_1.logger.info(`✅ Updated package-lock.json path keys`);
    }
}
async function renamePackageGenerator(tree, options) {
    // Validate options
    validateOptions(options);
    devkit_1.logger.info('');
    devkit_1.logger.info('🔄 Starting package rename...');
    devkit_1.logger.info('');
    // Validate .unisphere exists
    (0, utils_1.validateUnisphereConfig)(tree);
    // Normalize package names
    const normalizedOldName = (0, devkit_1.names)(options.oldPackageName).fileName;
    const normalizedNewName = (0, devkit_1.names)(options.newPackageName).fileName;
    // Validate the package is not reserved
    validateNotReservedPackage(normalizedOldName);
    // Validate old package exists
    const packageInfo = validateOldPackageExists(tree, normalizedOldName);
    // Validate new package doesn't exist (checks in same subdirectory)
    validateNewPackageDoesNotExist(tree, normalizedNewName, packageInfo.subdirectory);
    // Calculate new package.json name
    const newPackageJsonName = calculateNewPackageJsonName(tree, normalizedNewName, packageInfo.packageJsonName);
    const oldNxProjectName = `unisphere-package-${normalizedOldName}`;
    const newNxProjectName = `unisphere-package-${normalizedNewName}`;
    // Build new path preserving subdirectory structure
    const newPath = packageInfo.subdirectory
        ? `unisphere/packages/${packageInfo.subdirectory}/${normalizedNewName}`
        : `unisphere/packages/${normalizedNewName}`;
    devkit_1.logger.info(`📦 Old package name: ${normalizedOldName}`);
    devkit_1.logger.info(`📦 New package name: ${normalizedNewName}`);
    devkit_1.logger.info(`🏷️  Old package.json name: ${packageInfo.packageJsonName}`);
    devkit_1.logger.info(`🏷️  New package.json name: ${newPackageJsonName}`);
    devkit_1.logger.info(`🏷️  Nx project name: ${oldNxProjectName} → ${newNxProjectName}`);
    devkit_1.logger.info('');
    // Step 1: Run Nx's built-in move generator
    devkit_1.logger.info('🔧 Running Nx move generator...');
    try {
        await (0, generators_1.moveGenerator)(tree, {
            projectName: oldNxProjectName,
            destination: newPath,
            newProjectName: newNxProjectName,
            importPath: newPackageJsonName,
            updateImportPath: true,
            skipFormat: false,
        });
        devkit_1.logger.info(`✅ Nx moved project from ${packageInfo.oldPath} to ${newPath}`);
    }
    catch (error) {
        devkit_1.logger.error(`❌ Failed to move project with Nx: ${error}`);
        throw error;
    }
    devkit_1.logger.info('');
    devkit_1.logger.info('🧹 Performing Unisphere-specific cleanup...');
    // Step 2: Update .unisphere configuration (Unisphere-specific)
    updateUnisphereConfiguration(tree, normalizedOldName, normalizedNewName, packageInfo);
    // Step 3: Update package.json name to match Unisphere conventions
    updatePackageJson(tree, newPath, newPackageJsonName);
    // Step 4: Update package-lock.json path keys (Nx doesn't handle this)
    updatePackageLock(tree, packageInfo.oldPath, newPath);
    // Step 5: Update vite.config.ts cache directories (Nx doesn't handle this)
    updateViteConfig(tree, newPath, normalizedOldName, normalizedNewName);
    // Step 6: Update project.json lintFilePatterns and comments (Nx doesn't handle these)
    updateProjectJson(tree, newPath, packageInfo.oldPath, oldNxProjectName, newNxProjectName);
    // Step 7: Update README.md (Nx doesn't handle documentation)
    updateReadme(tree, newPath, normalizedOldName, normalizedNewName);
    devkit_1.logger.info('');
    devkit_1.logger.info('✅ Package renamed successfully!');
    devkit_1.logger.info('');
    devkit_1.logger.info('📋 Summary:');
    devkit_1.logger.info(`  • Old name: ${normalizedOldName}`);
    devkit_1.logger.info(`  • New name: ${normalizedNewName}`);
    devkit_1.logger.info(`  • Old location: ${packageInfo.sourceRoot}`);
    devkit_1.logger.info(`  • New location: ${newPath}`);
    devkit_1.logger.info(`  • Package.json name: ${newPackageJsonName}`);
    devkit_1.logger.info('');
    devkit_1.logger.info('📝 Next steps:');
    devkit_1.logger.info('  1. Review the changes: git status');
    devkit_1.logger.info('  2. Stage all changes: git add -A');
    devkit_1.logger.info('     Git will detect the directory move as a rename (preserving history)');
    devkit_1.logger.info('  3. Run: npm install (to update package-lock.json)');
    devkit_1.logger.info('  4. Run: npm run build (to verify everything builds)');
    devkit_1.logger.info('  5. Commit the changes: git commit -m "Rename package..."');
    devkit_1.logger.info('');
}
exports.default = renamePackageGenerator;
