"use strict";
/**
 * Migration: Reorganize Packages by Distribution Channel
 *
 * This migration reorganizes packages based on their `distributionChannel` property in `.unisphere`:
 *
 * Directory structure:
 * - none -> packages/local/
 * - github -> packages/kaltura-corp/
 * - npm -> packages/unisphere/
 * - kaltura-ai -> packages/kaltura-ai/
 *
 * Package scope changes:
 * - none -> @local/{pkg}
 * - github -> @kaltura-corp/unisphere-{pkg} (replaces @kaltura/unisphere-{pkg})
 * - npm -> @unisphere/{pkg} (unchanged)
 * - kaltura-ai -> @kaltura-ai/{pkg}
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = update;
const devkit_1 = require("@nx/devkit");
// Workaround: chalk + tslib >=2.8 incompatibility causes @nx/workspace barrel to crash
require('chalk').__esModule = true;
const generators_1 = require("@nx/workspace/generators");
const fs_1 = require("fs");
const path_1 = require("path");
const has_react_dependency_1 = require("../utils/has-react-dependency");
/**
 * Check if a package name needs the "-react" suffix and return the new name.
 * Following the convention in validateReactNaming() from dependency-config.ts.
 */
function applyReactSuffixIfNeeded(name, hasReact) {
    if (hasReact && !name.endsWith('-react')) {
        return `${name}-react`;
    }
    return name;
}
/**
 * Infer distribution channel from the current sourceRoot path.
 * This is needed when distributionChannel is not explicitly set in .unisphere config,
 * but the package is already in a subdirectory from a previous migration or manual move.
 */
function inferDistributionChannelFromPath(sourceRoot) {
    if (sourceRoot.includes('/local/') || sourceRoot.includes('/packages/local/')) {
        return 'none';
    }
    if (sourceRoot.includes('/kaltura-corp/') || sourceRoot.includes('/packages/kaltura-corp/')) {
        return 'github';
    }
    if (sourceRoot.includes('/unisphere/') || sourceRoot.includes('/packages/unisphere/')) {
        return 'npm';
    }
    if (sourceRoot.includes('/kaltura-ai/') || sourceRoot.includes('/packages/kaltura-ai/')) {
        return 'kaltura-ai';
    }
    return null;
}
/**
 * Get the subdirectory based on distribution channel
 */
function getSubdirectory(distributionChannel) {
    switch (distributionChannel) {
        case 'none':
            return 'local';
        case 'github':
            return 'kaltura-corp';
        case 'npm':
            return 'unisphere';
        case 'kaltura-ai':
            return 'kaltura-ai';
        default:
            return 'local';
    }
}
/**
 * Calculate the new package.json name based on distribution channel
 */
function calculateNewPackageJsonName(currentPackageJsonName, distributionChannel) {
    // Extract the base package name (after the scope and any prefixes)
    let baseName;
    if (currentPackageJsonName.startsWith('@kaltura/unisphere-')) {
        baseName = currentPackageJsonName.replace('@kaltura/unisphere-', '');
    }
    else if (currentPackageJsonName.startsWith('@unisphere/')) {
        baseName = currentPackageJsonName.replace('@unisphere/', '');
    }
    else if (currentPackageJsonName.startsWith('@kaltura-corp/unisphere-')) {
        baseName = currentPackageJsonName.replace('@kaltura-corp/unisphere-', '');
    }
    else if (currentPackageJsonName.startsWith('@unisphere-corp/')) {
        baseName = currentPackageJsonName.replace('@unisphere-corp/', '');
    }
    else if (currentPackageJsonName.startsWith('@local/')) {
        baseName = currentPackageJsonName.replace('@local/', '');
    }
    else if (currentPackageJsonName.startsWith('@kaltura-ai/')) {
        baseName = currentPackageJsonName.replace('@kaltura-ai/', '');
    }
    else {
        // No scope, use as-is
        baseName = currentPackageJsonName;
    }
    switch (distributionChannel) {
        case 'npm':
            return `@unisphere/${baseName}`;
        case 'github':
            return `@kaltura-corp/unisphere-${baseName}`;
        case 'kaltura-ai':
            return `@kaltura-ai/${baseName}`;
        case 'none':
        default:
            return `@local/${baseName}`;
    }
}
/**
 * Get the subdirectory for core/types packages.
 * Special case: types packages should only go to 'unisphere' (public) or 'kaltura-corp' (internal).
 * - If distributionChannel is 'npm', it's a public product -> 'unisphere'
 * - Otherwise (none, github, kaltura-ai), assume internal -> 'kaltura-corp'
 */
