"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateUnisphereConfig = validateUnisphereConfig;
exports.updateTsConfigPaths = updateTsConfigPaths;
exports.validateElementNameUnique = validateElementNameUnique;
exports.updateUnisphereConfig = updateUnisphereConfig;
exports.validateRuntimeExists = validateRuntimeExists;
exports.findCorePackageAlias = findCorePackageAlias;
exports.findTypesPackageAlias = findTypesPackageAlias;
exports.findTypesOrCorePackageInfo = findTypesOrCorePackageInfo;
exports.createNameTransforms = createNameTransforms;
exports.isPackageInstalled = isPackageInstalled;
exports.checkIfRuntimeExists = checkIfRuntimeExists;
exports.getSubdirectoryFromScope = getSubdirectoryFromScope;
exports.extractSubdirectoryFromSourceRoot = extractSubdirectoryFromSourceRoot;
exports.extractPackageNameFromSourceRoot = extractPackageNameFromSourceRoot;
exports.addScriptToRootPackageJson = addScriptToRootPackageJson;
exports.extractApplicationSubdirectory = extractApplicationSubdirectory;
exports.renameScriptInRootPackageJson = renameScriptInRootPackageJson;
const devkit_1 = require("@nx/devkit");
/**
 * Extracts visual type names from a runtime.tsx file
 * Looks for visualTypes object in the constructor's super() call
 * Returns empty array if file doesn't exist or parsing fails
 */
