"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
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 = require("../constants");
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 decorators_1 = require("../common/decorators");
const semver = require("semver/preload");
class ProjectConfigService {
    constructor($fs, $logger, $injector, $options, $cleanupService) {
        this.$fs = $fs;
        this.$logger = $logger;
        this.$injector = $injector;
        this.$options = $options;
        this.$cleanupService = $cleanupService;
        this.forceUsingNewConfig = false;
        this.forceUsingLegacyConfig = false;
    }
    setForceUsingNewConfig(force) {
        return (this.forceUsingNewConfig = force);
    }
    setForceUsingLegacyConfig(force) {
        return (this.forceUsingLegacyConfig = force);
    }
    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();
    }
    warnUsingLegacyNSConfig() {
        const isMigrate = _.get(this.$options, "argv._[0]") === "migrate";
        if (isMigrate) {
            return;
        }
        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.`);
    }
    detectProjectConfigs(projectDir) {
        const JSConfigPath = path.join(projectDir || this.projectHelper.projectDir, constants_1.CONFIG_FILE_NAME_JS);
        const TSConfigPath = path.join(projectDir || this.projectHelper.projectDir, constants_1.CONFIG_FILE_NAME_TS);
        const NSConfigPath = path.join(projectDir || this.projectHelper.projectDir, constants_1.CONFIG_NS_FILE_NAME);
        const hasTSConfig = this.$fs.exists(TSConfigPath);
        const hasJSConfig = this.$fs.exists(JSConfigPath);
        const hasNSConfig = this.$fs.exists(NSConfigPath);
        const usingNSConfig = !(hasTSConfig || hasJSConfig);
        if (hasTSConfig && hasJSConfig) {
            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 {
            hasTSConfig,
            hasJSConfig,
            hasNSConfig,
            usingNSConfig,
            TSConfigPath,
            JSConfigPath,
            NSConfigPath,
        };
    }
    readConfig(projectDir) {
        const info = this.detectProjectConfigs(projectDir);
        if (this.forceUsingLegacyConfig ||
            (info.usingNSConfig && !this.forceUsingNewConfig)) {
            this.$logger.trace("Project Config Service using legacy configuration...");
            if (!this.forceUsingLegacyConfig) {
                this.warnUsingLegacyNSConfig();
            }
            return this.fallbackToLegacyNSConfig(info);
        }
        let config;
        if (info.hasTSConfig) {
            const rawSource = this.$fs.readText(info.TSConfigPath);
            const transpiledSource = ts.transpileModule(rawSource, {
                compilerOptions: { module: ts.ModuleKind.CommonJS },
            });
            const result = this.requireFromString(transpiledSource.outputText, info.TSConfigPath);
            config = result["default"] ? result["default"] : result;
        }
        else if (info.hasJSConfig) {
            const rawSource = this.$fs.readText(info.JSConfigPath);
            config = this.requireFromString(rawSource, info.JSConfigPath);
        }
        return config;
    }
    getValue(key) {
        return _.get(this.readConfig(), key);
    }
    setValue(key, value) {
        return __awaiter(this, void 0, void 0, function* () {
            const { hasTSConfig, hasNSConfig, TSConfigPath, JSConfigPath, usingNSConfig, NSConfigPath, } = this.detectProjectConfigs();
            const configFilePath = TSConfigPath || JSConfigPath;
            if (this.forceUsingLegacyConfig ||
                (usingNSConfig && !this.forceUsingNewConfig)) {
                try {
                    this.$logger.trace("Project Config Service -> setValue writing to legacy config.");
                    const NSConfig = hasNSConfig ? this.$fs.readJson(NSConfigPath) : {};
                    _.set(NSConfig, key, value);
                    this.$fs.writeJson(NSConfigPath, NSConfig);
                    return true;
                }
                catch (error) {
                    this.$logger.trace(`Failed to setValue on legacy config. Error is ${error.message}`, error);
                    return false;
                }
            }
            if (!this.$fs.exists(configFilePath)) {
                this.writeDefaultConfig(this.projectHelper.projectDir);
            }
            if (typeof value === "object") {
                let allSuccessful = true;
                for (const prop of this.flattenObjectToPaths(value)) {
                    if (!(yield 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 ${hasTSConfig ? constants_1.CONFIG_FILE_NAME_TS : constants_1.CONFIG_FILE_NAME_JS}.${os_1.EOL}`);
                    this.$logger.printMarkdown(`Please manually update \`${hasTSConfig ? 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 { TSConfigPath } = this.detectProjectConfigs(projectDir);
        if (this.$fs.exists(TSConfigPath)) {
            return false;
        }
        this.$fs.writeFile(TSConfigPath, this.getDefaultTSConfig(appId));
        return TSConfigPath;
    }
    fallbackToLegacyNSConfig(info) {
        const additionalData = [];
        const NSConfig = info.hasNSConfig
            ? this.$fs.readJson(info.NSConfigPath)
            : {};
        try {
            const projectData = this.$injector.resolve("projectData");
            const embeddedPackageJsonPath = path.resolve(this.projectHelper.projectDir, projectData.getAppDirectoryRelativePath(), constants.PACKAGE_JSON_FILE_NAME);
            const embeddedPackageJson = this.$fs.readJson(embeddedPackageJsonPath);
            additionalData.push(_.pick(embeddedPackageJson, [
                "android",
                "ios",
                "profiling",
                "cssParser",
                "discardUncaughtJsExceptions",
                "main",
            ]));
        }
        catch (err) {
            this.$logger.trace("failed to add embedded package.json data to config", err);
        }
        const packageJson = this.$fs.readJson(path.join(this.projectHelper.projectDir, "package.json"));
        if (!NSConfig.id &&
            packageJson &&
            packageJson.nativescript &&
            packageJson.nativescript.id) {
            const ids = packageJson.nativescript.id;
            if (typeof ids === "string") {
                additionalData.push({
                    id: packageJson.nativescript.id,
                });
            }
            else if (typeof ids === "object") {
                for (const platform of Object.keys(ids)) {
                    additionalData.push({
                        [platform]: {
                            id: packageJson.nativescript.id[platform],
                        },
                    });
                }
            }
        }
        return _.defaultsDeep({}, ...additionalData, NSConfig);
    }
    writeLegacyNSConfigIfNeeded(projectDir, runtimePackage) {
        return __awaiter(this, void 0, void 0, function* () {
            const { usingNSConfig } = this.detectProjectConfigs(projectDir);
            if (usingNSConfig) {
                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"),
            });
            yield this.$cleanupService.addCleanupDeleteAction(nsConfigPath);
        });
    }
    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],
                },
            ];
        }, []);
    }
}
__decorate([
    decorators_1.cache()
], ProjectConfigService.prototype, "warnUsingLegacyNSConfig", null);
__decorate([
    decorators_1.exported("projectConfigService")
], ProjectConfigService.prototype, "readConfig", null);
__decorate([
    decorators_1.exported("projectConfigService")
], ProjectConfigService.prototype, "getValue", null);
__decorate([
    decorators_1.exported("projectConfigService")
], ProjectConfigService.prototype, "setValue", null);
exports.ProjectConfigService = ProjectConfigService;
yok_1.injector.register("projectConfigService", ProjectConfigService);