function getSubdirectoryForTypes(distributionChannel) {
    if (distributionChannel === 'npm') {
        return 'unisphere';
    }
    return 'kaltura-corp';
}
/**
 * Check if a package has already been migrated to the new subdirectory structure.
 * Checks both regular name and name with "-react" suffix since we don't know
 * at this point if the package has React dependencies.
 */
function isAlreadyMigrated(tree, name, distributionChannel) {
    // Handle core -> types rename
    const targetName = name === 'core' ? 'types' : name;
    const isCoreOrTypes = name === 'core' || name === 'types';
    // Special case for core/types: only allow 'unisphere' or 'kaltura-corp'
    const subdirectory = isCoreOrTypes
        ? getSubdirectoryForTypes(distributionChannel)
        : getSubdirectory(distributionChannel);
    // Check regular path
    const newPath = `unisphere/packages/${subdirectory}/${targetName}/package.json`;
    if (tree.exists(newPath)) {
        return true;
    }
    // Also check with -react suffix (for packages with React dependencies)
    const newPathWithReact = `unisphere/packages/${subdirectory}/${targetName}-react/package.json`;
    return tree.exists(newPathWithReact);
}
/**
 * Get the actual Nx project name from project.json
 */
function getProjectNameFromProjectJson(tree, projectPath) {
    const projectJsonPath = `${projectPath}/project.json`;
    if (!tree.exists(projectJsonPath)) {
        return null;
    }
    try {
        const projectJson = (0, devkit_1.readJson)(tree, projectJsonPath);
        return projectJson.name || null;
    }
    catch {
        return null;
    }
}
/**
 * Discover all packages in the workspace
 */
