"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transform = exports.populateEjsData = exports.diffAndWriteFiles = exports.diffFiles = exports.mergeEnv = exports.merge = exports.transformFilename = void 0;
const chalk_1 = __importDefault(require("chalk"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const glob_1 = __importDefault(require("glob"));
const path_1 = __importStar(require("path"));
const dotenv_1 = require("dotenv");
const ejs_1 = require("ejs");
const inquirer_1 = __importDefault(require("inquirer"));
const helpers_1 = require("../utils/helpers");
const diff_1 = require("diff");
const { version } = require('../../../package.json');
const FILE_FOR_COPY_REGEXP = /(index\.html)$|\.(gif|jpg|jpeg|tiff|png|svg|ashx|ico|pdf|jar|eot|woff|ttf|woff2)$/;
const transformFilename = (file, args) => {
    // eslint-disable-next-line guard-for-in
    for (const key in args) {
        const value = args[key];
        if (typeof value !== 'string')
            continue;
        file = file.replace(`{{${key}}}`, value);
    }
    return file;
};
exports.transformFilename = transformFilename;
const merge = (targetObj, sourceObj) => {
    const mergeObject = (target, source) => {
        for (const key of Object.keys(source)) {
            const sourceVal = source[key];
            const targetVal = target[key];
            if (Array.isArray(targetVal) && Array.isArray(sourceVal)) {
                // use Set to remove duplicates from arrays
                target[key] = [...new Set([...targetVal, ...sourceVal])];
            }
            else if (!Array.isArray(targetVal) &&
                !Array.isArray(sourceVal) &&
                typeof targetVal === 'object' &&
                typeof sourceVal === 'object') {
                target[key] = (0, helpers_1.sortKeys)(mergeObject(targetVal, sourceVal));
            }
            else {
                target[key] = sourceVal;
            }
        }
        return target;
    };
    return mergeObject(targetObj, sourceObj);
};
exports.merge = merge;
const mergeEnv = (targetContent, sourceContent) => {
    const sourceEnv = (0, dotenv_1.parse)(sourceContent);
    if (!sourceEnv || !Object.keys(sourceEnv).length)
        return targetContent;
    const targetEnv = (0, dotenv_1.parse)(targetContent);
    const sourceLines = sourceContent.split('\r\n');
    Object.keys(sourceEnv).forEach((sourceEnvVar) => {
        const isDuplicate = Object.keys(targetEnv).includes(sourceEnvVar);
        if (!isDuplicate)
            return;
        // Overriding value in a target .env file
        targetContent = targetContent.replace(new RegExp(`^${sourceEnvVar}=.*$`, 'gm'), `${sourceEnvVar}=${sourceEnv[sourceEnvVar]}`);
        // Removing duplicate variable and related comments in a source .env file,
        // since source and target will be concatenated
        let lineIndex = sourceLines.findIndex((line) => line.startsWith(sourceEnvVar));
        // remove empty lines after the definition
        while (sourceLines[lineIndex + 1] === '') {
            sourceLines.splice(lineIndex + 1, 1);
        }
        // remove definition and comments
        do {
            sourceLines.splice(lineIndex, 1);
            lineIndex--;
        } while (lineIndex >= 0 && sourceLines[lineIndex].startsWith('#'));
    });
    sourceContent = sourceLines.join('\r\n');
    // No variables left in a source
    if (!sourceContent)
        return targetContent;
    // NOTE we are enforcing CRLF for the repo in .gitattributes, so match it here
    const eol = '\r\n';
    return targetContent + eol + sourceContent;
};
exports.mergeEnv = mergeEnv;
/**
 * @param {string} sourceFileContent transformed version of our template
 * @param {string} targetFilePath user's file path
 */
const diffFiles = (sourceFileContent, targetFilePath) => __awaiter(void 0, void 0, void 0, function* () {
    if (!fs_extra_1.default.pathExistsSync(targetFilePath))
        return '';
    const targetFileContents = fs_extra_1.default.readFileSync(path_1.default.resolve(process.cwd(), targetFilePath), 'utf8');
    if (targetFileContents === sourceFileContent)
        return '';
    const diff = targetFilePath.endsWith('.json')
        ? (0, diff_1.diffJson)(JSON.parse(targetFileContents), JSON.parse(sourceFileContent))
        : (0, diff_1.diffLines)(targetFileContents, sourceFileContent);
    diff.forEach((change) => {
        const color = change.added ? chalk_1.default.green : change.removed ? chalk_1.default.red : chalk_1.default.gray;
        const prefix = change.added ? '+' : change.removed ? '-' : '=';
        change.value.split('\n').forEach((value) => {
            if (!value)
                return;
            console.log(color(`${prefix} ${value}`));
        });
    });
    // TODO - enhancement: Write 'pagination' function that prints off
    // only x lines and prints remaining x lines on user input.
    // allow user to move forward and back like when piping to more in bash
    // examples of more: https://shapeshed.com/unix-more/#what-is-the-more-command-in-unix
    console.log(`Showing potential changes in ${chalk_1.default.yellow(targetFilePath.replace('/', '\\'))}`);
    const answer = yield inquirer_1.default.prompt({
        type: 'list',
        name: 'choice',
        choices: ['yes', 'skip', 'yes to all', 'abort'],
        message: `File ${chalk_1.default.yellow(targetFilePath.replace('/', '\\'))} is about to be overwritten with the above changes. Are you sure you want to continue?`,
    });
    return answer.choice;
});
exports.diffFiles = diffFiles;
const diffAndWriteFiles = (_a) => __awaiter(void 0, [_a], void 0, function* ({ rendered, pathToNewFile, answers, }) {
    const targetFilePath = (0, exports.transformFilename)(pathToNewFile, answers);
    const choice = yield (0, exports.diffFiles)(rendered, targetFilePath);
    switch (choice) {
        case 'yes':
            (0, helpers_1.writeFileToPath)(targetFilePath, rendered);
            return;
        case 'yes to all':
            // set force to true so diff is not run again
            answers.force = true;
            (0, helpers_1.writeFileToPath)(targetFilePath, rendered);
            return;
        case 'skip':
            return;
        case 'abort':
            console.log(chalk_1.default.yellow('Goodbye!'));
            return process.exit();
        // eslint-disable no-fallthrough
        case '':
            // writeFile to default case so that when an initializer is
            // run for the first time, it will still copy files that
            // do not return a diff.
            (0, helpers_1.writeFileToPath)(targetFilePath, rendered);
            return;
        default:
            return;
    }
});
exports.diffAndWriteFiles = diffAndWriteFiles;
const populateEjsData = (answers, destination) => {
    // pass in helper to answers object
    // Use exact version for jss dependencies in beta and canary versions
    const jssVersion = version.match(/(\-[a-zA-Z]+\.\d+)$/) ? version : `~${version}`;
    const ejsData = Object.assign(Object.assign({}, answers), { version: jssVersion, helper: {
            isDev: (0, helpers_1.isDevEnvironment)(destination || answers.destination),
            getPascalCaseName: helpers_1.getPascalCaseName,
            getAppPrefix: helpers_1.getAppPrefix,
        } });
    return ejsData;
};
exports.populateEjsData = populateEjsData;
/**
 * Handles each template file and applies ejs renderer, also:
 * - Determines files for copy.
 * - Determines files for skip.
 * if some files already exist:
 *   - merges package.json files
 *   - concatenates .env files
 *   - compares diffs
 * @param {string} templatePath path to the template
 * @param {BaseArgs} answers CLI arguments
 * @param {TransformOptions} options custom options
 */
const transform = (templatePath_1, answers_1, ...args_1) => __awaiter(void 0, [templatePath_1, answers_1, ...args_1], void 0, function* (templatePath, answers, options = {}) {
    const { isFileForCopy, isFileForSkip, fileForCopyRegExp = FILE_FOR_COPY_REGEXP } = options;
    const destinationPath = path_1.default.resolve(answers.destination);
    if (!answers.appPrefix) {
        answers.appPrefix = false;
    }
    const ejsData = (0, exports.populateEjsData)(answers);
    // the templates to be run through ejs render or copied directly
    const files = glob_1.default.sync('**/*', { cwd: templatePath, dot: true, nodir: true });
    for (const file of files) {
        try {
            let pathToNewFile = `${destinationPath}${path_1.sep}${file}`;
            const pathToTemplate = path_1.default.join(templatePath, file);
            // Rename gitignore after the fact to prevent npm from renaming it to .npmignore
            // See: https://github.com/npm/npm/issues/1862
            if (!file.endsWith('.gitignore') && file.endsWith('gitignore')) {
                pathToNewFile = pathToNewFile.replace(/\gitignore$/, '.gitignore');
            }
            if (isFileForSkip && isFileForSkip(file)) {
                continue;
            }
            // holds the content to be written to the new file
            let str;
            // if the directory doesn't exist, create it
            fs_extra_1.default.mkdirsSync(path_1.default.dirname((0, exports.transformFilename)(pathToNewFile, answers)));
            if (isFileForCopy ? isFileForCopy(file, fileForCopyRegExp) : file.match(fileForCopyRegExp)) {
                // pdfs may have <% encoded, which throws an error for ejs.
                // we simply want to copy file if it's a pdf, not render it with ejs.
                fs_extra_1.default.copySync(pathToTemplate, pathToNewFile);
                continue;
            }
            if (file.endsWith('.json') && fs_extra_1.default.existsSync(pathToNewFile)) {
                // we treat a .json a bit differently
                // read the current .json and the template .json (rendered with ejs)
                const currentJson = (0, helpers_1.openJsonFile)(pathToNewFile);
                const templateJson = JSON.parse(yield (0, ejs_1.renderFile)(path_1.default.resolve(pathToTemplate), ejsData));
                // merge them and set the result to str which will then go through diff
                const merged = (0, exports.merge)(currentJson, templateJson);
                str = JSON.stringify(merged, null, 2);
            }
            if (file.endsWith('.env') && fs_extra_1.default.existsSync(pathToNewFile)) {
                // we treat .env files a bit differently
                // read the current .env and the template .env (rendered with ejs)
                const currentDotEnv = fs_extra_1.default.readFileSync(path_1.default.resolve(process.cwd(), pathToNewFile), 'utf8');
                const templateDotEnv = yield (0, ejs_1.renderFile)(path_1.default.resolve(pathToTemplate), ejsData);
                // merge them and set the result to str which will then go through diff
                str = (0, exports.mergeEnv)(currentDotEnv, templateDotEnv);
            }
            str = str !== null && str !== void 0 ? str : (yield (0, ejs_1.renderFile)(path_1.default.resolve(pathToTemplate), ejsData));
            if (!answers.force) {
                yield (0, exports.diffAndWriteFiles)({
                    rendered: str,
                    pathToNewFile,
                    answers,
                });
            }
            else {
                (0, helpers_1.writeFileToPath)((0, exports.transformFilename)(pathToNewFile, answers), str);
            }
        }
        catch (error) {
            console.log(chalk_1.default.red(error));
            console.log(`Error occurred when trying to render to ${chalk_1.default.yellow(path_1.default.resolve(file))}`);
        }
    }
});
exports.transform = transform;
