'use strict';
/**
 * @license Angular v21.2.1
 * (c) 2010-2026 Google LLC. https://angular.dev/
 * License: MIT
 */
'use strict';

var ts = require('typescript');
require('@angular/compiler-cli');
var migrations = require('@angular/compiler-cli/private/migrations');
require('node:path');
var project_paths = require('./project_paths-D2V-Uh2L.cjs');
var compiler = require('@angular/compiler');
var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs');
var imports = require('./imports-CVmcbVA9.cjs');
var parse_html = require('./parse_html-C8eKA9px.cjs');
var ng_component_template = require('./ng_component_template-BOuKAnQd.cjs');
require('@angular-devkit/core');
require('node:path/posix');
require('@angular-devkit/schematics');
require('./project_tsconfig_paths-DkkMibv-.cjs');
require('./ng_decorators-DYy6II6x.cjs');
require('./property_name-BCpALNpZ.cjs');

const ngStyleStr = 'NgStyle';
const commonModuleStr = '@angular/common';
const commonModuleImportsStr = 'CommonModule';
function migrateNgStyleBindings(template, config, componentNode, typeChecker) {
    const parsed = parse_html.parseTemplate(template);
    if (!parsed.tree || !parsed.tree.rootNodes.length) {
        return { migrated: template, changed: false, replacementCount: 0, canRemoveCommonModule: false };
    }
    const visitor = new NgStyleCollector(template, componentNode, typeChecker);
    compiler.visitAll(visitor, parsed.tree.rootNodes, config);
    let newTemplate = template;
    let changedOffset = 0;
    let replacementCount = 0;
    for (const { start, end, replacement } of visitor.replacements) {
        const currentLength = newTemplate.length;
        newTemplate = replaceTemplate(newTemplate, replacement, start, end, changedOffset);
        changedOffset += newTemplate.length - currentLength;
        replacementCount++;
    }
    const changed = newTemplate !== template;
    return {
        migrated: newTemplate,
        changed,
        replacementCount,
        canRemoveCommonModule: changed ? parse_html.canRemoveCommonModule(newTemplate) : false,
    };
}
/**
 * Creates a Replacement to remove `NgStyle` from a component's `imports` array.
 * Uses ReflectionHost + PartialEvaluator for robust AST analysis.
 */
function createNgStyleImportsArrayRemoval(classNode, file, typeChecker, removeCommonModule) {
    const reflector = new migrations.TypeScriptReflectionHost(typeChecker);
    const decorators = reflector.getDecoratorsOfDeclaration(classNode);
    if (!decorators) {
        return null;
    }
    const componentDecorator = decorators.find((decorator) => decorator.name === 'Component');
    if (!componentDecorator?.node) {
        return null;
    }
    const decoratorNode = componentDecorator.node;
    if (!ts.isDecorator(decoratorNode) ||
        !ts.isCallExpression(decoratorNode.expression) ||
        decoratorNode.expression.arguments.length === 0 ||
        !ts.isObjectLiteralExpression(decoratorNode.expression.arguments[0])) {
        return null;
    }
    const metadata = decoratorNode.expression.arguments[0];
    const importsProperty = metadata.properties.find((p) => ts.isPropertyAssignment(p) && p.name?.getText() === 'imports');
    if (!importsProperty || !ts.isArrayLiteralExpression(importsProperty.initializer)) {
        return null;
    }
    const importsArray = importsProperty.initializer;
    const elementsToRemove = new Set([ngStyleStr]);
    if (removeCommonModule) {
        elementsToRemove.add(commonModuleImportsStr);
    }
    const originalElements = importsArray.elements;
    const filteredElements = originalElements.filter((el) => !ts.isIdentifier(el) || !elementsToRemove.has(el.text));
    if (filteredElements.length === originalElements.length) {
        return null; // No changes needed.
    }
    // If the array becomes empty, remove the entire `imports` property.
    if (filteredElements.length === 0) {
        const removalRange = getPropertyRemovalRange(importsProperty);
        return new project_paths.Replacement(file, new project_paths.TextUpdate({
            position: removalRange.start,
            end: removalRange.end,
            toInsert: '',
        }));
    }
    const printer = ts.createPrinter();
    const newArray = ts.factory.updateArrayLiteralExpression(importsArray, filteredElements);
    const newText = printer.printNode(ts.EmitHint.Unspecified, newArray, classNode.getSourceFile());
    return new project_paths.Replacement(file, new project_paths.TextUpdate({
        position: importsArray.getStart(),
        end: importsArray.getEnd(),
        toInsert: newText,
    }));
}
/**
 * Calculates the removal range for a property in an object literal,
 * including the trailing comma if it's not the last property.
 */