async function discoverPackages(tree) {
    if (!tree.exists('.unisphere')) {
        devkit_1.logger.warn('No .unisphere file found, skipping migration');
        return [];
    }
    const unisphereConfig = (0, devkit_1.readJson)(tree, '.unisphere');
    const packages = [];
    const packagesConfig = unisphereConfig.elements?.packages || {};
    for (const [name, config] of Object.entries(packagesConfig)) {
        const normalizedName = (0, devkit_1.names)(name).fileName;
        // Get sourceRoot from .unisphere config, or fall back to default path
        const sourceRoot = config.sourceRoot;
        const currentPath = sourceRoot || `unisphere/packages/${normalizedName}`;
        // Determine distribution channel:
        // 1. First check if explicitly set in config
        // 2. If not, try to infer from current sourceRoot path (for already-migrated packages)
        // 3. Default to 'none' if neither works
        let distributionChannel;
        const configDistributionChannel = config.distributionChannel;
        if (configDistributionChannel) {
            distributionChannel = configDistributionChannel;
        }
        else {
            const inferredChannel = inferDistributionChannelFromPath(currentPath);
            if (inferredChannel) {
                distributionChannel = inferredChannel;
                devkit_1.logger.info(`  Inferred distributionChannel '${distributionChannel}' from path for ${name}`);
            }
            else {
                distributionChannel = 'none';
            }
        }
        // Calculate target path to check if package is already at the target location
        // Handle core -> types rename when calculating target path
        const targetName = normalizedName === 'core' ? 'types' : normalizedName;
        const isCoreOrTypes = normalizedName === 'core' || normalizedName === 'types';
        // Special case for core/types: only allow 'unisphere' or 'kaltura-corp'
        const subdirectory = isCoreOrTypes
            ? getSubdirectoryForTypes(distributionChannel)
            : getSubdirectory(distributionChannel);
        const targetPath = `unisphere/packages/${subdirectory}/${targetName}`;
        // Check if package is already at the target location (sourceRoot == targetPath)
        // Also check with -react suffix since we don't know React dependency status yet
        if (currentPath === targetPath || currentPath === `${targetPath}-react`) {
            devkit_1.logger.info(`  Package ${name} is already at target location (${currentPath}), skipping`);
            continue;
        }
        // Check if already migrated to new location (package.json exists at new location)
        if (isAlreadyMigrated(tree, normalizedName, distributionChannel)) {
            devkit_1.logger.info(`  Package ${name} already migrated to new location, skipping`);
            continue;
        }
        // Check if target path already exists with actual content (package.json)
        // If target exists but is empty (no package.json), clean it up and proceed
        // Also check with potential -react suffix
        let skipPackage = false;
        for (const pathToCheck of [targetPath, `${targetPath}-react`]) {
            if (tree.exists(pathToCheck)) {
                if (tree.exists(`${pathToCheck}/package.json`)) {
                    devkit_1.logger.info(`  Package ${name} target path already exists with package.json (${pathToCheck}), skipping`);
                    skipPackage = true;
                    break;
                }
                else {
                    // Empty directory - clean it up so migration can proceed
                    devkit_1.logger.info(`  Package ${name} target path exists but is empty, cleaning up: ${pathToCheck}`);
                    tree.delete(pathToCheck);
                }
            }
        }
        if (skipPackage) {
            continue;
        }
        const packageJsonPath = `${currentPath}/package.json`;
        if (!tree.exists(packageJsonPath)) {
            devkit_1.logger.warn(`Package ${name} not found at ${currentPath}, skipping`);
            continue;
        }
        // Read actual project name from project.json instead of assuming it
        const actualProjectName = getProjectNameFromProjectJson(tree, currentPath);
        if (!actualProjectName) {
            devkit_1.logger.warn(`Package ${name} has no project.json or missing name at ${currentPath}, skipping`);
            continue;
        }
        const packageJson = (0, devkit_1.readJson)(tree, packageJsonPath);
        // Check if package has React dependencies (including transitive through workspace packages)
        const hasReact = await (0, has_react_dependency_1.hasReactDependency)(actualProjectName);
        devkit_1.logger.info(`  Found package: ${normalizedName} (Nx project: ${actualProjectName}, channel: ${distributionChannel})${hasReact ? ' [React]' : ''}`);
        packages.push({
            name: normalizedName,
            distributionChannel: distributionChannel,
            currentPath,
            currentPackageJsonName: packageJson.name,
            currentNxProjectName: actualProjectName,
            hasReactDependency: hasReact,
        });
    }
    return packages;
}
/**
 * Calculate migration target for a package
 * Handles:
 * - Moving to subdirectory based on distribution channel
 * - Renaming "core" to "types"
 * - Adding "-react" suffix for packages with React dependencies (except core/types)
 */
