"use strict";
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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProjectConfigService = void 0;
const constants_1 = require("../constants");
const path = require("path");
const _ = require("lodash");
const ts = require("typescript");
const config_transformer_1 = require("../tools/config-manipulation/config-transformer");
const yok_1 = require("../common/yok");
const os_1 = require("os");
const prettier_1 = require("prettier");
const semver = require("semver/preload");
class ProjectConfigService {
    constructor($fs, $logger, $options, $injector) {
        this.$fs = $fs;
        this.$logger = $logger;
        this.$options = $options;
        this.$injector = $injector;
        this._hasWarnedLegacy = false;
        this._handledLegacyId = false;
    }
    requireFromString(src, filename) {
        const m = new module.constructor();
        m.paths = module.paths;
        m._compile(src, filename);
        return m.exports;
    }
    get projectHelper() {
        return this.$injector.resolve("projectHelper");
    }
    getDefaultTSConfig(appId = "org.nativescript.app") {
        return `import { NativeScriptConfig } from '@nativescript/core';

export default {
  id: '${appId}',
  appResourcesPath: 'App_Resources',
  android: {
    v8Flags: '--expose_gc',
    markingMode: 'none'
  }
} as NativeScriptConfig;`.trim();
    }
    detectInfo(projectDir) {
        const configJSFilePath = path.join(projectDir || this.projectHelper.projectDir, constants_1.CONFIG_FILE_NAME_JS);
        const configTSFilePath = path.join(projectDir || this.projectHelper.projectDir, constants_1.CONFIG_FILE_NAME_TS);
        const configNSConfigFilePath = path.join(projectDir || this.projectHelper.projectDir, constants_1.CONFIG_NS_FILE_NAME);
        const hasTS = this.$fs.exists(configTSFilePath);
        const hasJS = this.$fs.exists(configJSFilePath);
        let usesLegacyConfig = false;
        if (this.$fs.exists(configNSConfigFilePath) && !hasTS && !hasJS) {
            usesLegacyConfig = true;
            if (!this._hasWarnedLegacy) {
                this._hasWarnedLegacy = true;
                this.$logger.debug("the value of this.$options.argv._[0] is: " + this.$options.argv._[0]);
                const isMigrate = _.get(this.$options, "argv._[0]") === "migrate";
                if (!isMigrate) {
                    this.$logger.warn(`You are using the deprecated ${constants_1.CONFIG_NS_FILE_NAME} file. Just be aware that NativeScript 7 has an improved ${constants_1.CONFIG_FILE_NAME_DISPLAY} file for when you're ready to upgrade this project.`);
                }
            }
        }
        if (hasTS && hasJS) {
            this.$logger.warn(`You have both a ${constants_1.CONFIG_FILE_NAME_JS} and ${constants_1.CONFIG_FILE_NAME_TS} file. Defaulting to ${constants_1.CONFIG_FILE_NAME_TS}.`);
        }
        return {
            hasTS,
            hasJS,
            usesLegacyConfig,
            configJSFilePath,
            configTSFilePath,
            configNSConfigFilePath,
        };
    }
    readConfig(projectDir) {
        const { hasTS, hasJS, configJSFilePath, configTSFilePath, usesLegacyConfig, configNSConfigFilePath, } = this.detectInfo(projectDir);
        let config;
        if (usesLegacyConfig) {
            this.$logger.trace("Project Config Service: Using legacy nsconfig.json...");
            return this._readAndUpdateLegacyConfig(configNSConfigFilePath);
        }
        if (hasTS) {
            const rawSource = this.$fs.readText(configTSFilePath);
            const transpiledSource = ts.transpileModule(rawSource, {
                compilerOptions: { module: ts.ModuleKind.CommonJS },
            });
            const result = this.requireFromString(transpiledSource.outputText, configTSFilePath);
            config = result["default"] ? result["default"] : result;
        }
        else if (hasJS) {
            const rawSource = this.$fs.readText(configJSFilePath);
            config = this.requireFromString(rawSource, configJSFilePath);
        }
        return config;
    }
    getValue(key) {
        return _.get(this.readConfig(), key);
    }
    setValue(key, value) {
        return __awaiter(this, void 0, void 0, function* () {
            const { hasTS, configJSFilePath, configTSFilePath } = this.detectInfo();
            const configFilePath = configTSFilePath || configJSFilePath;
            if (!this.$fs.exists(configFilePath)) {
                this.writeDefaultConfig(this.projectHelper.projectDir);
            }
            if (typeof value === "object") {
                let allSuccessful = true;
                this.flattenObjectToPaths(value).forEach((prop) => {
                    if (!this.setValue(prop.key, prop.value)) {
                        allSuccessful = false;
                    }
                });
                return allSuccessful;
            }
            const configContent = this.$fs.readText(configFilePath);
            try {
                const transformer = new config_transformer_1.ConfigTransformer(configContent);
                const newContent = transformer.setValue(key, value);
                const prettierOptions = (yield prettier_1.resolveConfig(this.projectHelper.projectDir, { editorconfig: true })) || {
                    semi: false,
                    singleQuote: true,
                };
                this.$logger.trace("updating config, prettier options: ", prettierOptions);
                this.$fs.writeFile(configFilePath, prettier_1.format(newContent, Object.assign(Object.assign({}, prettierOptions), { parser: "typescript" })));
            }
            catch (error) {
                this.$logger.error(`Failed to update config.` + error);
            }
            finally {
                if (this.getValue(key) !== value) {
                    this.$logger.error(`${os_1.EOL}Failed to update ${hasTS ? constants_1.CONFIG_FILE_NAME_TS : constants_1.CONFIG_FILE_NAME_JS}.${os_1.EOL}`);
                    this.$logger.printMarkdown(`Please manually update \`${hasTS ? constants_1.CONFIG_FILE_NAME_TS : constants_1.CONFIG_FILE_NAME_JS}\` and set \`${key}\` to \`${value}\`.${os_1.EOL}`);
                    this.$fs.writeFile(configFilePath, configContent);
                    return false;
                }
                return true;
            }
        });
    }
    writeDefaultConfig(projectDir, appId) {
        const configTSFilePath = path.join(projectDir || this.projectHelper.projectDir, constants_1.CONFIG_FILE_NAME_TS);
        this.$fs.writeFile(configTSFilePath, this.getDefaultTSConfig(appId));
    }
    _readAndUpdateLegacyConfig(configNSConfigFilePath) {
        let nsConfig = this.$fs.readJson(configNSConfigFilePath);
        if (this._handledLegacyId) {
            return;
        }
        this._handledLegacyId = true;
        const packageJson = this.$fs.readJson(path.join(this.projectHelper.projectDir, "package.json"));
        if (packageJson &&
            packageJson.nativescript &&
            packageJson.nativescript.id) {
            nsConfig = Object.assign({ id: packageJson.nativescript.id }, nsConfig);
            this.$fs.writeJson(configNSConfigFilePath, nsConfig);
        }
        return nsConfig;
    }
    writeLegacyNSConfigIfNeeded(projectDir, runtimePackage) {
        const { usesLegacyConfig } = this.detectInfo(projectDir || this.projectHelper.projectDir);
        if (usesLegacyConfig) {
            return;
        }
        if (runtimePackage.version &&
            semver.gte(runtimePackage.version, "7.0.0-rc.5")) {
            return;
        }
        const runtimePackageDisplay = `${runtimePackage.name}${runtimePackage.version ? " v" + runtimePackage.version : ""}`;
        this.$logger.info();
        this.$logger.printMarkdown(`
Using __${runtimePackageDisplay}__ which requires \`nsconfig.json\` to be present.
Writing \`nsconfig.json\` based on the values set in \`${constants_1.CONFIG_FILE_NAME_DISPLAY}\`.
You may add \`nsconfig.json\` to \`.gitignore\` as the CLI will regenerate it as necessary.`);
        const nsConfigPath = path.join(projectDir || this.projectHelper.projectDir, "nsconfig.json");
        this.$fs.writeJson(nsConfigPath, {
            _info1: `Auto Generated for backwards compatibility with the currently used runtime.`,
            _info2: `Do not edit this file manually, as any changes will be ignored.`,
            _info3: `Config changes should be done in ${constants_1.CONFIG_FILE_NAME_DISPLAY} instead.`,
            appPath: this.getValue("appPath"),
            appResourcesPath: this.getValue("appResourcesPath"),
        });
    }
    flattenObjectToPaths(obj, basePath) {
        const toPath = (key) => [basePath, key].filter(Boolean).join(".");
        return Object.keys(obj).reduce((all, key) => {
            if (typeof obj[key] === "object") {
                return [...all, ...this.flattenObjectToPaths(obj[key], toPath(key))];
            }
            return [
                ...all,
                {
                    key: toPath(key),
                    value: obj[key],
                },
            ];
        }, []);
    }
}
exports.ProjectConfigService = ProjectConfigService;
yok_1.injector.register("projectConfigService", ProjectConfigService);
