"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addVisualGenerator = addVisualGenerator;
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 visual_utils_1 = require("./visual-utils");
async function getEnquirerPrompt() {
    const enquirer = await import('enquirer');
    // Handle both CommonJS and ESM exports
    return enquirer.prompt || enquirer.default?.prompt || enquirer;
}
async function promptForRuntimeName(availableRuntimes, existingRuntimeName) {
    if (existingRuntimeName) {
        return existingRuntimeName;
    }
    const prompt = await getEnquirerPrompt();
    const runtimeNames = Object.keys(availableRuntimes);
    if (runtimeNames.length === 0) {
        throw new Error('No runtimes found in .unisphere configuration.\n' +
            'Please create a runtime first using: nx generate @unisphere/nx:add-runtime');
    }
    const runtimeChoices = runtimeNames.map(name => ({ name, message: name }));
    const response = await prompt({
        type: 'select',
        name: 'runtimeName',
        message: 'Select a runtime to add the visual to:',
        choices: runtimeChoices,
    });
    return response.runtimeName;
}
async function promptForVisualName(existingVisualName) {
    if (existingVisualName) {
        return existingVisualName;
    }
    const prompt = await getEnquirerPrompt();
    const response = await prompt({
        type: 'input',
        name: 'visualName',
        message: 'What is the visual name?',
    });
    return response.visualName;
}
async function promptForIsSingleOccurrence(existingValue) {
    if (existingValue !== undefined) {
        return existingValue;
    }
    const prompt = await getEnquirerPrompt();
    const response = await prompt({
        type: 'confirm',
        name: 'isSingleOccurrence',
        message: 'Is this visual limited to single occurrence?',
    });
    return response.isSingleOccurrence;
}
/**
 * Adds necessary imports (runtimeVisualSettings, runtimeVisualSchema) to the runtime file.
 */
function addImportToRuntime(sourceFilePath, tree, vars, project) {
    const sourceFile = (0, visual_utils_1.loadSourceFile)(tree, sourceFilePath, project);
    const { visualName__humanReadable: humanName, runtimeVisualSettings, runtimeVisualSchema, typesAlias, } = vars;
    // Check if import already exists to prevent duplicates
    const alreadyExists = sourceFile
        .getImportDeclarations()
        .some((decl) => decl
        .getNamedImports()
        .some((imp) => imp.getName() === runtimeVisualSettings));
    if (alreadyExists) {
        console.log(`⚠️ Import for visual ${humanName} already exists.`);
        return;
    }
    sourceFile.addImportDeclaration({
        kind: ts_morph_1.StructureKind.ImportDeclaration,
        namedImports: [runtimeVisualSettings, runtimeVisualSchema],
        moduleSpecifier: typesAlias,
    }); // Explicit cast for clarity
    (0, visual_utils_1.saveSourceFile)(tree, sourceFilePath, sourceFile.getFullText());
    console.log(`✅ Added import for visual ${humanName}`);
}
function updateRuntimeIndexExport(tree, runtimeName, visualName, typesPackageInfo) {
    const runtimeIndexPath = `${typesPackageInfo.basePath}/src/lib/${runtimeName}-runtime/index.ts`;
    if (!tree.exists(runtimeIndexPath)) {
        throw new Error(`Runtime index file not found at ${runtimeIndexPath}`);
    }
    const currentContent = tree.read(runtimeIndexPath, 'utf-8');
    if (!currentContent) {
        throw new Error(`Failed to read content from ${runtimeIndexPath}`);
    }
    const exportLine = `export * from './${visualName}-visual-types';`;
    // 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.replace(/\n\n$/, '\n') + exportLine;
    tree.write(runtimeIndexPath, updatedContent);
}
/**
 * Adds a new case to the primary switch statement in the runtime file.
 */