function calculateMigrationTarget(pkg) {
    // Check if this is the core/types package (special case - no React suffix)
    const isCoreOrTypes = pkg.name === 'core' || pkg.name === 'types';
    // Special case for core/types: only allow 'unisphere' or 'kaltura-corp'
    const subdirectory = isCoreOrTypes
        ? getSubdirectoryForTypes(pkg.distributionChannel)
        : getSubdirectory(pkg.distributionChannel);
    // Step 1: Apply special case renames (core -> types)
    let newName = pkg.name === 'core' ? 'types' : pkg.name;
    // Step 2: Apply "-react" suffix if package has React dependencies
    // Skip for core/types packages - they don't get the React suffix
    // Following the convention in validateReactNaming() from dependency-config.ts
    if (!isCoreOrTypes) {
        newName = applyReactSuffixIfNeeded(newName, pkg.hasReactDependency);
    }
    const isRename = newName !== pkg.name;
    const newPath = `unisphere/packages/${subdirectory}/${newName}`;
    // Calculate new package.json name
    // For core/types, use the effective distribution channel that matches the subdirectory
    // (since we override the subdirectory for core/types, we need to match the package name)
    const effectiveDistributionChannel = isCoreOrTypes
        ? (pkg.distributionChannel === 'npm' ? 'npm' : 'github') // 'github' maps to @kaltura-corp/unisphere-
        : pkg.distributionChannel;
    let newPackageJsonName = calculateNewPackageJsonName(pkg.currentPackageJsonName, effectiveDistributionChannel);
    // Handle core->types rename in package.json name
    if (pkg.name === 'core') {
        newPackageJsonName = newPackageJsonName
            .replace(/-core$/, '-types')
            .replace(/\/core$/, '/types');
    }
    // Handle React suffix in package.json name (skip for core/types)
    if (!isCoreOrTypes && pkg.hasReactDependency && !newPackageJsonName.endsWith('-react')) {
        newPackageJsonName = `${newPackageJsonName}-react`;
    }
    // Update Nx project name if renamed
    const newNxProjectName = isRename
        ? `unisphere-package-${newName}`
        : pkg.currentNxProjectName;
    return {
        package: pkg,
        newPath,
        newPackageJsonName,
        newNxProjectName,
        newName,
        subdirectory,
    };
}
/**
 * Validate migration preconditions
 */
function validateMigrationPreconditions(tree, targets) {
    // Check destination directories don't conflict
    const destinations = new Set();
    for (const target of targets) {
        if (destinations.has(target.newPath)) {
            throw new Error(`Duplicate destination path: ${target.newPath}`);
        }
        destinations.add(target.newPath);
        // Check destination doesn't already exist
        if (tree.exists(target.newPath)) {
            throw new Error(`Destination already exists: ${target.newPath}`);
        }
    }
    // Check all source packages exist
    for (const target of targets) {
        if (!tree.exists(target.package.currentPath)) {
            throw new Error(`Source package not found: ${target.package.currentPath}`);
        }
    }
}
/**
 * Update .unisphere configuration sourceRoot
 * Also handles renaming the package key if oldName !== newName (e.g., core -> types, admin -> admin-react)
 * Note: distributionChannel removal is handled by the separate upgrade-schema-to-2-0-0 migration
 */
function updateUnisphereSourceRoot(tree, oldName, newName, newPath) {
    const unisphereConfig = (0, devkit_1.readJson)(tree, '.unisphere');
    if (unisphereConfig.elements?.packages?.[oldName]) {
        const packageConfig = unisphereConfig.elements.packages[oldName];
        packageConfig.sourceRoot = newPath;
        // If renaming (e.g., core -> types, admin -> admin-react), update the key in packages object
        if (oldName !== newName) {
            // Preserve package order by rebuilding the packages object
            const newPackages = {};
            for (const key of Object.keys(unisphereConfig.elements.packages)) {
                if (key === oldName) {
                    newPackages[newName] = packageConfig;
                }
                else {
                    newPackages[key] = unisphereConfig.elements.packages[key];
                }
            }
            unisphereConfig.elements.packages = newPackages;
            devkit_1.logger.info(`  Renamed package key in .unisphere: ${oldName} -> ${newName}`);
        }
        (0, devkit_1.writeJson)(tree, '.unisphere', unisphereConfig);
    }
}
/**
 * Update package-lock.json paths
 */
