'use strict';

var path = require('path');
var ts = require('typescript');
var utils_ast = require('./ast.js');
var utils_typescript = require('./typescript.js');

/**
 * Update separate html file
 * @param tree
 * @param filePaths
 * @param context
 * @param migrationData
 */
async function migrateTemplate(tree, filePaths, context, migrationData) {
    for (const templatePath of filePaths) {
        const parsedFilePath = `.${templatePath}`;
        let res = Promise.resolve({
            fileContent: '',
            changed: false,
            errors: []
        });
        const template = tree.read(parsedFilePath)?.toString();
        if (!template) {
            context.logger.error(`Cannot read "${parsedFilePath}" because it does not exist.`);
            continue;
        }
        res = transformTemplateAttributes(template, parsedFilePath, migrationData);
        const { fileContent, changed, errors } = await res;
        if (changed) {
            tree.overwrite(templatePath, fileContent);
        }
        if (errors.length > 0) {
            context.logger.error(errors.map(({ error }) => error.toString()).join('\n'));
        }
    }
}
/**
 * Update typescript file if classes with @Component decorator and `template` property exists
 * @param tree
 * @param sourceFiles
 * @param basePath
 * @param context
 * @param migrationData
 */
async function migrateTs(tree, sourceFiles, basePath, context, migrationData) {
    // run and track changed components
    const analysis = new Map();
    for (const sourceFile of sourceFiles) {
        utils_typescript.forEachClass(sourceFile, (node) => {
            analyzeDecorators(node, sourceFile, analysis);
        });
    }
    // update source file
    for (const path$1 of analysis.keys()) {
        const file = analysis.get(path$1);
        const ranges = file.getSortedRanges();
        const relativePath = path.relative(basePath, path$1);
        const content = tree.readText(relativePath);
        const update = tree.beginUpdate(relativePath);
        for (const { start, end } of ranges) {
            const template = content.slice(start, end);
            const length = (end ?? content.length) - start;
            const { fileContent, changed, errors } = await transformTemplateAttributes(template, relativePath, migrationData);
            if (changed) {
                update.remove(start, length);
                update.insertLeft(start, fileContent);
            }
            if (errors.length > 0) {
                context.logger.error(errors.map(({ error }) => error.toString()).join('\n'));
            }
        }
        tree.commitUpdate(update);
    }
}
/**
 * Analyzes the decorators of a given class declaration to find and save
 * inline `template` metadata within `@Component` decorators.
 */
function analyzeDecorators(node, sourceFile, analyzedFiles) {
    const decorator = ts.getDecorators(node)?.find((dec) => {
        return (ts.isCallExpression(dec.expression) &&
            ts.isIdentifier(dec.expression.expression) &&
            dec.expression?.expression?.text === 'Component');
    });
    const metadata = decorator &&
        decorator.expression.arguments.length > 0 &&
        ts.isObjectLiteralExpression(decorator.expression.arguments[0])
        ? decorator.expression.arguments[0]
        : null;
    // Exit early if there's no metadata or it's not an object literal
    if (!metadata) {
        return;
    }
    for (const prop of metadata.properties) {
        // All the properties we care about should have static
        // names and be initialized to a static string.
        if (!ts.isPropertyAssignment(prop) || (!ts.isIdentifier(prop.name) && !ts.isStringLiteralLike(prop.name))) {
            continue;
        }
        // check decorator 'template' property, check for text content parse
        if (prop.name.text === 'template' && ts.isStringLiteralLike(prop.initializer) && prop.initializer.text) {
            // +1/-1 to exclude the opening/closing characters from the range.
            utils_typescript.AnalyzedFile.addRange(sourceFile.fileName, sourceFile, analyzedFiles, {
                start: prop.initializer.getStart(sourceFile) + 1,
                end: prop.initializer.getEnd() - 1,
                node: prop,
                type: 'template',
                remove: true
            });
        }
    }
}
/**
 * Migrates attribute values in an Angular template based on predefined cases.
 */
async function transformTemplateAttributes(template, fileName, migrationData) {
    const parsed = await utils_typescript.parseTemplate(template);
    let updatedTemplate = template;
    // extra text offset as result of text replacement;
    let offset = 0;
    if (parsed.tree !== undefined) {
        const visitor = new utils_typescript.ElementCollector(migrationData, fileName);
        utils_ast.visitAll(visitor, parsed.tree.rootNodes);
        for (const el of visitor.elementsToMigrate) {
            const migrationAttr = el.attrs.find((attr) => utils_ast.getSimpleAttributeName(attr.name) === migrationData.attrs.key.from);
            let updatedAttrValue;
            if (migrationAttr) {
                const cleanAttrValue = utils_ast.getSimpleAttributeValue(migrationAttr.value);
                const replacement = migrationData.attrs.value.replacements.find(({ from }) => from === cleanAttrValue);
                if (replacement) {
                    updatedAttrValue = replacement.to;
                }
                else {
                    console.warn(`Element is using dynamic value. Check the code and change value on your own.${(fileName && ' File: ' + fileName) || ''}`);
                    continue;
                }
                updatedTemplate =
                    updatedTemplate.slice(0, migrationAttr.keySpan.start.offset - offset) +
                        `${migrationData.attrs.key.to}="${updatedAttrValue}"` +
                        updatedTemplate.slice(migrationAttr.valueSpan.end.offset + 1 - offset, updatedTemplate.length);
                offset += migrationAttr.name.length - migrationData.attrs.key.to.length;
                offset += migrationAttr.value.length - updatedAttrValue.length;
            }
        }
        return { fileContent: updatedTemplate, changed: visitor.elementsToMigrate.length > 0, errors: parsed.errors };
    }
    return { fileContent: updatedTemplate, changed: undefined, errors: parsed.errors };
}

exports.analyzeDecorators = analyzeDecorators;
exports.migrateTemplate = migrateTemplate;
exports.migrateTs = migrateTs;
