"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
const tsUtils_1 = require("./tsUtils");
const util_1 = require("./util");
// tslint:disable:arrow-parens
class UpdateChanges {
    /**
     * Create a new base schematic to apply changes
     * @param rootPath Root folder for the schematic to read configs, pass __dirname
     */
    constructor(rootPath, host, context) {
        this.rootPath = rootPath;
        this.host = host;
        this.context = context;
        this._templateFiles = [];
        this._tsFiles = [];
        this.sourcePaths = util_1.getProjectPaths(util_1.getWorkspace(host));
        const selectorJson = path.join(this.rootPath, 'changes', 'selectors.json');
        if (fs.existsSync(selectorJson)) {
            this.selectorChanges = JSON.parse(fs.readFileSync(selectorJson, 'utf-8'));
        }
        const classJson = path.join(this.rootPath, 'changes', 'classes.json');
        if (fs.existsSync(classJson)) {
            this.classChanges = JSON.parse(fs.readFileSync(classJson, 'utf-8'));
        }
        const outputsJson = path.join(this.rootPath, 'changes', 'outputs.json');
        if (fs.existsSync(outputsJson)) {
            this.outputChanges = JSON.parse(fs.readFileSync(outputsJson, 'utf-8'));
        }
    }
    get templateFiles() {
        if (!this._templateFiles.length) {
            // https://github.com/angular/devkit/blob/master/packages/angular_devkit/schematics/src/tree/filesystem.ts
            this.sourceDirsVisitor((fulPath, entry) => {
                if (fulPath.endsWith('component.html')) {
                    this._templateFiles.push(entry.path);
                }
            });
        }
        return this._templateFiles;
    }
    get tsFiles() {
        if (!this._tsFiles.length) {
            this.sourceDirsVisitor((fulPath, entry) => {
                if (fulPath.endsWith('.ts')) {
                    this._tsFiles.push(entry.path);
                }
            });
        }
        return this._tsFiles;
    }
    /** Apply configured changes to the Host Tree */
    applyChanges() {
        if (this.selectorChanges && this.selectorChanges.changes.length) {
            for (const entryPath of this.templateFiles) {
                this.updateSelectors(entryPath);
            }
        }
        if (this.outputChanges && this.outputChanges.changes.length) {
            for (const entryPath of this.templateFiles) {
                this.updateOutputs(entryPath);
            }
        }
        /** TS files */
        if (this.classChanges && this.classChanges.changes.length) {
            for (const entryPath of this.tsFiles) {
                this.updateClasses(entryPath);
            }
        }
    }
    updateSelectors(entryPath) {
        let fileContent = this.host.read(entryPath).toString();
        let overwrite = false;
        for (const change of this.selectorChanges.changes) {
            let searchPttrn = change.type === 'component' ? '<' : '';
            searchPttrn += change.selector;
            if (fileContent.indexOf(searchPttrn) !== -1) {
                fileContent = this.applySelectorChange(fileContent, change);
                overwrite = true;
            }
        }
        if (overwrite) {
            this.host.overwrite(entryPath, fileContent);
        }
    }
    applySelectorChange(fileContent, change) {
        let regSource;
        let replace;
        switch (change.type) {
            case 'component':
                if (change.remove) {
                    regSource = String.raw `\<${change.selector}[\s\S]*?\<\/${change.selector}\>`;
                    replace = '';
                }
                else {
                    regSource = String.raw `\<(\/?)${change.selector}`;
                    replace = `<$1${change.replaceWith}`;
                }
                break;
            case 'directive':
                if (change.remove) {
                    // Group match (\2) as variable as it looks like octal escape (error in strict)
                    regSource = String.raw `\s*?\[?${change.selector}\]?(=(["']).*?${'\\2'}(?=\s|\>))?`;
                    replace = '';
                }
                else {
                    regSource = change.selector;
                    replace = change.replaceWith;
                }
                break;
            default:
                break;
        }
        fileContent = fileContent.replace(new RegExp(regSource, 'g'), replace);
        return fileContent;
    }
    updateClasses(entryPath) {
        let fileContent = this.host.read(entryPath).toString();
        let overwrite = false;
        for (const change of this.classChanges.changes) {
            if (fileContent.indexOf(change.name) !== -1) {
                const positions = tsUtils_1.getIdentifierPositions(fileContent, change.name);
                // loop backwards to preserve positions
                for (let i = positions.length; i--;) {
                    const pos = positions[i];
                    fileContent = fileContent.slice(0, pos.start) + change.replaceWith + fileContent.slice(pos.end);
                }
                overwrite = true;
            }
        }
        if (overwrite) {
            this.host.overwrite(entryPath, fileContent);
        }
    }
    updateOutputs(entryPath) {
        let fileContent = this.host.read(entryPath).toString();
        let overwrite = false;
        for (const change of this.outputChanges.changes) {
            if (fileContent.indexOf(change.owner.selector) !== -1 && fileContent.indexOf(change.name) !== -1) {
                let reg = new RegExp(String.raw `\(${change.name}\)`, 'g');
                let replace = `(${change.replaceWith})`;
                let searchPattern;
                if (change.remove) {
                    // Group match (\1) as variable as it looks like octal escape (error in strict)
                    reg = new RegExp(String.raw `\s*\(${change.name}\)=(["']).*?${'\\1'}(?=\s|\>)`, 'g');
                    replace = '';
                }
                switch (change.owner.type) {
                    case 'component':
                        searchPattern = String.raw `\<${change.owner.selector}[^\>]*\>`;
                        break;
                    case 'directive':
                        searchPattern = String.raw `\<[^\>]*[\s\[]${change.owner.selector}[^\>]*\>`;
                        break;
                }
                const matches = fileContent.match(new RegExp(searchPattern, 'g'));
                for (const match of matches) {
                    fileContent = fileContent.replace(match, match.replace(reg, replace));
                }
                overwrite = true;
            }
        }
        if (overwrite) {
            this.host.overwrite(entryPath, fileContent);
        }
    }
    sourceDirsVisitor(visitor) {
        for (const sourcePath of this.sourcePaths) {
            const srcDir = this.host.getDir(sourcePath);
            srcDir.visit(visitor);
        }
    }
}
exports.UpdateChanges = UpdateChanges;