function updatePackageLockPaths(tree, oldPath, newPath) {
    if (!tree.exists('package-lock.json')) {
        return;
    }
    const packageLock = (0, devkit_1.readJson)(tree, 'package-lock.json');
    let updated = false;
    if (packageLock.packages) {
        const newPackages = {};
        for (const [key, value] of Object.entries(packageLock.packages)) {
            if (key === oldPath) {
                newPackages[newPath] = value;
                updated = true;
            }
            else if (key.startsWith(oldPath + '/')) {
                // Handle nested node_modules paths if any
                newPackages[key.replace(oldPath, newPath)] = value;
                updated = true;
            }
            else {
                newPackages[key] = value;
            }
        }
        if (updated) {
            packageLock.packages = newPackages;
        }
    }
    // Update resolved paths if they exist
    if (packageLock.packages) {
        for (const value of Object.values(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, 'package-lock.json', packageLock);
        devkit_1.logger.info(`  Updated package-lock.json paths`);
    }
}
/**
 * Fix .eslintrc.json extends path after moving to subdirectory
 * Path changes from ../../../.eslintrc.json to ../../../../.eslintrc.json
 */
function fixEslintConfigPath(tree, packagePath) {
    const eslintConfigPath = `${packagePath}/.eslintrc.json`;
    if (!tree.exists(eslintConfigPath)) {
        return;
    }
    const eslintConfig = (0, devkit_1.readJson)(tree, eslintConfigPath);
    if (eslintConfig.extends && Array.isArray(eslintConfig.extends)) {
        const updatedExtends = eslintConfig.extends.map((ext) => {
            // Update the relative path to root .eslintrc.json
            if (ext === '../../../.eslintrc.json') {
                return '../../../../.eslintrc.json';
            }
            return ext;
        });
        eslintConfig.extends = updatedExtends;
        (0, devkit_1.writeJson)(tree, eslintConfigPath, eslintConfig);
        devkit_1.logger.info(`  Fixed .eslintrc.json extends path`);
    }
}
/**
 * Verify package.json name is correct
 */
function verifyPackageJsonName(tree, packagePath, expectedName) {
    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);
    if (packageJson.name !== expectedName) {
        packageJson.name = expectedName;
        (0, devkit_1.writeJson)(tree, packageJsonPath, packageJson);
        devkit_1.logger.info(`  Updated package.json name to "${expectedName}"`);
    }
}
/**
 * Update tsconfig.base.json paths for the package.
 * This ensures paths are updated even if moveGenerator fails to do so
 * (e.g., due to stale project graph cache).
 *
 * Handles both:
 * - Updating existing path mappings (when old path still exists)
 * - Adding new path mappings (when old path was removed in cleanup phase)
 */