function getPropertyRemovalRange(property) {
    const parent = property.parent;
    if (!ts.isObjectLiteralExpression(parent)) {
        return { start: property.getStart(), end: property.getEnd() };
    }
    const properties = parent.properties;
    const propertyIndex = properties.indexOf(property);
    const end = property.getEnd();
    if (propertyIndex < properties.length - 1) {
        const nextProperty = properties[propertyIndex + 1];
        return { start: property.getStart(), end: nextProperty.getStart() };
    }
    return { start: property.getStart(), end };
}
function calculateImportReplacements(info, sourceFiles, filesToRemoveCommonModule) {
    const importReplacements = {};
    const importManager = new migrations.ImportManager();
    for (const sf of sourceFiles) {
        const file = project_paths.projectFile(sf, info);
        // Always remove NgStyle if it's imported directly.
        importManager.removeImport(sf, ngStyleStr, commonModuleStr);
        // Conditionally remove CommonModule if it's no longer needed.
        if (filesToRemoveCommonModule.has(file.id)) {
            importManager.removeImport(sf, commonModuleImportsStr, commonModuleStr);
        }
        const addRemove = [];
        apply_import_manager.applyImportManagerChanges(importManager, addRemove, [sf], info);
        if (addRemove.length > 0) {
            importReplacements[file.id] = {
                add: [],
                addAndRemove: addRemove,
            };
        }
    }
    return importReplacements;
}
function replaceTemplate(template, replaceValue, start, end, offset) {
    return template.slice(0, start + offset) + replaceValue + template.slice(end + offset);
}
/**
 * Visitor class that scans Angular templates and collects replacements
 * for [ngStyle] bindings that use static object literals.
 */
class NgStyleCollector extends compiler.RecursiveVisitor {
    originalTemplate;
    replacements = [];
    isNgStyleImported = true; // Default to true (permissive)
    constructor(originalTemplate, componentNode, typeChecker) {
        super();
        this.originalTemplate = originalTemplate;
        // If we have enough information, check if NgStyle is actually imported.
        // If not, we can confidently disable the migration for this component.
        if (componentNode && typeChecker) {
            const imports$1 = imports.getImportSpecifiers(componentNode.getSourceFile(), commonModuleStr, [
                ngStyleStr,
                commonModuleImportsStr,
            ]);
            if (imports$1.length === 0) {
                this.isNgStyleImported = false;
            }
        }
    }
    visitElement(element, config) {
        // If NgStyle is not imported, do not attempt to migrate.
        if (!this.isNgStyleImported) {
            return;
        }
        for (const attr of element.attrs) {
            if (attr.name !== '[ngStyle]' && attr.name !== 'ngStyle') {
                continue;
            }
            if (attr.name === '[ngStyle]' && attr.valueSpan) {
                const expr = this.originalTemplate.slice(attr.valueSpan.start.offset, attr.valueSpan.end.offset);
                const staticMatch = parseStaticObjectLiteral(expr);
                if (staticMatch === null) {
                    if (config.bestEffortMode && !isObjectLiteralSyntax(expr.trim())) {
                        const keyReplacement = this.originalTemplate
                            .slice(attr.sourceSpan.start.offset, attr.valueSpan.start.offset)
                            .replace('[ngStyle]', '[style]');
                        this.replacements.push({
                            start: attr.sourceSpan.start.offset,
                            end: attr.valueSpan.start.offset,
                            replacement: keyReplacement,
                        });
                    }
                    continue;
                }
                let replacement;
                if (staticMatch.length === 0) {
                    replacement = '';
                }
                else if (staticMatch.length === 1) {
                    const { key, value } = staticMatch[0];
                    // Special case: If the key is an empty string, use [style]=""
                    if (key === '') {
                        replacement = '';
                    }
                    else {
                        // Normal single condition: use [style.styleName]="condition"
                        replacement = `[style.${key}]="${value}"`;
                    }
                }
                else {
                    replacement = `[style]="${expr}"`;
                }
                this.replacements.push({
                    start: attr.sourceSpan.start.offset,
                    end: attr.sourceSpan.end.offset,
                    replacement,
                });
                continue;
            }
            if (attr.name === 'ngStyle' && attr.value) {
                this.replacements.push({
                    start: attr.sourceSpan.start.offset,
                    end: attr.sourceSpan.end.offset,
                    replacement: `style="${attr.value}"`,
                });
            }
        }
        return super.visitElement(element, config);
    }
}
function parseStaticObjectLiteral(expr) {
    const trimmedExpr = expr.trim();
    if (trimmedExpr === '{}' || trimmedExpr === '[]') {
        return [];
    }
    if (!isObjectLiteralSyntax(trimmedExpr)) {
        return null;
    }
    const objectLiteral = parseAsObjectLiteral(trimmedExpr);
    if (!objectLiteral) {
        return null;
    }
    return extractStyleBindings(objectLiteral);
}
/**
 * Validates basic object literal syntax
 */
