"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addRuntimeGenerator = addRuntimeGenerator;
const tslib_1 = require("tslib");
const devkit_1 = require("@nx/devkit");
const path = tslib_1.__importStar(require("path"));
const ts_morph_1 = require("ts-morph");
const utils_1 = require("../utils");
const dependency_config_1 = require("../dependency-config");
const visual_utils_1 = require("../add-visual/visual-utils");
function validateOptions(options) {
    if (!options.name || options.name.trim() === '') {
        throw new Error(`Missing required option: 'name'\n` +
            `  Description: The name of the runtime to create\n` +
            `  Type: string\n` +
            `  Pattern: ^[a-zA-Z][a-zA-Z0-9\\-\\s]*$\n` +
            `  Example: nx g @unisphere/nx:add-runtime --name=my-runtime`);
    }
    const namePattern = /^[a-zA-Z][a-zA-Z0-9\-\s]*$/;
    if (!namePattern.test(options.name)) {
        throw new Error(`Invalid value '${options.name}' for option 'name'\n` +
            `  Pattern: ^[a-zA-Z][a-zA-Z0-9\\-\\s]*$\n` +
            `  Received: ${options.name}`);
    }
}
function validateFlavorName(flavor) {
    if (!flavor || flavor.trim() === '') {
        throw new Error(`Missing required option: 'flavor'\n` +
            `  Description: The name of the flavor to add\n` +
            `  Pattern: ^[a-zA-Z][a-zA-Z0-9\\-\\s]*$`);
    }
    const namePattern = /^[a-zA-Z][a-zA-Z0-9\-\s]*$/;
    if (!namePattern.test(flavor)) {
        throw new Error(`Invalid value '${flavor}' for option 'flavor'\n` +
            `  Pattern: ^[a-zA-Z][a-zA-Z0-9\\-\\s]*$\n` +
            `  Received: ${flavor}`);
    }
    if ((0, devkit_1.names)(flavor).fileName === 'default') {
        throw new Error(`Flavor name 'default' is reserved. The default flavor is runtime.tsx.\n` +
            `  Please choose a different flavor name.`);
    }
}
function updateCreateFactory(tree, sourceRoot, flavorNames, project) {
    const factoryPath = [`${sourceRoot}/src/lib/create-factory.tsx`, `${sourceRoot}/src/lib/create-factory.ts`]
        .find((p) => tree.exists(p));
    if (!factoryPath) {
        throw new Error(`Create factory file not found in ${sourceRoot}/src/lib/.\n` +
            `  Expected: create-factory.tsx or create-factory.ts`);
    }
    const sourceFile = (0, visual_utils_1.loadSourceFile)(tree, factoryPath, project);
    const flavorLowerDash = flavorNames['flavorName__lowerDashCase'];
    const flavorPascal = flavorNames['flavorName__pascalCase'];
    const className = `Runtime${flavorPascal}`;
    const alreadyImported = sourceFile
        .getImportDeclarations()
        .some((decl) => decl.getNamedImports().some((imp) => imp.getName() === className));
    if (alreadyImported) {
        console.log(`⚠️ Import for ${className} already exists in create-factory.`);
        return;
    }
    sourceFile.addImportDeclaration({
        kind: ts_morph_1.StructureKind.ImportDeclaration,
        namedImports: [className],
        moduleSpecifier: `./runtime-${flavorLowerDash}`,
    });
    const switchStatement = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.SwitchStatement)[0];
    if (!switchStatement) {
        const createMethod = sourceFile
            .getDescendantsOfKind(ts_morph_1.SyntaxKind.MethodDeclaration)
            .find((m) => m.getName() === 'create');
        (0, visual_utils_1.throwIfNotFound)(createMethod, `No 'create' method found in ${factoryPath}`);
        const returnStatement = createMethod.getDescendantsOfKind(ts_morph_1.SyntaxKind.ReturnStatement)[0];
        (0, visual_utils_1.throwIfNotFound)(returnStatement, `No return statement found in create method in ${factoryPath}`);
        const returnExpr = returnStatement.getExpression()?.getText() || 'new Runtime(options)';
        returnStatement.replaceWithText(`switch (options.flavor) {\n` +
            `      case '${flavorLowerDash}':\n` +
            `        return new ${className}(options) as any;\n` +
            `      default:\n` +
            `        return ${returnExpr};\n` +
            `    }`);
    }
    else {
        const caseBlock = switchStatement.getCaseBlock();
        const cases = caseBlock.getClauses();
        const caseExists = cases.some((c) => {
            if (c.getKind() === ts_morph_1.SyntaxKind.CaseClause) {
                const clause = c;
                return clause.getExpression()?.getText().replace(/['"]/g, '') === flavorLowerDash;
            }
            return false;
        });
        if (caseExists) {
            console.log(`⚠️ Switch case for flavor '${flavorLowerDash}' already exists.`);
            return;
        }
        const newCase = `case '${flavorLowerDash}':\n        return new ${className}(options) as any;`;
        const defaultIndex = cases.findIndex((c) => c.getKind() === ts_morph_1.SyntaxKind.DefaultClause);
        if (defaultIndex !== -1) {
            const defaultClause = cases[defaultIndex];
            defaultClause.replaceWithText(`${newCase}\n      ${defaultClause.getText()}`);
        }
        else {
            const lastCase = cases[cases.length - 1];
            lastCase.replaceWithText(`${lastCase.getText()}\n      ${newCase}`);
        }
    }
    (0, visual_utils_1.saveSourceFile)(tree, factoryPath, sourceFile.getFullText());
    console.log(`✅ Updated create-factory with flavor '${flavorLowerDash}'`);
}
function updateFlavorTypesIndexExport(tree, runtimeName, flavorLowerDash, typesPackageInfo) {
    const indexPath = `${typesPackageInfo.basePath}/src/lib/${runtimeName}-runtime/index.ts`;
    if (!tree.exists(indexPath)) {
        throw new Error(`Runtime types index not found at ${indexPath}`);
    }
    const currentContent = tree.read(indexPath, 'utf-8');
    if (!currentContent) {
        throw new Error(`Failed to read content from ${indexPath}`);
    }
    const exportLine = `export * from './runtime-${flavorLowerDash}-types';`;
    if (currentContent.includes(exportLine)) {
        return;
    }
    const updatedContent = currentContent.replace(/\n\n$/, '\n') + exportLine;
    tree.write(indexPath, updatedContent);
}
async function addFlavorToRuntime(tree, options) {
    const runtimeName = (0, devkit_1.names)(options.name).fileName;
    const flavor = options.flavor;
    validateFlavorName(flavor);
    const unisphereConfig = (0, utils_1.validateUnisphereConfig)(tree);
    const sourceRoot = (0, utils_1.validateRuntimeExists)(tree, runtimeName);
    const typesPackageInfo = (0, utils_1.findTypesOrCorePackageInfo)(tree);
    const flavorLowerDash = (0, devkit_1.names)(flavor).fileName;
    const flavorFilePath = `${sourceRoot}/src/lib/runtime-${flavorLowerDash}.tsx`;
    if (tree.exists(flavorFilePath)) {
        throw new Error(`Flavor file already exists at ${flavorFilePath}.\n` +
            `  Choose a different flavor name or remove the existing file.`);
    }
    const flavorTypesPath = `${typesPackageInfo.basePath}/src/lib/${runtimeName}-runtime/runtime-${flavorLowerDash}-types.ts`;
    if (tree.exists(flavorTypesPath)) {
        throw new Error(`Flavor types file already exists at ${flavorTypesPath}.\n` +
            `  Choose a different flavor name or remove the existing file.`);
    }
    const runtimeNames = (0, utils_1.createNameTransforms)(runtimeName, 'runtimeName');
    const flavorNames = (0, utils_1.createNameTransforms)(flavorLowerDash, 'flavorName');
    const widgetNames = (0, utils_1.createNameTransforms)(unisphereConfig.name, 'widgetName');
    const templateVariables = {
        ...runtimeNames,
        ...flavorNames,
        ...widgetNames,
        typesAlias: typesPackageInfo.alias,
        tmpl: '',
    };
    (0, devkit_1.generateFiles)(tree, path.join(__dirname, 'templates/new-flavor'), `${sourceRoot}/src/lib`, templateVariables);
    (0, devkit_1.generateFiles)(tree, path.join(__dirname, 'templates/new-flavor-types'), `${typesPackageInfo.basePath}/src/lib/${runtimeName}-runtime`, templateVariables);
    updateFlavorTypesIndexExport(tree, runtimeName, flavorLowerDash, typesPackageInfo);
    const project = new ts_morph_1.Project({
        useInMemoryFileSystem: true,
        skipAddingFilesFromTsConfig: true,
    });
    updateCreateFactory(tree, sourceRoot, flavorNames, project);
    await (0, devkit_1.formatFiles)(tree);
    console.log(`\n🎉 Successfully added flavor '${flavorLowerDash}' to runtime '${runtimeName}'!`);
    console.log(`   Flavor file: ${flavorFilePath}`);
    console.log(`   Types file: ${flavorTypesPath}`);
    return () => {
        return { runtimeName, flavor: flavorLowerDash };
    };
}
function updateTypesIndexExport(tree, runtimeName, basePath) {
    const indexPath = `${basePath}/src/index.ts`;
    if (!tree.exists(indexPath)) {
        throw new Error(`Index file not found at ${indexPath}`);
    }
    const currentContent = tree.read(indexPath, 'utf-8');
    if (!currentContent) {
        throw new Error(`Failed to read content from ${indexPath}`);
    }
    const exportLine = `export * from './lib/${runtimeName}-runtime';`;
    // Check if the export already exists
    if (currentContent.includes(exportLine)) {
        return; // Already exported
    }
    // Add the export at the end of the file
    const updatedContent = currentContent + '\n' + exportLine;
    tree.write(indexPath, updatedContent);
}
async function addRuntimeGenerator(tree, options) {
    validateOptions(options);
    if (options.flavor) {
        return addFlavorToRuntime(tree, options);
    }
    // Validate and read .unisphere configuration
    const unisphereConfig = (0, utils_1.validateUnisphereConfig)(tree);
    // Validate dependencies
    const selectedDependencies = options.dependencies || [];
    // Extract environment variables
    const companyName = unisphereConfig.company;
    const widgetName = unisphereConfig.name;
    // Validate and correct runtime naming for React/DS dependencies
    let userInputRuntimeName = (0, devkit_1.names)(options.name).fileName;
    // Find types package (or fallback to core)
    const typesPackageInfo = (0, utils_1.findTypesOrCorePackageInfo)(tree);
    // Prepare template variables
    const templateVariables = {
        companyName,
        typesAlias: typesPackageInfo.alias,
        // Add all name transformations
        ...(0, utils_1.createNameTransforms)(userInputRuntimeName, 'runtimeName'),
        ...(0, utils_1.createNameTransforms)(widgetName, 'widgetName'),
        tmpl: '',
    };
    const projectRoot = `unisphere/runtimes/${userInputRuntimeName}`;
    // Check if name already exists across all element types in .unisphere
    (0, utils_1.validateElementNameUnique)(tree, userInputRuntimeName, 'runtimes');
    // Check if the runtime already exists on filesystem
    if (tree.exists(projectRoot)) {
        throw new Error(`Runtime "${userInputRuntimeName}" already exists at ${projectRoot}.\n` +
            'Please choose a different runtime name or remove the existing runtime first.');
    }
    // Check if the runtime types file already exists in types/core package
    const runtimeTypesPath = `${typesPackageInfo.basePath}/src/lib/${userInputRuntimeName}-runtime/runtime-types.ts`;
    if (tree.exists(runtimeTypesPath)) {
        const packageName = typesPackageInfo.hasTypesPackage ? 'types' : 'core';
        throw new Error(`Runtime types file "${userInputRuntimeName}-runtime/runtime-types.ts" already exists in ${packageName} package.\n` +
            'Please choose a different runtime name or remove the existing file first.');
    }
    // Generate files from templates for the runtime
    (0, devkit_1.generateFiles)(tree, path.join(__dirname, 'templates/new-runtime'), projectRoot, templateVariables);
    // Generate the runtime types file in the types/core package using template
    (0, devkit_1.generateFiles)(tree, path.join(__dirname, 'templates/types-templates'), `${typesPackageInfo.basePath}/src/lib`, templateVariables);
    // Update types package index.ts to export the new runtime types
    updateTypesIndexExport(tree, userInputRuntimeName, typesPackageInfo.basePath);
    // Update .unisphere configuration
    (0, utils_1.updateUnisphereConfig)(tree, 'runtimes', userInputRuntimeName, {
        sourceRoot: `unisphere/runtimes/${userInputRuntimeName}`,
        distributionChannel: 'unisphere',
    });
    // Update tsconfig.base.json with path mapping
    const runtimePathKey = `unisphere-runtime-${userInputRuntimeName}`;
    const runtimePathValue = `unisphere/runtimes/${userInputRuntimeName}/src/index.ts`;
    (0, utils_1.updateTsConfigPaths)(tree, runtimePathKey, runtimePathValue);
    // Install selected dependencies
    (0, dependency_config_1.installSelectedDependencies)(tree, selectedDependencies);
    await (0, devkit_1.formatFiles)(tree);
    // Return a function that will be executed after all file operations are complete
    return () => {
        (0, devkit_1.installPackagesTask)(tree, true);
        devkit_1.logger.info('');
        devkit_1.logger.info('✅ Runtime generated successfully!');
        devkit_1.logger.info('');
        devkit_1.logger.info(`🚀 Runtime Name: ${userInputRuntimeName}`);
        devkit_1.logger.info(`📁 Location: ${projectRoot}`);
        devkit_1.logger.info('📝 Next steps:');
        devkit_1.logger.info(`   1. Add a visual: nx g @unisphere/nx:add-visual --runtimeName=${userInputRuntimeName}`);
        devkit_1.logger.info(`   2. Add a dev app: nx g @unisphere/nx:add-application --name=${userInputRuntimeName}-dev --runtimeName=${userInputRuntimeName}`);
        devkit_1.logger.info('');
    };
}
exports.default = addRuntimeGenerator;