function updateTsConfigBasePaths(tree, oldPath, newPath, oldImportPath, newImportPath) {
    if (!tree.exists('tsconfig.base.json')) {
        return;
    }
    const tsconfigBase = (0, devkit_1.readJson)(tree, 'tsconfig.base.json');
    if (!tsconfigBase.compilerOptions?.paths) {
        return;
    }
    let updated = false;
    let foundOldPath = false;
    const newPaths = {};
    for (const [key, paths] of Object.entries(tsconfigBase.compilerOptions.paths)) {
        // Check if we need to rename the key (import path changed)
        let newKey = key;
        if (key === oldImportPath || key === `${oldImportPath}/*`) {
            newKey = key === oldImportPath ? newImportPath : `${newImportPath}/*`;
            updated = true;
            foundOldPath = true;
        }
        // Update the path values
        if (Array.isArray(paths)) {
            const updatedPaths = paths.map((p) => {
                if (p.startsWith(oldPath + '/') || p === oldPath) {
                    updated = true;
                    return p.replace(oldPath, newPath);
                }
                return p;
            });
            newPaths[newKey] = updatedPaths;
        }
        else {
            newPaths[newKey] = paths;
        }
    }
    // Ensure old import path is removed and new one is added
    if (!foundOldPath) {
        // Old path mapping wasn't found during iteration, so explicitly remove it
        // This handles the case where moveGenerator didn't properly update the mapping
        if (oldImportPath !== newImportPath) {
            // Delete old import path mapping if it exists
            if (newPaths[oldImportPath]) {
                devkit_1.logger.info(`  Removing old tsconfig path mapping: "${oldImportPath}"`);
                delete newPaths[oldImportPath];
                updated = true;
            }
            // Also check for the wildcard version
            if (newPaths[`${oldImportPath}/*`]) {
                devkit_1.logger.info(`  Removing old tsconfig path mapping: "${oldImportPath}/*"`);
                delete newPaths[`${oldImportPath}/*`];
                updated = true;
            }
        }
        // Add new path mapping
        devkit_1.logger.info(`  Adding new tsconfig path mapping: "${newImportPath}" -> "${newPath}/src/index.ts"`);
        newPaths[newImportPath] = [`${newPath}/src/index.ts`];
        updated = true;
    }
    if (updated) {
        tsconfigBase.compilerOptions.paths = newPaths;
        (0, devkit_1.writeJson)(tree, 'tsconfig.base.json', tsconfigBase);
        // CRITICAL: Also write to disk immediately so subsequent migrations see the changes
        // Nx's globWithWorkspaceContextSync reads from disk, not the Tree
        try {
            (0, fs_1.writeFileSync)('tsconfig.base.json', JSON.stringify(tsconfigBase, null, 2) + '\n', 'utf-8');
            devkit_1.logger.info(`  Updated tsconfig.base.json paths (written to disk)`);
        }
        catch (error) {
            devkit_1.logger.warn(`  Failed to write tsconfig.base.json to disk: ${error}`);
        }
    }
}
/**
 * Migrate a single package
 */