function isObjectLiteralSyntax(expr) {
    return expr.startsWith('{') && expr.endsWith('}');
}
/**
 * Parses expression as TypeScript object literal
 */
function parseAsObjectLiteral(expr) {
    const sourceFile = ts.createSourceFile('temp.ts', `const obj = ${expr}`, ts.ScriptTarget.Latest, true);
    const variableStatement = sourceFile.statements[0];
    if (!ts.isVariableStatement(variableStatement)) {
        return null;
    }
    const declaration = variableStatement.declarationList.declarations[0];
    if (!declaration.initializer || !ts.isObjectLiteralExpression(declaration.initializer)) {
        return null;
    }
    return declaration.initializer;
}
function extractStyleBindings(objectLiteral) {
    const result = [];
    for (const property of objectLiteral.properties) {
        if (ts.isShorthandPropertyAssignment(property)) {
            return null;
        }
        else if (ts.isPropertyAssignment(property)) {
            const keyText = extractPropertyKey(property.name);
            const valueText = extractPropertyValue(property.initializer);
            if (keyText === '' && valueText) {
                result.push({ key: '', value: valueText });
                continue;
            }
            if (!keyText || !valueText) {
                return null;
            }
            result.push({ key: keyText, value: valueText });
        }
        else {
            return null;
        }
    }
    return result;
}
/**
 * Extracts text from property key (name)
 */
function extractPropertyKey(name) {
    if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
        return name.text;
    }
    return null;
}
/**
 * Extracts text from property value
 */
function extractPropertyValue(initializer) {
    // String literals: 'value' or "value"
    if (ts.isStringLiteral(initializer)) {
        return `'${initializer.text}'`;
    }
    // Numeric literals: 42, 3.14
    if (ts.isNumericLiteral(initializer) || ts.isIdentifier(initializer)) {
        return initializer.text;
    }
    // Boolean and null keywords
    if (initializer.kind === ts.SyntaxKind.TrueKeyword) {
        return 'true';
    }
    if (initializer.kind === ts.SyntaxKind.FalseKeyword) {
        return 'false';
    }
    if (initializer.kind === ts.SyntaxKind.NullKeyword) {
        return 'null';
    }
    return initializer.getText();
}

