"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.ProjectChangesService = void 0;
const path = require("path");
const constants_1 = require("../constants");
const helpers_1 = require("../common/helpers");
const _ = require("lodash");
const yok_1 = require("../common/yok");
const prepareInfoFileName = ".nsprepareinfo";
class ProjectChangesInfo {
    get hasChanges() {
        return (this.nativeChanged ||
            this.appResourcesChanged ||
            this.configChanged ||
            this.signingChanged);
    }
    get changesRequireBuild() {
        return this.appResourcesChanged || this.nativeChanged;
    }
    get changesRequirePrepare() {
        return this.appResourcesChanged || this.signingChanged;
    }
}
class ProjectChangesService {
    constructor($devicePlatformsConstants, $fs, $logger, $hooksService, $nodeModulesDependenciesBuilder) {
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$fs = $fs;
        this.$logger = $logger;
        this.$hooksService = $hooksService;
        this.$nodeModulesDependenciesBuilder = $nodeModulesDependenciesBuilder;
    }
    get currentChanges() {
        return this._changesInfo;
    }
    checkForChanges(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            this._changesInfo = new ProjectChangesInfo();
            const isNewPrepareInfo = yield this.ensurePrepareInfo(platformData, projectData, prepareData);
            if (!isNewPrepareInfo) {
                const platformResourcesDir = path.join(projectData.appResourcesDirectoryPath, platformData.normalizedPlatformName);
                this._changesInfo.appResourcesChanged = this.containsNewerFiles(platformResourcesDir, projectData);
                this.$nodeModulesDependenciesBuilder
                    .getProductionDependencies(projectData.projectDir, projectData.ignoredDependencies)
                    .filter((dep) => dep.nativescript &&
                    this.$fs.exists(path.join(dep.directory, constants_1.PLATFORMS_DIR_NAME, platformData.platformNameLowerCase)))
                    .forEach((dep) => {
                    this._changesInfo.nativeChanged =
                        this._changesInfo.nativeChanged ||
                            this.containsNewerFiles(path.join(dep.directory, constants_1.PLATFORMS_DIR_NAME, platformData.platformNameLowerCase), projectData) ||
                            this.isFileModified(path.join(dep.directory, constants_1.PACKAGE_JSON_FILE_NAME));
                });
                if (!this._changesInfo.nativeChanged) {
                    this._prepareInfo.projectFileHash = this.getProjectFileStrippedHash(projectData.projectDir, platformData);
                    this._changesInfo.nativeChanged = this.isProjectFileChanged(projectData.projectDir, platformData);
                }
                this._changesInfo.nsConfigChanged = this.filesChanged([
                    path.join(projectData.projectDir, constants_1.CONFIG_FILE_NAME_JS),
                    path.join(projectData.projectDir, constants_1.CONFIG_FILE_NAME_TS),
                    path.join(projectData.projectDir, constants_1.CONFIG_NS_FILE_NAME),
                ]);
                this._changesInfo.nativeChanged =
                    this._changesInfo.nativeChanged || this._changesInfo.nsConfigChanged;
                this.$logger.trace(`Set nativeChanged to ${this._changesInfo.nativeChanged}.`);
                if (platformData.platformNameLowerCase ===
                    this.$devicePlatformsConstants.iOS.toLowerCase()) {
                    this._changesInfo.configChanged = this.filesChanged([
                        path.join(platformResourcesDir, platformData.configurationFileName),
                        path.join(platformResourcesDir, "LaunchScreen.storyboard"),
                        path.join(platformResourcesDir, constants_1.BUILD_XCCONFIG_FILE_NAME),
                    ]);
                }
                else {
                    this._changesInfo.configChanged = this.filesChanged([
                        path.join(platformResourcesDir, platformData.configurationFileName),
                        path.join(platformResourcesDir, constants_1.APP_GRADLE_FILE_NAME),
                    ]);
                }
                this.$logger.trace(`Set value of configChanged to ${this._changesInfo.configChanged}`);
            }
            if (!prepareData.nativePrepare ||
                !prepareData.nativePrepare.skipNativePrepare) {
                yield platformData.platformProjectService.checkForChanges(this._changesInfo, prepareData, projectData);
            }
            if (!!prepareData.release !== !!this._prepareInfo.release) {
                this.$logger.trace(`Setting all setting to true. Current options are: `, prepareData, " old prepare info is: ", this._prepareInfo);
                this._changesInfo.appResourcesChanged = true;
                this._changesInfo.configChanged = true;
                this._prepareInfo.release = prepareData.release;
            }
            if (this._changesInfo.appResourcesChanged) {
                this.$logger.trace(`Set configChanged to true, appResourcesChanged is: ${this._changesInfo.appResourcesChanged}`);
                this._changesInfo.configChanged = true;
            }
            if (this._changesInfo.hasChanges) {
                this._prepareInfo.changesRequireBuild = this._changesInfo.changesRequireBuild;
                this._prepareInfo.time = new Date().toString();
                if (this._prepareInfo.changesRequireBuild) {
                    this._prepareInfo.changesRequireBuildTime = this._prepareInfo.time;
                }
            }
            this._changesInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus;
            this.$logger.trace("checkForChanges returns", this._changesInfo);
            return this._changesInfo;
        });
    }
    getPrepareInfoFilePath(platformData) {
        const prepareInfoFilePath = path.join(platformData.projectRoot, prepareInfoFileName);
        return prepareInfoFilePath;
    }
    getPrepareInfo(platformData) {
        const prepareInfoFilePath = this.getPrepareInfoFilePath(platformData);
        let prepareInfo = null;
        if (this.$fs.exists(prepareInfoFilePath)) {
            try {
                prepareInfo = this.$fs.readJson(prepareInfoFilePath);
            }
            catch (e) {
                prepareInfo = null;
            }
        }
        return prepareInfo;
    }
    savePrepareInfo(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this._prepareInfo) {
                yield this.ensurePrepareInfo(platformData, projectData, prepareData);
            }
            const prepareInfoFilePath = this.getPrepareInfoFilePath(platformData);
            this.$fs.writeJson(prepareInfoFilePath, this._prepareInfo);
        });
    }
    setNativePlatformStatus(platformData, projectData, addedPlatform) {
        return __awaiter(this, void 0, void 0, function* () {
            this._prepareInfo = this._prepareInfo || this.getPrepareInfo(platformData);
            if (this._prepareInfo &&
                addedPlatform.nativePlatformStatus ===
                    "3") {
                this._prepareInfo.nativePlatformStatus =
                    addedPlatform.nativePlatformStatus;
            }
            else {
                this._prepareInfo = {
                    nativePlatformStatus: addedPlatform.nativePlatformStatus,
                };
            }
            yield this.savePrepareInfo(platformData, projectData, null);
        });
    }
    ensurePrepareInfo(platformData, projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            this._prepareInfo = this.getPrepareInfo(platformData);
            if (this._prepareInfo) {
                const prepareInfoFile = path.join(platformData.projectRoot, prepareInfoFileName);
                this._outputProjectMtime = this.$fs
                    .getFsStats(prepareInfoFile)
                    .mtime.getTime();
                this._outputProjectCTime = this.$fs
                    .getFsStats(prepareInfoFile)
                    .ctime.getTime();
                return false;
            }
            const nativePlatformStatus = !prepareData.nativePrepare || !prepareData.nativePrepare.skipNativePrepare
                ? "2"
                : "1";
            this._prepareInfo = {
                time: "",
                nativePlatformStatus,
                release: prepareData.release,
                changesRequireBuild: true,
                projectFileHash: this.getProjectFileStrippedHash(projectData.projectDir, platformData),
                changesRequireBuildTime: null,
            };
            this._outputProjectMtime = 0;
            this._outputProjectCTime = 0;
            this._changesInfo = this._changesInfo || new ProjectChangesInfo();
            this._changesInfo.appResourcesChanged = true;
            this._changesInfo.configChanged = true;
            this._changesInfo.nativeChanged = true;
            this._changesInfo.nsConfigChanged = true;
            return true;
        });
    }
    getProjectFileStrippedHash(projectDir, platformData) {
        const projectFilePath = path.join(projectDir, constants_1.PACKAGE_JSON_FILE_NAME);
        const projectFileContents = this.$fs.readJson(projectFilePath);
        return helpers_1.getHash(JSON.stringify(projectFileContents));
    }
    isProjectFileChanged(projectDir, platformData) {
        const projectFileStrippedContentsHash = this.getProjectFileStrippedHash(projectDir, platformData);
        const prepareInfo = this.getPrepareInfo(platformData);
        return projectFileStrippedContentsHash !== prepareInfo.projectFileHash;
    }
    filesChanged(files) {
        for (const file of files) {
            if (this.$fs.exists(file)) {
                const fileStats = this.$fs.getFsStats(file);
                if (fileStats.mtime.getTime() >= this._outputProjectMtime ||
                    fileStats.ctime.getTime() >= this._outputProjectCTime) {
                    return true;
                }
            }
        }
        return false;
    }
    containsNewerFiles(dir, projectData) {
        const dirName = path.basename(dir);
        this.$logger.trace(`containsNewerFiles will check ${dir}`);
        if (_.startsWith(dirName, ".")) {
            return false;
        }
        if (this.isFileModified(dir)) {
            this.$logger.trace(`containsNewerFiles returns true for ${dir} as the dir itself has been modified.`);
            return true;
        }
        const files = this.$fs.readDirectory(dir);
        for (const file of files) {
            const filePath = path.join(dir, file);
            const fileStats = this.$fs.getFsStats(filePath);
            const changed = this.isFileModified(filePath, fileStats);
            if (changed) {
                this.$logger.trace(`containsNewerFiles returns true for ${dir}. The modified file is ${filePath}`);
                return true;
            }
            if (fileStats.isDirectory()) {
                if (this.containsNewerFiles(filePath, projectData)) {
                    this.$logger.trace(`containsNewerFiles returns true for ${dir}.`);
                    return true;
                }
            }
        }
        return false;
    }
    isFileModified(filePath, filePathStats) {
        filePathStats = filePathStats || this.$fs.getFsStats(filePath);
        let changed = filePathStats.mtime.getTime() >= this._outputProjectMtime ||
            filePathStats.ctime.getTime() >= this._outputProjectCTime;
        if (!changed) {
            const lFileStats = this.$fs.getLsStats(filePath);
            changed =
                lFileStats.mtime.getTime() >= this._outputProjectMtime ||
                    lFileStats.ctime.getTime() >= this._outputProjectCTime;
        }
        return changed;
    }
}
__decorate([
    helpers_1.hook("checkForChanges")
], ProjectChangesService.prototype, "checkForChanges", null);
exports.ProjectChangesService = ProjectChangesService;
yok_1.injector.register("projectChangesService", ProjectChangesService);