async function migratePackage(tree, target) {
    const { package: pkg, newPath, newPackageJsonName, newNxProjectName, newName } = target;
    const isRename = pkg.name !== newName;
    if (isRename) {
        const renameReason = pkg.hasReactDependency && !pkg.name.endsWith('-react')
            ? ' (adding -react suffix for React dependency)'
            : pkg.name === 'core' ? ' (core -> types rename)' : '';
        devkit_1.logger.info(`Moving and renaming ${pkg.name} -> ${newName} to ${target.subdirectory}/${renameReason}`);
    }
    else {
        devkit_1.logger.info(`Moving ${pkg.name} to ${target.subdirectory}/`);
    }
    devkit_1.logger.info(`  From: ${pkg.currentPath}`);
    devkit_1.logger.info(`  To: ${newPath}`);
    devkit_1.logger.info(`  Nx project name: ${pkg.currentNxProjectName} -> ${newNxProjectName}`);
    devkit_1.logger.info(`  Package.json name: ${pkg.currentPackageJsonName} -> ${newPackageJsonName}`);
    // Verify source exists before attempting move (safety check)
    const sourcePackageJson = `${pkg.currentPath}/package.json`;
    if (!tree.exists(sourcePackageJson)) {
        devkit_1.logger.warn(`  Source package.json not found at ${sourcePackageJson}, skipping`);
        return;
    }
    // Step 0: Clean node_modules from destination on disk if present.
    // npm install may create node_modules/ at the target path (e.g. for workspace
    // dependency resolution). tree.delete() only records a virtual deletion and
    // doesn't remove on-disk children, so moveGenerator's checkDestination
    // (which uses tree.children() -> readdirSync) would see node_modules and
    // throw "Path is not empty". We must remove it from the real filesystem.
    const destNodeModules = (0, path_1.join)(newPath, 'node_modules');
    if ((0, fs_1.existsSync)(destNodeModules)) {
        devkit_1.logger.info(`  Removing leftover node_modules at destination: ${destNodeModules}`);
        (0, fs_1.rmSync)(destNodeModules, { recursive: true, force: true });
    }
    // Step 1: Use Nx moveGenerator for the heavy lifting
    try {
        await (0, generators_1.moveGenerator)(tree, {
            projectName: pkg.currentNxProjectName,
            destination: newPath,
            newProjectName: newNxProjectName,
            importPath: newPackageJsonName,
            updateImportPath: true, // Let moveGenerator update tsconfig paths
            skipFormat: true,
        });
        devkit_1.logger.info(`  Nx moved project successfully`);
    }
    catch (error) {
        devkit_1.logger.error(`  Failed to move project with Nx: ${error}`);
        devkit_1.logger.error(`  Source path: ${pkg.currentPath}`);
        devkit_1.logger.error(`  Destination path: ${newPath}`);
        devkit_1.logger.error(`  Project name: ${pkg.currentNxProjectName}`);
        throw error;
    }
    // Step 2: Update .unisphere configuration (handles rename if needed)
    updateUnisphereSourceRoot(tree, pkg.name, newName, newPath);
    devkit_1.logger.info(`  Updated .unisphere sourceRoot`);
    // Step 3: Update package-lock.json
    updatePackageLockPaths(tree, pkg.currentPath, newPath);
    // Step 4: Verify package.json name
    verifyPackageJsonName(tree, newPath, newPackageJsonName);
    // Step 5: Fix .eslintrc.json extends path
    fixEslintConfigPath(tree, newPath);
    // Step 6: Update tsconfig.base.json paths
    // This ensures paths are updated even if moveGenerator didn't do it
    // (can happen due to stale project graph cache issues)
    updateTsConfigBasePaths(tree, pkg.currentPath, newPath, pkg.currentPackageJsonName, newPackageJsonName);
    // Step 7: Update vite.config.ts cache directories if renamed
    if (isRename) {
        const viteConfigPath = `${newPath}/vite.config.ts`;
        if (tree.exists(viteConfigPath)) {
            let viteContent = tree.read(viteConfigPath, 'utf-8');
            if (viteContent) {
                const oldPattern = `package-${pkg.name}`;
                const newPattern = `package-${newName}`;
                if (viteContent.includes(oldPattern)) {
                    viteContent = viteContent.replace(new RegExp(oldPattern, 'g'), newPattern);
                    tree.write(viteConfigPath, viteContent);
                    devkit_1.logger.info(`  Updated vite.config.ts cache directories`);
                }
            }
        }
    }
}
/**
 * Main migration function
 */