class NgStyleMigration extends project_paths.TsurgeFunnelMigration {
    config;
    constructor(config = {}) {
        super();
        this.config = config;
    }
    processTemplate(template, node, file, info, typeChecker) {
        const { migrated, changed, replacementCount, canRemoveCommonModule } = migrateNgStyleBindings(template.content, this.config, node, typeChecker);
        if (!changed) {
            return null;
        }
        const fileToMigrate = template.inline
            ? file
            : project_paths.projectFile(template.filePath, info);
        const end = template.start + template.content.length;
        return {
            replacements: [prepareTextReplacement(fileToMigrate, migrated, template.start, end)],
            replacementCount,
            canRemoveCommonModule,
        };
    }
    async analyze(info) {
        const { sourceFiles, program } = info;
        const typeChecker = program.getTypeChecker();
        const ngStyleReplacements = [];
        const filesWithNgStyleDeclarations = new Set();
        const filesToRemoveCommonModule = new Set();
        for (const sf of sourceFiles) {
            ts.forEachChild(sf, (node) => {
                if (!ts.isClassDeclaration(node)) {
                    return;
                }
                const file = project_paths.projectFile(sf, info);
                if (this.config.shouldMigrate && !this.config.shouldMigrate(file)) {
                    return;
                }
                const templateVisitor = new ng_component_template.NgComponentTemplateVisitor(typeChecker);
                templateVisitor.visitNode(node);
                const replacementsForStyle = [];
                let replacementCountForStyle = 0;
                let canRemoveCommonModuleForFile = true;
                for (const template of templateVisitor.resolvedTemplates) {
                    const result = this.processTemplate(template, node, file, info, typeChecker);
                    if (result) {
                        replacementsForStyle.push(...result.replacements);
                        replacementCountForStyle += result.replacementCount;
                        if (!result.canRemoveCommonModule) {
                            canRemoveCommonModuleForFile = false;
                        }
                    }
                }
                if (replacementsForStyle.length > 0) {
                    if (canRemoveCommonModuleForFile) {
                        filesToRemoveCommonModule.add(file.id);
                    }
                    // Handle the `@Component({ imports: [...] })` array.
                    const importsRemoval = createNgStyleImportsArrayRemoval(node, file, typeChecker, canRemoveCommonModuleForFile);
                    if (importsRemoval) {
                        replacementsForStyle.push(importsRemoval);
                    }
                    ngStyleReplacements.push({
                        file,
                        replacementCount: replacementCountForStyle,
                        replacements: replacementsForStyle,
                    });
                    filesWithNgStyleDeclarations.add(sf);
                }
            });
        }
        const importReplacements = calculateImportReplacements(info, filesWithNgStyleDeclarations, filesToRemoveCommonModule);
        return project_paths.confirmAsSerializable({
            ngStyleReplacements,
            importReplacements,
        });
    }
    async combine(unitA, unitB) {
        const importReplacements = {};
        for (const unit of [unitA, unitB]) {
            for (const fileIDStr of Object.keys(unit.importReplacements)) {
                const fileID = fileIDStr;
                importReplacements[fileID] = unit.importReplacements[fileID];
            }
        }
        return project_paths.confirmAsSerializable({
            ngStyleReplacements: [...unitA.ngStyleReplacements, ...unitB.ngStyleReplacements],
            importReplacements,
        });
    }
    async globalMeta(combinedData) {
        return project_paths.confirmAsSerializable({
            ngStyleReplacements: combinedData.ngStyleReplacements,
            importReplacements: combinedData.importReplacements,
        });
    }
    async stats(globalMetadata) {
        const touchedFilesCount = globalMetadata.ngStyleReplacements.length;
        const replacementCount = globalMetadata.ngStyleReplacements.reduce((acc, cur) => acc + cur.replacementCount, 0);
        return project_paths.confirmAsSerializable({
            touchedFilesCount,
            replacementCount,
        });
    }
    async migrate(globalData) {
        const replacements = [];
        replacements.push(...globalData.ngStyleReplacements.flatMap(({ replacements }) => replacements));
        for (const fileIDStr of Object.keys(globalData.importReplacements)) {
            const fileID = fileIDStr;
            const importReplacements = globalData.importReplacements[fileID];
            replacements.push(...importReplacements.addAndRemove);
        }
        return { replacements };
    }
}
function prepareTextReplacement(file, replacement, start, end) {
    return new project_paths.Replacement(file, new project_paths.TextUpdate({
        position: start,
        end: end,
        toInsert: replacement,
    }));
}

function migrate(options) {
    return async (tree, context) => {
        await project_paths.runMigrationInDevkit({
            tree,
            getMigration: (fs) => new NgStyleMigration({
                bestEffortMode: options.bestEffortMode,
                shouldMigrate: (file) => {
                    return (file.rootRelativePath.startsWith(fs.normalize(options.path)) &&
                        !/(^|\/)node_modules\//.test(file.rootRelativePath));
                },
            }),
            beforeProgramCreation: (tsconfigPath, stage) => {
                if (stage === project_paths.MigrationStage.Analysis) {
                    context.logger.info(`Preparing analysis for: ${tsconfigPath}...`);
                }
                else {
                    context.logger.info(`Running migration for: ${tsconfigPath}...`);
                }
            },
            beforeUnitAnalysis: (tsconfigPath) => {
                context.logger.info(`Scanning for component tags: ${tsconfigPath}...`);
            },
            afterAllAnalyzed: () => {
                context.logger.info(``);
                context.logger.info(`Processing analysis data between targets...`);
                context.logger.info(``);
            },
            afterAnalysisFailure: () => {
                context.logger.error('Migration failed unexpectedly with no analysis data');
            },
            whenDone: ({ touchedFilesCount, replacementCount, }) => {
                context.logger.info('');
                context.logger.info(`Successfully migrated to style bindings from ngStyle 🎉`);
                context.logger.info(`  -> Migrated ${replacementCount} ngStyle to style bindings in ${touchedFilesCount} files.`);
            },
        });
    };
}

exports.migrate = migrate;