function addSwitchCaseToRuntime(sourceFilePath, tree, vars, project) {
    const sourceFile = (0, visual_utils_1.loadSourceFile)(tree, sourceFilePath, project);
    const { visualName__camelCase: visualCamel, visualName__pascalCase: visualPascal, visualName__humanReadable: humanName, } = vars;
    const switchStatement = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.SwitchStatement)[0];
    (0, visual_utils_1.throwIfNotFound)(switchStatement, `No switch statement found in ${sourceFilePath}`);
    const caseBlock = switchStatement.getCaseBlock();
    const cases = caseBlock.getClauses();
    const exists = cases.some((c) => {
        if (c.getKind() === ts_morph_1.SyntaxKind.CaseClause) {
            const clause = c;
            return (clause.getExpression()?.getText().replace(/['"]/g, '') === visualCamel);
        }
        return false;
    });
    if (exists) {
        console.log(`⚠️ Switch case for visual ${humanName} already exists.`);
        return;
    }
    const newCase = `case '${visualCamel}':
          this._render${visualPascal}(visual);
          return true`;
    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, sourceFilePath, sourceFile.getFullText());
    console.log(`✅ Added switch case for visual ${humanName}`);
}
/**
 * Adds a new visual type entry to the `visualTypes` object within the constructor's super call.
 */
function addVisualTypesToConstructor(sourceFilePath, tree, vars, project, isSingleOccurrence) {
    const sourceFile = (0, visual_utils_1.loadSourceFile)(tree, sourceFilePath, project);
    const { visualName__camelCase: visualCamel, visualName__humanReadable: humanName, runtimeVisualSchema, } = vars;
    const clazz = (0, visual_utils_1.findFirstClass)(sourceFile);
    const constructor = (0, visual_utils_1.findFirstConstructor)(clazz);
    const superCall = (0, visual_utils_1.findSuperCall)(constructor);
    const objectLiteral = (0, visual_utils_1.getObjectLiteralFromFirstArg)(superCall);
    const visualTypesProp = (0, visual_utils_1.throwIfNotFound)(objectLiteral
        .getProperties()
        .find((prop) => prop.getKind() === ts_morph_1.SyntaxKind.PropertyAssignment &&
        prop.getName() === 'visualTypes'), // Assert type after finding
    `Could not find visualTypes property in super() call in ${sourceFilePath}`);
    const visualTypesObject = (0, visual_utils_1.throwIfNotFound)(visualTypesProp.getInitializer(), // Assert type after getting initializer
    `visualTypes is not an object literal in ${sourceFilePath}`);
    if ((0, visual_utils_1.propertyExists)(visualTypesObject, visualCamel)) {
        console.log(`⚠️ Visual type for visual ${humanName} already exists.`);
        return;
    }
    visualTypesObject.addPropertyAssignment({
        name: `'${visualCamel}'`, // Ensure the key is consistently quoted for string literals
        initializer: `{ singleton: ${isSingleOccurrence}, schema: ${runtimeVisualSchema} }`,
    });
    (0, visual_utils_1.saveSourceFile)(tree, sourceFilePath, sourceFile.getFullText());
    console.log(`✅ Added visual type for ${humanName} to visualTypes.`);
}
/**
 * Adds a private render method to the main class in the runtime file.
 * Uses Nx's generateFiles for proper template processing.
 */
function addRenderMethod(sourceFilePath, tree, vars, project, typesPackageInfo) {
    const sourceFile = (0, visual_utils_1.loadSourceFile)(tree, sourceFilePath, project);
    const methodName = `_render${vars.visualName__pascalCase}`;
    const clazz = (0, visual_utils_1.findFirstClass)(sourceFile);
    // Always check and add required imports first (even if method exists)
    let importsModified = false;
    // Check if ThemeProvider import exists, add if missing
    const themeProviderImportExists = sourceFile
        .getImportDeclarations()
        .some((decl) => decl.getModuleSpecifier().getLiteralValue() ===
        '@kaltura/ds-react-theme' &&
        decl.getNamedImports().some((imp) => imp.getName() === 'ThemeProvider'));
    if (!themeProviderImportExists) {
        sourceFile.addImportDeclaration({
            kind: ts_morph_1.StructureKind.ImportDeclaration,
            namedImports: ['ThemeProvider'],
            moduleSpecifier: '@kaltura/ds-react-theme',
        });
        console.log(`✅ Added ThemeProvider import to ${sourceFilePath}`);
        importsModified = true;
    }
    // Check if ScopedUnisphereWorkspaceProvider import exists, add if missing
    const scopedProviderImportExists = sourceFile
        .getImportDeclarations()
        .some((decl) => decl.getModuleSpecifier().getLiteralValue() ===
        '@unisphere/runtime-react' &&
        decl
            .getNamedImports()
            .some((imp) => imp.getName() === 'ScopedUnisphereWorkspaceProvider'));
    if (!scopedProviderImportExists) {
        // Check if there's already an import from @unisphere/runtime-react
        const existingRuntimeReactImport = sourceFile
            .getImportDeclarations()
            .find((decl) => decl.getModuleSpecifier().getLiteralValue() ===
            '@unisphere/runtime-react');
        if (existingRuntimeReactImport) {
            // Add to existing import
            existingRuntimeReactImport.addNamedImport('ScopedUnisphereWorkspaceProvider');
        }
        else {
            // Create new import
            sourceFile.addImportDeclaration({
                kind: ts_morph_1.StructureKind.ImportDeclaration,
                namedImports: ['ScopedUnisphereWorkspaceProvider'],
                moduleSpecifier: '@unisphere/runtime-react',
            });
        }
        console.log(`✅ Added ScopedUnisphereWorkspaceProvider import to ${sourceFilePath}`);
        importsModified = true;
    }
    // Check if method already exists.
    const exists = clazz
        .getInstanceMethods()
        .some((m) => m.getName() === methodName);
    if (exists) {
        // Save imports if they were modified, even though method exists
        if (importsModified) {
            (0, visual_utils_1.saveSourceFile)(tree, sourceFilePath, sourceFile.getFullText());
        }
        console.log(`⚠️ Method ${methodName} already exists.`);
        return;
    }
    // Use Nx's generateFiles to process the template to a temporary location
    const tempDir = 'temp-render-method';
    (0, devkit_1.generateFiles)(tree, path.join(__dirname, 'templates', 'new-visual'), tempDir, {
        ...vars,
        tmpl: '', // This removes the .template extension
    });
    updateRuntimeIndexExport(tree, vars.runtimeName__lowerDashCase, vars.visualName__lowerDashCase, typesPackageInfo);
    // Read the processed template content
    const processedContent = (0, visual_utils_1.readTreeFile)(tree, `${tempDir}/render-method`);
    // Clean up the temporary file
    tree.delete(`${tempDir}/render-method`);
    // Add the private method to the class using ts-morph's addMethod
    clazz.addMethod({
        name: methodName,
        parameters: [{ name: 'visual', type: 'HtmlDomRuntimeVisual<Root>' }],
        statements: processedContent,
        scope: ts_morph_1.Scope.Private,
    });
    (0, visual_utils_1.saveSourceFile)(tree, sourceFilePath, sourceFile.getFullText());
    console.log(`✅ Added render method ${methodName}.`);
}
/**
 * Adds an interface and schema definition to the types/core runtime types file.
 */
function createVisualTypes(tree, vars, typesPackageInfo) {
    // Check if the runtime types file already exists
    const visualTypesPath = `${typesPackageInfo.basePath}/src/lib/${vars.runtimeName__lowerDashCase}-runtime/${vars.visualName__lowerDashCase}-visual-types.ts`;
    const packageName = typesPackageInfo.hasTypesPackage ? 'types' : 'core';
    if (tree.exists(visualTypesPath)) {
        throw new Error(`Visual types file "${vars.visualName__lowerDashCase}-visual-types.ts" already exists in ${packageName} package.\n` +
            'Please choose a different visual name or remove the existing file first.');
    }
    const { runtimeVisualSettings: interfaceName, runtimeVisualSchema: schemaName, visualName__humanReadable: humanName, } = vars;
    // Construct the new interface and schema content.
    const newContent = `import { ValidatorSchema } from '@unisphere/core';

export interface ${interfaceName} {
  schemaVersion?: string;
}

export const ${schemaName}: ValidatorSchema = {
  type: 'object',
  properties: {
    schemaVersion: { type: 'literal', value: '1', optional: true },
  },
};
`;
    // Append the new content to the file.
    tree.write(visualTypesPath, newContent);
    console.log(`✅ Added interface and schema for ${humanName}`);
}
/* -------------------------------------------
 * Entry Point (Main generator function)
 * -----------------------------------------*/
/**
 * Nx generator to create a new visual within a specified runtime.
 */
async function addVisualGenerator(tree, options) {
    // Validate and read .unisphere configuration first
    const unisphereConfig = (0, utils_1.validateUnisphereConfig)(tree);
    // Prompt for runtime if not provided
    const runtimeName = await promptForRuntimeName(unisphereConfig.runtimes, options.runtimeName);
    // Prompt for visual name if not provided
    const visualName = await promptForVisualName(options.visualName);
    // Prompt for isSingleOccurrence if not provided
    const isSingleOccurrence = await promptForIsSingleOccurrence(options.isSingleOccurrence);
    // Extract environment variables
    const widgetName = unisphereConfig.name;
    // Validate runtime existence and get source root path.
    const runtimeSourceRoot = (0, utils_1.validateRuntimeExists)(tree, runtimeName);
    const runtimeTSxPath = `${runtimeSourceRoot}/src/lib/runtime.tsx`;
    // Find types package (or fallback to core)
    const typesPackageInfo = (0, utils_1.findTypesOrCorePackageInfo)(tree);
    const runtimeNames = (0, utils_1.createNameTransforms)(runtimeName, 'runtimeName');
    const visualNames = (0, utils_1.createNameTransforms)(visualName, 'visualName');
    const widgetNames = (0, utils_1.createNameTransforms)(widgetName, 'widgetName');
    // Derive specific names for runtime visual settings and schema based on transforms.
    const runtimeVisualSettings = `${runtimeNames.runtimeName__pascalCase}Runtime${visualNames.visualName__pascalCase}VisualSettings`;
    const runtimeVisualSchema = `${runtimeNames.runtimeName__camelCase}Runtime${visualNames.visualName__pascalCase}VisualSettingsSchema`;
    // Combine all template variables for easy access and passing to functions.
    const templateVariables = {
        ...runtimeNames,
        ...visualNames,
        ...widgetNames,
        runtimeVisualSettings,
        runtimeVisualSchema,
        typesAlias: typesPackageInfo.alias,
    };
    // Ensure the target runtime file exists before proceeding.
    if (!tree.exists(runtimeTSxPath)) {
        throw new Error(`Runtime file not found at ${runtimeTSxPath}`);
    }
    // Initialize ts-morph project with an in-memory file system for modifications.
    const project = new ts_morph_1.Project({
        useInMemoryFileSystem: true,
        skipAddingFilesFromTsConfig: true,
    });
    // Execute all code modification steps.
    addImportToRuntime(runtimeTSxPath, tree, templateVariables, project);
    addVisualTypesToConstructor(runtimeTSxPath, tree, templateVariables, project, isSingleOccurrence);
    addSwitchCaseToRuntime(runtimeTSxPath, tree, templateVariables, project);
    addRenderMethod(runtimeTSxPath, tree, templateVariables, project, typesPackageInfo);
    // 'project' parameter is not needed here as createVisualTypes directly writes to the tree.
    createVisualTypes(tree, templateVariables, typesPackageInfo);
    // Format all modified files for consistent code style.
    await (0, devkit_1.formatFiles)(tree);
    // Log success and summary information to the console.
    console.log(`\n🎉 Successfully added visual '${visualName}' to runtime '${runtimeName}'!`);
    console.log(`   Runtime: ${runtimeName} (${runtimeSourceRoot})`);
    console.log(`   Visual: ${visualName}`);
    console.log(`   Single occurrence: ${isSingleOccurrence ? 'Yes' : 'No'}`);
    console.log(`   Modified file: ${runtimeTSxPath}`);
    return () => {
        return { visualName };
    };
}
exports.default = addVisualGenerator;