async function update(tree) {
    devkit_1.logger.info('');
    devkit_1.logger.info('========================================');
    devkit_1.logger.info('Reorganizing packages by distribution channel');
    devkit_1.logger.info('========================================');
    devkit_1.logger.info('');
    // Phase 1: Discover packages
    const packages = await discoverPackages(tree);
    if (packages.length === 0) {
        devkit_1.logger.info('No packages found to migrate');
        return;
    }
    devkit_1.logger.info(`Found ${packages.length} package(s) to reorganize`);
    // Phase 2: Calculate migration targets
    const targets = packages.map(calculateMigrationTarget);
    // Group by subdirectory for logging
    const byChannel = {
        'local': targets.filter((t) => t.subdirectory === 'local'),
        'kaltura-corp': targets.filter((t) => t.subdirectory === 'kaltura-corp'),
        'unisphere': targets.filter((t) => t.subdirectory === 'unisphere'),
        'kaltura-ai': targets.filter((t) => t.subdirectory === 'kaltura-ai'),
    };
    devkit_1.logger.info('');
    devkit_1.logger.info('Migration plan:');
    devkit_1.logger.info(`  - local/ (none): ${byChannel['local'].length} package(s)`);
    devkit_1.logger.info(`  - kaltura-corp/ (github): ${byChannel['kaltura-corp'].length} package(s)`);
    devkit_1.logger.info(`  - unisphere/ (npm): ${byChannel['unisphere'].length} package(s)`);
    devkit_1.logger.info(`  - kaltura-ai/ (kaltura-ai): ${byChannel['kaltura-ai'].length} package(s)`);
    devkit_1.logger.info('');
    // Phase 3: Validate preconditions
    try {
        validateMigrationPreconditions(tree, targets);
    }
    catch (error) {
        devkit_1.logger.error(`Migration precondition failed: ${error}`);
        throw error;
    }
    // Phase 4: Execute migrations SEQUENTIALLY
    let migrated = 0;
    for (const target of targets) {
        try {
            await migratePackage(tree, target);
            migrated++;
            devkit_1.logger.info(`  [${migrated}/${targets.length}] Migrated ${target.package.name}`);
            devkit_1.logger.info('');
        }
        catch (error) {
            devkit_1.logger.error(`Failed to migrate ${target.package.name}: ${error}`);
            throw error;
        }
    }
    // Format all files at the end
    await (0, devkit_1.formatFiles)(tree);
    // Phase 5: Physically clean up any remaining old package directories from disk
    // After formatFiles(), moveGenerator should have deleted old directories from the Tree
    // But we need to ensure they're also gone from disk before the next migration runs
    devkit_1.logger.info('');
    devkit_1.logger.info('Physically cleaning up old package directories from disk...');
    const packagesDir = (0, path_1.join)(tree.root, 'unisphere', 'packages');
    if ((0, fs_1.existsSync)(packagesDir)) {
        const subdirs = (0, fs_1.readdirSync)(packagesDir, { withFileTypes: true })
            .filter(dirent => dirent.isDirectory())
            .map(dirent => dirent.name);
        let removedCount = 0;
        for (const subdir of subdirs) {
            // Keep only the new subdirectories (local, kaltura-corp, unisphere, kaltura-ai)
            // Delete everything else (old package directories that moveGenerator may have left)
            if (!['local', 'kaltura-corp', 'unisphere', 'kaltura-ai'].includes(subdir)) {
                const oldPackagePath = (0, path_1.join)(packagesDir, subdir);
                devkit_1.logger.info(`  Physically removing old package directory: ${oldPackagePath}`);
                (0, fs_1.rmSync)(oldPackagePath, { recursive: true, force: true });
                removedCount++;
            }
        }
        if (removedCount > 0) {
            devkit_1.logger.info(`  Physically removed ${removedCount} old package director(ies) from disk`);
        }
        else {
            devkit_1.logger.info('  No old package directories found on disk to remove');
        }
    }
    // Phase 6: Reset Nx's workspace data and in-memory context
    // The native WorkspaceContext (Rust addon) persists a file index in .nx/workspace-data/.
    // Since we deleted directories in Phase 5, the persisted index is stale. Delete the persisted
    // data AND reset the in-memory singleton so subsequent migrations (e.g., applications
    // reorganization) get a fresh disk scan via globWithWorkspaceContextSync().
    const workspaceDataDir = (0, path_1.join)(tree.root, '.nx', 'workspace-data');
    if ((0, fs_1.existsSync)(workspaceDataDir)) {
        (0, fs_1.rmSync)(workspaceDataDir, { recursive: true, force: true });
        devkit_1.logger.info('Deleted .nx/workspace-data/ (stale persisted file index)');
    }
    try {
        const { resetWorkspaceContext } = require('nx/src/utils/workspace-context');
        resetWorkspaceContext();
        devkit_1.logger.info('Reset in-memory workspace context');
    }
    catch (e) {
        devkit_1.logger.warn('Could not reset workspace context: ' + e);
    }
    devkit_1.logger.info('');
    devkit_1.logger.info('========================================');
    devkit_1.logger.info(`Successfully reorganized ${migrated} package(s)`);
    devkit_1.logger.info('========================================');
    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('  3. Verify all imports are updated');
    devkit_1.logger.info('');
}