function extractVisualTypesFromRuntime(tree, runtimeSourceRoot) {
    const runtimeFilePath = `${runtimeSourceRoot}/src/lib/runtime.tsx`;
    // Check if runtime.tsx exists
    if (!tree.exists(runtimeFilePath)) {
        return [];
    }
    try {
        // Read the file content
        const content = tree.read(runtimeFilePath, 'utf-8');
        if (!content) {
            return [];
        }
        // Find the start of visualTypes
        const visualTypesStart = content.indexOf('visualTypes:');
        if (visualTypesStart === -1) {
            return [];
        }
        // Find the opening brace after visualTypes
        const openBraceIndex = content.indexOf('{', visualTypesStart);
        if (openBraceIndex === -1) {
            return [];
        }
        // Count braces to find the matching closing brace
        let braceCount = 1;
        let currentIndex = openBraceIndex + 1;
        let closeBraceIndex = -1;
        while (currentIndex < content.length && braceCount > 0) {
            if (content[currentIndex] === '{') {
                braceCount++;
            }
            else if (content[currentIndex] === '}') {
                braceCount--;
                if (braceCount === 0) {
                    closeBraceIndex = currentIndex;
                    break;
                }
            }
            currentIndex++;
        }
        if (closeBraceIndex === -1) {
            return [];
        }
        // Extract the content between the braces
        const visualTypesContent = content.substring(openBraceIndex + 1, closeBraceIndex);
        // Find the minimum indentation level (first-level properties)
        const lines = visualTypesContent.split('\n');
        const indentations = [];
        for (const line of lines) {
            const match = line.match(/^(\s*)(\w+)\s*:/);
            if (match) {
                indentations.push(match[1].length);
            }
        }
        // If no properties found, return empty array
        if (indentations.length === 0) {
            return [];
        }
        // Find minimum indentation (first-level properties)
        const minIndent = Math.min(...indentations);
        // Extract visual names (only first-level properties with minimum indentation)
        const visuals = [];
        for (const line of lines) {
            const match = line.match(/^(\s*)(\w+)\s*:/);
            if (match && match[1].length === minIndent) {
                visuals.push(match[2]);
            }
        }
        return visuals;
    }
    catch (error) {
        // If any error occurs during parsing, return empty array
        return [];
    }
}
function validateUnisphereConfig(tree) {
    // Check if file exists
    if (!tree.exists('.unisphere')) {
        throw new Error('Missing .unisphere configuration file in root project.\n' +
            'Please create .unisphere with the following structure:\n' +
            '{\n' +
            '  "company": "Your Company Name",\n' +
            '  "name": "your-experience-name"\n' +
            '}');
    }
    // Read and parse JSON
    let config;
    try {
        config = (0, devkit_1.readJson)(tree, '.unisphere');
    }
    catch (error) {
        throw new Error(`Failed to read .unisphere: ${error instanceof Error ? error.message : 'Unknown error'}\n` + 'Please ensure the file contains valid JSON.');
    }
    // Validate required fields
    const missingFields = [];
    if (!config.company) {
        missingFields.push('"company"');
    }
    if (!config.name) {
        missingFields.push('"name"');
    }
    if (missingFields.length > 0) {
        throw new Error(`Missing required field${missingFields.length > 1 ? 's' : ''} ${missingFields.join(' and ')} in .unisphere.\n` +
            'Please ensure your .unisphere contains:\n' +
            '{\n' +
            '  "company": "Your Company Name",\n' +
            '  "name": "your-experience-name"\n' +
            '}');
    }
    // Extract runtimes with their visual types
    const runtimes = {};
    if (config.elements?.runtimes) {
        for (const [runtimeName, runtimeConfig] of Object.entries(config.elements.runtimes)) {
            const sourceRoot = runtimeConfig.sourceRoot;
            if (sourceRoot && typeof sourceRoot === 'string') {
                const visuals = extractVisualTypesFromRuntime(tree, sourceRoot);
                runtimes[runtimeName] = {
                    sourceRoot,
                    visuals,
                };
            }
        }
    }
    return {
        company: config.company,
        name: config.name,
        runtimes,
    };
}
function updateTsConfigPaths(tree, pathKey, pathValue) {
    if (tree.exists('tsconfig.base.json')) {
        try {
            const tsconfigBase = (0, devkit_1.readJson)(tree, 'tsconfig.base.json');
            // Ensure compilerOptions and paths exist
            if (!tsconfigBase.compilerOptions) {
                tsconfigBase.compilerOptions = {};
            }
            if (!tsconfigBase.compilerOptions.paths) {
                tsconfigBase.compilerOptions.paths = {};
            }
            // Add the path mapping (only if it doesn't already exist)
            if (!tsconfigBase.compilerOptions.paths[pathKey]) {
                tsconfigBase.compilerOptions.paths[pathKey] = [pathValue];
            }
            // Write the updated tsconfig back
            (0, devkit_1.writeJson)(tree, 'tsconfig.base.json', tsconfigBase);
        }
        catch (error) {
            throw new Error(`Failed to update tsconfig.base.json: ${error instanceof Error ? error.message : 'Unknown error'}`);
        }
    }
}
function validateElementNameUnique(tree, elementName, targetType) {
    if (!tree.exists('.unisphere')) {
        return;
    }
    const config = (0, devkit_1.readJson)(tree, '.unisphere');
    const elements = config.elements || {};
    const allTypes = ['packages', 'runtimes', 'applications', 'documentation'];
    for (const type of allTypes) {
        if (elements[type] && elements[type][elementName]) {
            if (type === targetType) {
                throw new Error(`A ${type.slice(0, -1)} named "${elementName}" already exists.\n` +
                    `  Registered in .unisphere under elements.${type}.`);
            }
            else {
                throw new Error(`The name "${elementName}" is already used by a ${type.slice(0, -1)}.\n` +
                    `  Registered in .unisphere under elements.${type}.\n` +
                    `  Names must be unique across all element types (packages, runtimes, applications, documentation).`);
            }
        }
    }
}
function updateUnisphereConfig(tree, elementType, elementName, elementConfig) {
    const existingConfig = (0, devkit_1.readJson)(tree, '.unisphere');
    // Ensure elements structure exists
    if (!existingConfig.elements) {
        existingConfig.elements = {};
    }
    if (!existingConfig.elements[elementType]) {
        existingConfig.elements[elementType] = {};
    }
    // Add the new element entry (only if it doesn't already exist)
    if (!existingConfig.elements[elementType][elementName]) {
        existingConfig.elements[elementType][elementName] = elementConfig;
    }
    // Write the updated configuration back
    try {
        (0, devkit_1.writeJson)(tree, '.unisphere', existingConfig);
    }
    catch (error) {
        throw new Error(`Failed to update .unisphere: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
}
function validateRuntimeExists(tree, runtimeName) {
    // First validate that .unisphere exists
    if (!tree.exists('.unisphere')) {
        throw new Error('Missing .unisphere configuration file in root project.');
    }
    // Read and parse the .unisphere configuration
    let config;
    try {
        config = (0, devkit_1.readJson)(tree, '.unisphere');
    }
    catch (error) {
        throw new Error(`Failed to read .unisphere: ${error instanceof Error ? error.message : 'Unknown error'}\n` + 'Please ensure the file contains valid JSON.');
    }
    // Check if elements.runtimes exists
    if (!config.elements?.runtimes) {
        throw new Error(`No runtimes found in .unisphere configuration.\n` +
            `Runtime "${runtimeName}" not found.\n` +
            'Please create a runtime first using: nx generate @unisphere/nx:add-runtime');
    }
    // Check if the specific runtime exists
    const runtimeConfig = config.elements.runtimes[runtimeName];
    if (!runtimeConfig) {
        throw new Error(`Runtime "${runtimeName}" not found in .unisphere configuration.`);
    }
    // Check if sourceRoot exists
    if (!runtimeConfig.sourceRoot) {
        throw new Error(`Runtime "${runtimeName}" exists but has no sourceRoot configured`);
    }
    return runtimeConfig.sourceRoot;
}
/**
 * Checks if a path matches the core package pattern
 * Supports both old (unisphere/packages/core) and new (unisphere/packages/<visibility>/core) patterns
 */
function isCorePackagePath(pathValue) {
    // Old pattern: unisphere/packages/core/src/index.ts
    // New patterns with subdirectories
    const corePatterns = [
        'unisphere/packages/core/src/index.ts',
        'unisphere/packages/local/core/src/index.ts',
        'unisphere/packages/kaltura-corp/core/src/index.ts',
        'unisphere/packages/unisphere/core/src/index.ts',
        'unisphere/packages/kaltura-ui/core/src/index.ts',
        'unisphere/packages/kaltura-apps/core/src/index.ts',
        'unisphere/packages/kaltura-sdk/core/src/index.ts',
    ];
    return corePatterns.includes(pathValue);
}
/**
 * Extracts the base path from a tsconfig path value
 * e.g., "unisphere/packages/public/core/src/index.ts" -> "unisphere/packages/public/core"
 */
function extractBasePath(pathValue) {
    return pathValue.replace('/src/index.ts', '');
}
function findCorePackageAlias(tree) {
    if (!tree.exists('tsconfig.base.json')) {
        throw new Error('tsconfig.base.json not found in the project root.');
    }
    try {
        const tsconfigBase = (0, devkit_1.readJson)(tree, 'tsconfig.base.json');
        if (!tsconfigBase.compilerOptions?.paths) {
            throw new Error('No paths found in tsconfig.base.json compilerOptions.');
        }
        // Find the key that has a core package path (supports both old and new patterns)
        for (const [key, paths] of Object.entries(tsconfigBase.compilerOptions.paths)) {
            if (Array.isArray(paths) && paths.some(p => isCorePackagePath(p))) {
                return key;
            }
        }
        throw new Error('Core package not found in tsconfig.base.json paths. ' +
            'Expected to find a key with value matching "unisphere/packages/[<visibility>/]core/src/index.ts"');
    }
    catch (error) {
        throw new Error(`Failed to read tsconfig.base.json: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
}
/**
 * Checks if a path matches the types package pattern
 * Supports both old (unisphere/packages/types) and new (unisphere/packages/<visibility>/types) patterns
 */
function isTypesPackagePath(pathValue) {
    const typesPatterns = [
        'unisphere/packages/types/src/index.ts',
        // Old subdirectory names (for backwards compatibility)
        'unisphere/packages/private/types/src/index.ts',
        'unisphere/packages/corp/types/src/index.ts',
        'unisphere/packages/public/types/src/index.ts',
        // New subdirectory names (v3 migration)
        'unisphere/packages/local/types/src/index.ts',
        'unisphere/packages/kaltura-corp/types/src/index.ts',
        'unisphere/packages/unisphere/types/src/index.ts',
        'unisphere/packages/kaltura-ui/types/src/index.ts',
        'unisphere/packages/kaltura-apps/types/src/index.ts',
        'unisphere/packages/kaltura-sdk/types/src/index.ts',
    ];
    return typesPatterns.includes(pathValue);
}
/**
 * Tries to find the types package alias in tsconfig.base.json
 * Returns null if types package is not found
 */
function findTypesPackageAlias(tree) {
    if (!tree.exists('tsconfig.base.json')) {
        return null;
    }
    try {
        const tsconfigBase = (0, devkit_1.readJson)(tree, 'tsconfig.base.json');
        if (!tsconfigBase.compilerOptions?.paths) {
            return null;
        }
        // Find the key that has a types package path (supports both old and new patterns)
        for (const [key, paths] of Object.entries(tsconfigBase.compilerOptions.paths)) {
            if (Array.isArray(paths) && paths.some(p => isTypesPackagePath(p))) {
                return key;
            }
        }
        return null;
    }
    catch {
        return null;
    }
}
/**
 * Finds the actual base path for a package from tsconfig.base.json
 */
function findPackageBasePath(tree, pathMatcher) {
    if (!tree.exists('tsconfig.base.json')) {
        return null;
    }
    try {
        const tsconfigBase = (0, devkit_1.readJson)(tree, 'tsconfig.base.json');
        if (!tsconfigBase.compilerOptions?.paths) {
            return null;
        }
        for (const [, paths] of Object.entries(tsconfigBase.compilerOptions.paths)) {
            if (Array.isArray(paths)) {
                const matchingPath = paths.find(p => pathMatcher(p));
                if (matchingPath) {
                    return extractBasePath(matchingPath);
                }
            }
        }
        return null;
    }
    catch {
        return null;
    }
}
/**
 * Finds the types package if it exists, otherwise falls back to core package.
 * Returns info about which package to use for runtime types.
 */
function findTypesOrCorePackageInfo(tree) {
    const typesAlias = findTypesPackageAlias(tree);
    if (typesAlias) {
        const basePath = findPackageBasePath(tree, isTypesPackagePath);
        if (!basePath) {
            throw new Error('Types package base path not found');
        }
        return {
            alias: typesAlias,
            basePath,
            hasTypesPackage: true,
        };
    }
    // Fallback to core package
    const coreAlias = findCorePackageAlias(tree);
    const basePath = findPackageBasePath(tree, isCorePackagePath);
    if (!basePath) {
        throw new Error('Core package base path not found');
    }
    return {
        alias: coreAlias,
        basePath,
        hasTypesPackage: false,
    };
}
function createNameTransforms(baseName, prefix) {
    if (!baseName) {
        return {
            [`${prefix}`]: '',
            [`${prefix}__lowerDashCase`]: '',
            [`${prefix}__camelCase`]: '',
            [`${prefix}__pascalCase`]: '',
            [`${prefix}__constantCase`]: '',
            [`${prefix}__humanReadable`]: '',
        }; // Type assertion to match the defined type
    }
    const nameVariants = (0, devkit_1.names)(baseName); // Assuming 'names' function is available and returns NameVariants
    const humanReadable = baseName
        .replace(/[-_]/g, ' ')
        .replace(/\b\w/g, (l) => l.toUpperCase());
    return {
        [`${prefix}`]: baseName,
        [`${prefix}__lowerDashCase`]: nameVariants.fileName,
        [`${prefix}__camelCase`]: nameVariants.propertyName,
        [`${prefix}__pascalCase`]: nameVariants.className,
        [`${prefix}__constantCase`]: nameVariants.constantName,
        [`${prefix}__humanReadable`]: humanReadable,
    }; // Type assertion to match the defined type
}
function isPackageInstalled(tree, packageName) {
    // Check if package.json exists
    if (!tree.exists('package.json')) {
        devkit_1.logger.warn('⚠️ package.json not found in root directory');
        return false;
    }
    try {
        const packageJson = (0, devkit_1.readJson)(tree, 'package.json');
        // Check all dependency types
        const allDependencies = {
            ...packageJson.dependencies,
            ...packageJson.devDependencies,
            ...packageJson.peerDependencies,
        };
        const installedVersion = allDependencies[packageName];
        return installedVersion || false;
    }
    catch (error) {
        devkit_1.logger.warn(`⚠️ Failed to read package.json: ${error instanceof Error ? error.message : 'Unknown error'}`);
        return false;
    }
}
function checkIfRuntimeExists(tree, runtimeName) {
    // Check if .unisphere file exists
    if (!tree.exists('.unisphere')) {
        return false;
    }
    try {
        // Read and parse the .unisphere configuration
        const config = (0, devkit_1.readJson)(tree, '.unisphere');
        // Check if elements.runtimes exists and contains the runtime
        return !!(config.elements?.runtimes?.[runtimeName]);
    }
    catch (error) {
        // If there's any error reading/parsing the file, return false
        return false;
    }
}
/**
 * Maps package scope to subdirectory name
 * @param scope - The package scope ('public', 'internal', 'none')
 * @returns The subdirectory name ('unisphere', 'kaltura-corp', 'local')
 */
function getSubdirectoryFromScope(scope) {
    return scope;
}
/**
 * Extracts the subdirectory from a package sourceRoot path
 * @param sourceRoot - The sourceRoot path (e.g., "unisphere/packages/kaltura-corp/shared")
 * @returns The subdirectory name ('unisphere', 'kaltura-corp', 'local') or null if flat structure
 */
function extractSubdirectoryFromSourceRoot(sourceRoot) {
    const parts = sourceRoot.split('/');
    // Expected format: unisphere/packages/<subdirectory>/<packageName>
    // Or old format: unisphere/packages/<packageName>
    if (parts.length === 4 && parts[0] === 'unisphere' && parts[1] === 'packages') {
        const subdirectory = parts[2];
        if (['local', 'kaltura-corp', 'unisphere', 'kaltura-ui', 'kaltura-apps', 'kaltura-sdk'].includes(subdirectory)) {
            return subdirectory;
        }
    }
    return null; // Flat structure
}
/**
 * Extracts the package name from a package sourceRoot path
 * @param sourceRoot - The sourceRoot path (e.g., "unisphere/packages/corp/shared" or "unisphere/packages/shared")
 * @returns The package name
 */
function extractPackageNameFromSourceRoot(sourceRoot) {
    const parts = sourceRoot.split('/');
    // Return the last part of the path
    return parts[parts.length - 1];
}
/**
 * Adds a script to the root package.json
 * @param tree - The file system tree
 * @param scriptName - The name of the script (e.g., "dev:my-app")
 * @param scriptCommand - The command to run (e.g., "npx unisphere application serve my-app --port 4002")
 */
function addScriptToRootPackageJson(tree, scriptName, scriptCommand) {
    if (!tree.exists('package.json')) {
        devkit_1.logger.warn('⚠️ package.json not found in root directory, cannot add script');
        return;
    }
    try {
        const packageJson = (0, devkit_1.readJson)(tree, 'package.json');
        // Ensure scripts object exists
        if (!packageJson.scripts) {
            packageJson.scripts = {};
        }
        // Only add if script doesn't already exist
        if (!packageJson.scripts[scriptName]) {
            packageJson.scripts[scriptName] = scriptCommand;
            (0, devkit_1.writeJson)(tree, 'package.json', packageJson);
        }
    }
    catch (error) {
        devkit_1.logger.warn(`⚠️ Failed to add script to package.json: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
}
function extractApplicationSubdirectory(sourceRoot) {
    const parts = sourceRoot.split('/');
    if (parts.length === 4 && parts[0] === 'unisphere' && parts[1] === 'applications') {
        const subdirectory = parts[2];
        if (['local', 'server'].includes(subdirectory)) {
            return subdirectory;
        }
    }
    return null;
}
function renameScriptInRootPackageJson(tree, oldScriptName, newScriptName, newScriptCommand) {
    if (!tree.exists('package.json')) {
        return;
    }
    const packageJson = (0, devkit_1.readJson)(tree, 'package.json');
    if (!packageJson.scripts?.[oldScriptName]) {
        return;
    }
    delete packageJson.scripts[oldScriptName];
    packageJson.scripts[newScriptName] = newScriptCommand;
    (0, devkit_1.writeJson)(tree, 'package.json', packageJson);
    devkit_1.logger.info(`✅ Renamed serve script: ${oldScriptName} → ${newScriptName}`);
}
