"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.IOSProjectService = void 0;
const path = require("path");
const shell = require("shelljs");
const _ = require("lodash");
const constants = require("../constants");
const constants_1 = require("../common/constants");
const helpers = require("../common/helpers");
const helpers_1 = require("../common/helpers");
const projectServiceBaseLib = require("./platform-project-service-base");
const plist_merge_patch_1 = require("plist-merge-patch");
const os_1 = require("os");
const plist = require("plist");
const constants_2 = require("../constants");
const helpers_2 = require("../common/helpers");
const yok_1 = require("../common/yok");
const DevicePlatformSdkName = "iphoneos";
const SimulatorPlatformSdkName = "iphonesimulator";
const FRAMEWORK_EXTENSIONS = [".framework", ".xcframework"];
const getPlatformSdkName = (forDevice) => forDevice ? DevicePlatformSdkName : SimulatorPlatformSdkName;
const getConfigurationName = (release) => release ? constants_1.Configurations.Release : constants_1.Configurations.Debug;
class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase {
    constructor($fs, $childProcess, $cocoapodsService, $errors, $logger, $injector, $projectDataService, $devicePlatformsConstants, $hostInfo, $xcprojService, $iOSProvisionService, $iOSSigningService, $pbxprojDomXcode, $xcode, $iOSEntitlementsService, $platformEnvironmentRequirements, $plistParser, $xcconfigService, $xcodebuildService, $iOSExtensionsService, $iOSWatchAppService, $iOSNativeTargetService, $sysInfo, $tempService) {
        super($fs, $projectDataService);
        this.$childProcess = $childProcess;
        this.$cocoapodsService = $cocoapodsService;
        this.$errors = $errors;
        this.$logger = $logger;
        this.$injector = $injector;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$hostInfo = $hostInfo;
        this.$xcprojService = $xcprojService;
        this.$iOSProvisionService = $iOSProvisionService;
        this.$iOSSigningService = $iOSSigningService;
        this.$pbxprojDomXcode = $pbxprojDomXcode;
        this.$xcode = $xcode;
        this.$iOSEntitlementsService = $iOSEntitlementsService;
        this.$platformEnvironmentRequirements = $platformEnvironmentRequirements;
        this.$plistParser = $plistParser;
        this.$xcconfigService = $xcconfigService;
        this.$xcodebuildService = $xcodebuildService;
        this.$iOSExtensionsService = $iOSExtensionsService;
        this.$iOSWatchAppService = $iOSWatchAppService;
        this.$iOSNativeTargetService = $iOSNativeTargetService;
        this.$sysInfo = $sysInfo;
        this.$tempService = $tempService;
        this._platformsDirCache = null;
        this._platformData = null;
    }
    getPlatformData(projectData) {
        if (!projectData && !this._platformData) {
            throw new Error("First call of getPlatformData without providing projectData.");
        }
        if (projectData &&
            projectData.platformsDir &&
            this._platformsDirCache !== projectData.platformsDir) {
            const projectRoot = path.join(projectData.platformsDir, this.$devicePlatformsConstants.iOS.toLowerCase());
            const runtimePackage = this.$projectDataService.getRuntimePackage(projectData.projectDir, "ios");
            this._platformData = {
                frameworkPackageName: runtimePackage.name,
                normalizedPlatformName: "iOS",
                platformNameLowerCase: "ios",
                appDestinationDirectoryPath: path.join(projectRoot, projectData.projectName),
                platformProjectService: this,
                projectRoot: projectRoot,
                getBuildOutputPath: (options) => {
                    const config = getConfigurationName(!options || options.release);
                    return path.join(projectRoot, constants.BUILD_DIR, `${config}-${getPlatformSdkName(!options || options.buildForDevice || options.buildForAppStore)}`);
                },
                getValidBuildOutputData: (buildOptions) => {
                    const forDevice = !buildOptions ||
                        !!buildOptions.buildForDevice ||
                        !!buildOptions.buildForAppStore;
                    if (forDevice) {
                        const ipaFileName = _.find(this.$fs.readDirectory(this._platformData.getBuildOutputPath(buildOptions)), (entry) => path.extname(entry) === ".ipa");
                        return {
                            packageNames: [ipaFileName, `${projectData.projectName}.ipa`],
                        };
                    }
                    return {
                        packageNames: [
                            `${projectData.projectName}.app`,
                            `${projectData.projectName}.zip`,
                        ],
                    };
                },
                frameworkDirectoriesExtensions: FRAMEWORK_EXTENSIONS,
                frameworkDirectoriesNames: [
                    "Metadata",
                    "metadataGenerator",
                    "NativeScript",
                    "internal",
                ],
                targetedOS: ["darwin"],
                configurationFileName: constants.INFO_PLIST_FILE_NAME,
                configurationFilePath: path.join(projectRoot, projectData.projectName, projectData.projectName + `-${constants.INFO_PLIST_FILE_NAME}`),
                relativeToFrameworkConfigurationFilePath: path.join("__PROJECT_NAME__", "__PROJECT_NAME__-Info.plist"),
                fastLivesyncFileExtensions: [
                    ".tiff",
                    ".tif",
                    ".jpg",
                    "jpeg",
                    "gif",
                    ".png",
                    ".bmp",
                    ".BMPf",
                    ".ico",
                    ".cur",
                    ".xbm",
                ],
            };
        }
        return this._platformData;
    }
    validateOptions(projectId, provision, teamId) {
        return __awaiter(this, void 0, void 0, function* () {
            if (provision && teamId) {
                this.$errors.fail("The options --provision and --teamId are mutually exclusive.");
            }
            if (provision === true) {
                yield this.$iOSProvisionService.listProvisions(projectId);
                this.$errors.fail("Please provide provisioning profile uuid or name with the --provision option.");
            }
            if (teamId === true) {
                yield this.$iOSProvisionService.listTeams();
                this.$errors.fail("Please provide team id or team name with the --teamId options.");
            }
            return true;
        });
    }
    getAppResourcesDestinationDirectoryPath(projectData) {
        return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName, "Resources");
    }
    validate(projectData, options, notConfiguredEnvOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.$hostInfo.isDarwin) {
                return;
            }
            const checkEnvironmentRequirementsOutput = yield this.$platformEnvironmentRequirements.checkEnvironmentRequirements({
                platform: this.getPlatformData(projectData).normalizedPlatformName,
                projectDir: projectData.projectDir,
                options,
                notConfiguredEnvOptions,
            });
            if (checkEnvironmentRequirementsOutput &&
                checkEnvironmentRequirementsOutput.canExecute) {
                const xcodeWarning = yield this.$sysInfo.getXcodeWarning();
                if (xcodeWarning) {
                    this.$logger.warn(xcodeWarning);
                }
            }
            return {
                checkEnvironmentRequirementsOutput,
            };
        });
    }
    createProject(frameworkDir, frameworkVersion, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$fs.ensureDirectoryExists(path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER));
            shell.cp("-R", path.join(frameworkDir, "*"), this.getPlatformData(projectData).projectRoot);
        });
    }
    interpolateData(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectRootFilePath = path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER);
            if (this.$fs.exists(path.join(projectRootFilePath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + "-Info.plist"))) {
                this.replaceFileName("-Info.plist", projectRootFilePath, projectData);
            }
            this.replaceFileName("-Prefix.pch", projectRootFilePath, projectData);
            if (this.$fs.exists(path.join(projectRootFilePath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + ".entitlements"))) {
                this.replaceFileName(".entitlements", projectRootFilePath, projectData);
            }
            const xcschemeDirPath = path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER +
                constants_2.IosProjectConstants.XcodeProjExtName, "xcshareddata/xcschemes");
            const xcschemeFilePath = path.join(xcschemeDirPath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER +
                constants_2.IosProjectConstants.XcodeSchemeExtName);
            if (this.$fs.exists(xcschemeFilePath)) {
                this.$logger.debug("Found shared scheme at xcschemeFilePath, renaming to match project name.");
                this.$logger.debug("Checkpoint 0");
                this.replaceFileContent(xcschemeFilePath, projectData);
                this.$logger.debug("Checkpoint 1");
                this.replaceFileName(constants_2.IosProjectConstants.XcodeSchemeExtName, xcschemeDirPath, projectData);
                this.$logger.debug("Checkpoint 2");
            }
            else {
                this.$logger.debug("Copying xcscheme from template not found at " + xcschemeFilePath);
            }
            this.replaceFileName(constants_2.IosProjectConstants.XcodeProjExtName, this.getPlatformData(projectData).projectRoot, projectData);
            const pbxprojFilePath = this.getPbxProjPath(projectData);
            this.replaceFileContent(pbxprojFilePath, projectData);
            const internalDirPath = path.join(projectRootFilePath, "..", "internal");
            const xcframeworksFilePath = path.join(internalDirPath, "XCFrameworks.zip");
            if (this.$fs.exists(xcframeworksFilePath)) {
                yield this.$fs.unzip(xcframeworksFilePath, internalDirPath);
                this.$fs.deleteFile(xcframeworksFilePath);
            }
        });
    }
    interpolateConfigurationFile(projectData) {
        return undefined;
    }
    cleanProject(projectRoot, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            return null;
        });
    }
    afterCreateProject(projectRoot, projectData) {
        this.$fs.rename(path.join(projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER), path.join(projectRoot, projectData.projectName));
    }
    buildProject(projectRoot, projectData, buildData) {
        return __awaiter(this, void 0, void 0, function* () {
            const platformData = this.getPlatformData(projectData);
            const handler = (data) => {
                this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data);
            };
            if (buildData.buildForDevice) {
                yield this.$iOSSigningService.setupSigningForDevice(projectRoot, projectData, buildData);
                yield helpers_1.attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, this.$xcodebuildService.buildForDevice(platformData, projectData, buildData));
            }
            else if (buildData.buildForAppStore) {
                yield helpers_1.attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, this.$xcodebuildService.buildForAppStore(platformData, projectData, buildData));
            }
            else {
                yield helpers_1.attachAwaitDetach(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, this.$xcodebuildService.buildForSimulator(platformData, projectData, buildData));
            }
            this.validateApplicationIdentifier(projectData);
        });
    }
    isPlatformPrepared(projectRoot, projectData) {
        return this.$fs.exists(path.join(projectRoot, projectData.projectName, constants.APP_FOLDER_NAME));
    }
    cleanDeviceTempFolder(deviceIdentifier) {
        return Promise.resolve();
    }
    isDynamicFramework(frameworkPath) {
        return __awaiter(this, void 0, void 0, function* () {
            const frameworkName = path.basename(frameworkPath, path.extname(frameworkPath));
            const isDynamicFrameworkBundle = (bundlePath) => __awaiter(this, void 0, void 0, function* () {
                const frameworkBinaryPath = path.join(bundlePath, frameworkName);
                const fileResult = (yield this.$childProcess.spawnFromEvent("file", [frameworkBinaryPath], "close")).stdout;
                const isDynamicallyLinked = _.includes(fileResult, "dynamically linked");
                return isDynamicallyLinked;
            });
            if (path.extname(frameworkPath) === ".xcframework") {
                let isDynamic = true;
                const subDirs = this.$fs
                    .readDirectory(frameworkPath)
                    .filter((entry) => this.$fs.getFsStats(path.join(frameworkPath, entry)).isDirectory());
                for (const subDir of subDirs) {
                    const singlePlatformFramework = path.join(subDir, frameworkName + ".framework");
                    if (this.$fs.exists(singlePlatformFramework)) {
                        isDynamic = yield isDynamicFrameworkBundle(singlePlatformFramework);
                        break;
                    }
                }
                return isDynamic;
            }
            else {
                return yield isDynamicFrameworkBundle(frameworkPath);
            }
        });
    }
    addFramework(frameworkPath, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.$hostInfo.isWindows) {
                this.validateFramework(frameworkPath);
                const project = this.createPbxProj(projectData);
                const frameworkAddOptions = { customFramework: true };
                if (yield this.isDynamicFramework(frameworkPath)) {
                    frameworkAddOptions["embed"] = true;
                    frameworkAddOptions["sign"] = true;
                }
                const frameworkRelativePath = "$(SRCROOT)/" +
                    this.getLibSubpathRelativeToProjectPath(frameworkPath, projectData);
                project.addFramework(frameworkRelativePath, frameworkAddOptions);
                this.savePbxProj(project, projectData);
            }
        });
    }
    addStaticLibrary(staticLibPath, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const libraryName = path.basename(staticLibPath, ".a");
            const headersSubpath = path.join(path.dirname(staticLibPath), "include", libraryName);
            const project = this.createPbxProj(projectData);
            const relativeStaticLibPath = this.getLibSubpathRelativeToProjectPath(staticLibPath, projectData);
            project.addFramework(relativeStaticLibPath);
            const relativeHeaderSearchPath = path.join(this.getLibSubpathRelativeToProjectPath(headersSubpath, projectData));
            project.addToHeaderSearchPaths({ relativePath: relativeHeaderSearchPath });
            this.generateModulemap(headersSubpath, libraryName);
            this.savePbxProj(project, projectData);
        });
    }
    prepareProject(projectData, prepareData) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectRoot = path.join(projectData.platformsDir, "ios");
            const platformData = this.getPlatformData(projectData);
            const resourcesDirectoryPath = projectData.getAppResourcesDirectoryPath();
            const provision = prepareData && prepareData.provision;
            const teamId = prepareData && prepareData.teamId;
            if (provision) {
                yield this.$iOSSigningService.setupSigningFromProvision(projectRoot, projectData, provision, prepareData.mobileProvisionData);
            }
            if (teamId) {
                yield this.$iOSSigningService.setupSigningFromTeam(projectRoot, projectData, teamId);
            }
            const project = this.createPbxProj(projectData);
            const resources = project.pbxGroupByName("Resources");
            if (resources) {
                const references = project.pbxFileReferenceSection();
                const xcodeProjectImages = _.map(resources.children, (resource) => this.replace(references[resource.value].name));
                this.$logger.trace("Images from Xcode project");
                this.$logger.trace(xcodeProjectImages);
                const appResourcesImages = this.$fs.readDirectory(this.getAppResourcesDestinationDirectoryPath(projectData));
                this.$logger.trace("Current images from App_Resources");
                this.$logger.trace(appResourcesImages);
                const imagesToAdd = _.difference(appResourcesImages, xcodeProjectImages);
                this.$logger.trace(`New images to add into xcode project: ${imagesToAdd.join(", ")}`);
                _.each(imagesToAdd, (image) => project.addResourceFile(path.relative(this.getPlatformData(projectData).projectRoot, path.join(this.getAppResourcesDestinationDirectoryPath(projectData), image))));
                const imagesToRemove = _.difference(xcodeProjectImages, appResourcesImages);
                this.$logger.trace(`Images to remove from xcode project: ${imagesToRemove.join(", ")}`);
                _.each(imagesToRemove, (image) => project.removeResourceFile(path.join(this.getAppResourcesDestinationDirectoryPath(projectData), image)));
                this.savePbxProj(project, projectData);
                const resourcesNativeCodePath = path.join(resourcesDirectoryPath, platformData.normalizedPlatformName, constants.NATIVE_SOURCE_FOLDER);
                yield this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, resourcesNativeCodePath, projectData);
            }
            const pbxProjPath = this.getPbxProjPath(projectData);
            this.$iOSWatchAppService.removeWatchApp({ pbxProjPath });
            const addedWatchApp = yield this.$iOSWatchAppService.addWatchAppFromPath({
                watchAppFolderPath: path.join(resourcesDirectoryPath, platformData.normalizedPlatformName),
                projectData,
                platformData,
                pbxProjPath,
            });
            if (addedWatchApp) {
                this.$logger.warn("The support for Apple Watch App is currently in Beta. For more information about the current development state and any known issues, please check the relevant GitHub issue: https://github.com/NativeScript/nativescript-cli/issues/4589");
            }
        });
    }
    prepareAppResources(projectData) {
        const platformData = this.getPlatformData(projectData);
        const projectAppResourcesPath = projectData.getAppResourcesDirectoryPath(projectData.projectDir);
        const platformsAppResourcesPath = this.getAppResourcesDestinationDirectoryPath(projectData);
        this.$fs.deleteDirectory(platformsAppResourcesPath);
        this.$fs.ensureDirectoryExists(platformsAppResourcesPath);
        this.$fs.copyFile(path.join(projectAppResourcesPath, platformData.normalizedPlatformName, "*"), platformsAppResourcesPath);
        this.$fs.deleteFile(path.join(platformsAppResourcesPath, platformData.configurationFileName));
        this.$fs.deleteFile(path.join(platformsAppResourcesPath, constants.PODFILE_NAME));
        this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, constants.NATIVE_SOURCE_FOLDER));
        this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, constants.NATIVE_EXTENSION_FOLDER));
        this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, "watchapp"));
        this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, "watchextension"));
    }
    processConfigurationFilesFromAppResources(projectData, opts) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.mergeInfoPlists(projectData, opts);
            yield this.$iOSEntitlementsService.merge(projectData);
            yield this.mergeProjectXcconfigFiles(projectData);
        });
    }
    ensureConfigurationFileInAppResources() {
        return null;
    }
    mergeInfoPlists(projectData, buildOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectDir = projectData.projectDir;
            const infoPlistPath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, this.getPlatformData(projectData).configurationFileName);
            this.ensureConfigurationFileInAppResources();
            const reporterTraceMessage = "Info.plist:";
            const reporter = {
                log: (txt) => this.$logger.trace(`${reporterTraceMessage} ${txt}`),
                warn: (txt) => this.$logger.warn(`${reporterTraceMessage} ${txt}`),
            };
            const session = new plist_merge_patch_1.PlistSession(reporter);
            const makePatch = (plistPath) => {
                if (!this.$fs.exists(plistPath)) {
                    this.$logger.trace("No plist found at: " + plistPath);
                    return;
                }
                this.$logger.trace("Schedule merge plist at: " + plistPath);
                session.patch({
                    name: path.relative(projectDir, plistPath),
                    read: () => this.$fs.readText(plistPath),
                });
            };
            const allPlugins = this.getAllProductionPlugins(projectData);
            for (const plugin of allPlugins) {
                const pluginInfoPlistPath = path.join(plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME), this.getPlatformData(projectData).configurationFileName);
                makePatch(pluginInfoPlistPath);
            }
            makePatch(infoPlistPath);
            if (projectData.projectIdentifiers && projectData.projectIdentifiers.ios) {
                session.patch({
                    name: "CFBundleIdentifier from package.json nativescript.id",
                    read: () => `<?xml version="1.0" encoding="UTF-8"?>
						<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
						<plist version="1.0">
						<dict>
							<key>CFBundleIdentifier</key>
							<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
						</dict>
						</plist>`,
                });
            }
            if (!buildOptions.release &&
                projectData.projectIdentifiers &&
                projectData.projectIdentifiers.ios) {
                session.patch({
                    name: "CFBundleURLTypes from package.json nativescript.id",
                    read: () => `<?xml version="1.0" encoding="UTF-8"?>
						<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
						<plist version="1.0">
						<dict>
							<key>CFBundleURLTypes</key>
							<array>
								<dict>
									<key>CFBundleTypeRole</key>
									<string>Editor</string>
									<key>CFBundleURLSchemes</key>
									<array>
										<string>${projectData.projectIdentifiers.ios.replace(/[^A-Za-z0-9]/g, "")}</string>
									</array>
								</dict>
							</array>
						</dict>
						</plist>`,
                });
            }
            const plistContent = session.build();
            this.$logger.trace("Info.plist: Write to: " +
                this.getPlatformData(projectData).configurationFilePath);
            this.$fs.writeFile(this.getPlatformData(projectData).configurationFilePath, plistContent);
        });
    }
    getAllProductionPlugins(projectData) {
        return (this.$injector.resolve("pluginsService")).getAllProductionPlugins(projectData, this.getPlatformData(projectData).platformNameLowerCase);
    }
    replace(name) {
        if (_.startsWith(name, '"')) {
            name = name.substr(1, name.length - 2);
        }
        return name.replace(/\\\"/g, '"');
    }
    getLibSubpathRelativeToProjectPath(targetPath, projectData) {
        const frameworkPath = path.relative(this.getPlatformData(projectData).projectRoot, targetPath);
        return frameworkPath;
    }
    getPbxProjPath(projectData) {
        return path.join(this.$xcprojService.getXcodeprojPath(projectData, this.getPlatformData(projectData).projectRoot), "project.pbxproj");
    }
    createPbxProj(projectData) {
        const project = new this.$xcode.project(this.getPbxProjPath(projectData));
        project.parseSync();
        return project;
    }
    savePbxProj(project, projectData, omitEmptyValues) {
        return this.$fs.writeFile(this.getPbxProjPath(projectData), project.writeSync({ omitEmptyValues }));
    }
    preparePluginNativeCode(pluginData, projectData, opts) {
        return __awaiter(this, void 0, void 0, function* () {
            const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
            const sourcePath = path.join(pluginPlatformsFolderPath, "src");
            yield this.prepareNativeSourceCode(pluginData.name, sourcePath, projectData);
            yield this.prepareResources(pluginPlatformsFolderPath, pluginData, projectData);
            yield this.prepareFrameworks(pluginPlatformsFolderPath, pluginData, projectData);
            yield this.prepareStaticLibs(pluginPlatformsFolderPath, pluginData, projectData);
        });
    }
    removePluginNativeCode(pluginData, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
            this.removeNativeSourceCode(pluginPlatformsFolderPath, pluginData, projectData);
            this.removeFrameworks(pluginPlatformsFolderPath, pluginData, projectData);
            this.removeStaticLibs(pluginPlatformsFolderPath, pluginData, projectData);
            const projectRoot = this.getPlatformData(projectData).projectRoot;
            this.$cocoapodsService.removePodfileFromProject(pluginData.name, this.$cocoapodsService.getPluginPodfilePath(pluginData), projectData, projectRoot);
        });
    }
    handleNativeDependenciesChange(projectData, opts) {
        return __awaiter(this, void 0, void 0, function* () {
            const platformData = this.getPlatformData(projectData);
            const pluginsData = this.getAllProductionPlugins(projectData);
            this.setProductBundleIdentifier(projectData);
            yield this.applyPluginsCocoaPods(pluginsData, projectData, platformData);
            yield this.$cocoapodsService.applyPodfileFromAppResources(projectData, platformData);
            yield this.$cocoapodsService.applyPodfileArchExclusions(projectData, platformData);
            const projectPodfilePath = this.$cocoapodsService.getProjectPodfilePath(platformData.projectRoot);
            if (this.$fs.exists(projectPodfilePath)) {
                yield this.$cocoapodsService.executePodInstall(platformData.projectRoot, this.$xcprojService.getXcodeprojPath(projectData, platformData.projectRoot));
                yield this.$cocoapodsService.mergePodXcconfigFile(projectData, platformData, opts);
            }
            const pbxProjPath = this.getPbxProjPath(projectData);
            this.$iOSExtensionsService.removeExtensions({ pbxProjPath });
            yield this.addExtensions(projectData, pluginsData);
        });
    }
    beforePrepareAllPlugins() {
        return Promise.resolve();
    }
    checkForChanges(changesInfo, prepareData, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const { provision, teamId } = prepareData;
            const hasProvision = provision !== undefined;
            const hasTeamId = teamId !== undefined;
            if (hasProvision || hasTeamId) {
                const pbxprojPath = this.getPbxProjPath(projectData);
                if (this.$fs.exists(pbxprojPath)) {
                    const xcode = this.$pbxprojDomXcode.Xcode.open(pbxprojPath);
                    const signing = xcode.getSigning(projectData.projectName);
                    if (hasProvision) {
                        if (signing && signing.style === "Manual") {
                            for (const name in signing.configurations) {
                                const config = signing.configurations[name];
                                if (config.uuid !== provision && config.name !== provision) {
                                    changesInfo.signingChanged = true;
                                    break;
                                }
                            }
                        }
                        else {
                            changesInfo.signingChanged = true;
                        }
                    }
                    if (hasTeamId) {
                        if (signing && signing.style === "Automatic") {
                            if (signing.team !== teamId) {
                                const teamIdsForName = yield this.$iOSProvisionService.getTeamIdsWithName(teamId);
                                if (!teamIdsForName.some((id) => id === signing.team)) {
                                    changesInfo.signingChanged = true;
                                }
                            }
                        }
                        else {
                            changesInfo.signingChanged = true;
                        }
                    }
                }
                else {
                    changesInfo.signingChanged = true;
                }
            }
        });
    }
    getDeploymentTarget(projectData) {
        const target = this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "IPHONEOS_DEPLOYMENT_TARGET");
        return target;
    }
    setProductBundleIdentifier(projectData) {
        const project = this.createPbxProj(projectData);
        this.$iOSNativeTargetService.setXcodeTargetBuildConfigurationProperties([
            {
                name: "PRODUCT_BUNDLE_IDENTIFIER",
                value: `"${projectData.projectIdentifiers.ios}"`,
            },
        ], projectData.projectName, project);
        this.savePbxProj(project, projectData);
    }
    getAllLibsForPluginWithFileExtension(pluginData, fileExtension) {
        const fileExtensions = _.isArray(fileExtension)
            ? fileExtension
            : [fileExtension];
        const filterCallback = (fileName, pluginPlatformsFolderPath) => fileExtensions.indexOf(path.extname(fileName)) !== -1;
        return this.getAllNativeLibrariesForPlugin(pluginData, IOSProjectService.IOS_PLATFORM_NAME, filterCallback);
    }
    validateFramework(libraryPath) {
        const infoPlistPath = path.join(libraryPath, constants.INFO_PLIST_FILE_NAME);
        if (!this.$fs.exists(infoPlistPath)) {
            this.$errors.fail("The bundle at %s does not contain an Info.plist file.", libraryPath);
        }
        const plistJson = this.$plistParser.parseFileSync(infoPlistPath);
        const packageType = plistJson["CFBundlePackageType"];
        if (packageType !== "FMWK" && packageType !== "XFWK") {
            this.$errors.fail("The bundle at %s does not appear to be a dynamic framework.", libraryPath);
        }
    }
    replaceFileContent(file, projectData) {
        const fileContent = this.$fs.readText(file);
        const replacedContent = helpers.stringReplaceAll(fileContent, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, projectData.projectName);
        this.$fs.writeFile(file, replacedContent);
    }
    replaceFileName(fileNamePart, fileRootLocation, projectData) {
        const oldFileName = IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + fileNamePart;
        const newFileName = projectData.projectName + fileNamePart;
        this.$fs.rename(path.join(fileRootLocation, oldFileName), path.join(fileRootLocation, newFileName));
    }
    prepareNativeSourceCode(groupName, sourceFolderPath, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const project = this.createPbxProj(projectData);
            const group = this.getRootGroup(groupName, sourceFolderPath);
            project.addPbxGroup(group.files, group.name, group.path, null, {
                isMain: true,
                filesRelativeToProject: true,
            });
            project.addToHeaderSearchPaths(group.path);
            const headerFiles = this.$fs.exists(sourceFolderPath)
                ? this.$fs.enumerateFilesInDirectorySync(sourceFolderPath, (file, stat) => stat.isDirectory() || path.extname(file) === ".h")
                : [];
            if (headerFiles.length > 0 &&
                !this.$fs.exists(path.join(sourceFolderPath, "module.modulemap"))) {
                this.$logger.warn(`warning: Directory ${sourceFolderPath} with native iOS source code doesn't contain a modulemap file. Metadata for it will not be generated and it will not be accessible from JavaScript. To learn more see https://docs.nativescript.org/guides/ios-source-code`);
            }
            this.savePbxProj(project, projectData);
        });
    }
    addExtensions(projectData, pluginsData) {
        return __awaiter(this, void 0, void 0, function* () {
            const resorcesExtensionsPath = path.join(projectData.getAppResourcesDirectoryPath(), this.getPlatformData(projectData).normalizedPlatformName, constants.NATIVE_EXTENSION_FOLDER);
            const platformData = this.getPlatformData(projectData);
            const pbxProjPath = this.getPbxProjPath(projectData);
            const addedExtensionsFromResources = yield this.$iOSExtensionsService.addExtensionsFromPath({
                extensionsFolderPath: resorcesExtensionsPath,
                projectData,
                platformData,
                pbxProjPath,
            });
            let addedExtensionsFromPlugins = false;
            for (const pluginIndex in pluginsData) {
                const pluginData = pluginsData[pluginIndex];
                const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
                const extensionPath = path.join(pluginPlatformsFolderPath, constants.NATIVE_EXTENSION_FOLDER);
                const addedExtensionFromPlugin = yield this.$iOSExtensionsService.addExtensionsFromPath({
                    extensionsFolderPath: extensionPath,
                    projectData,
                    platformData,
                    pbxProjPath,
                });
                addedExtensionsFromPlugins =
                    addedExtensionsFromPlugins || addedExtensionFromPlugin;
            }
            if (addedExtensionsFromResources || addedExtensionsFromPlugins) {
                this.$logger.warn("The support for iOS App Extensions is currently in Beta. For more information about the current development state and any known issues, please check the relevant GitHub issue: https://github.com/NativeScript/nativescript-cli/issues/4472");
            }
        });
    }
    getRootGroup(name, rootPath) {
        const filePathsArr = [];
        const rootGroup = {
            name: name,
            files: filePathsArr,
            path: rootPath,
        };
        if (this.$fs.exists(rootPath)) {
            const stats = this.$fs.getFsStats(rootPath);
            if (stats.isDirectory() && !this.$fs.isEmptyDir(rootPath)) {
                this.$fs.readDirectory(rootPath).forEach((fileName) => {
                    const filePath = path.join(rootGroup.path, fileName);
                    filePathsArr.push(filePath);
                });
            }
        }
        return rootGroup;
    }
    prepareResources(pluginPlatformsFolderPath, pluginData, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const project = this.createPbxProj(projectData);
            const resourcesPath = path.join(pluginPlatformsFolderPath, "Resources");
            if (this.$fs.exists(resourcesPath) && !this.$fs.isEmptyDir(resourcesPath)) {
                for (const fileName of this.$fs.readDirectory(resourcesPath)) {
                    const filePath = path.join(resourcesPath, fileName);
                    project.addResourceFile(filePath);
                }
            }
            this.savePbxProj(project, projectData);
        });
    }
    prepareFrameworks(pluginPlatformsFolderPath, pluginData, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            for (const fileName of this.getAllLibsForPluginWithFileExtension(pluginData, FRAMEWORK_EXTENSIONS)) {
                yield this.addFramework(path.join(pluginPlatformsFolderPath, fileName), projectData);
            }
        });
    }
    prepareStaticLibs(pluginPlatformsFolderPath, pluginData, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            for (const fileName of this.getAllLibsForPluginWithFileExtension(pluginData, ".a")) {
                yield this.addStaticLibrary(path.join(pluginPlatformsFolderPath, fileName), projectData);
            }
        });
    }
    removeNativeSourceCode(pluginPlatformsFolderPath, pluginData, projectData) {
        const project = this.createPbxProj(projectData);
        const group = this.getRootGroup(pluginData.name, pluginPlatformsFolderPath);
        project.removePbxGroup(group.name, group.path);
        project.removeFromHeaderSearchPaths(group.path);
        this.savePbxProj(project, projectData);
    }
    removeFrameworks(pluginPlatformsFolderPath, pluginData, projectData) {
        const project = this.createPbxProj(projectData);
        _.each(this.getAllLibsForPluginWithFileExtension(pluginData, FRAMEWORK_EXTENSIONS), (fileName) => {
            const relativeFrameworkPath = this.getLibSubpathRelativeToProjectPath(fileName, projectData);
            project.removeFramework(relativeFrameworkPath, {
                customFramework: true,
                embed: true,
            });
        });
        this.savePbxProj(project, projectData);
    }
    removeStaticLibs(pluginPlatformsFolderPath, pluginData, projectData) {
        const project = this.createPbxProj(projectData);
        _.each(this.getAllLibsForPluginWithFileExtension(pluginData, ".a"), (fileName) => {
            const staticLibPath = path.join(pluginPlatformsFolderPath, fileName);
            const relativeStaticLibPath = this.getLibSubpathRelativeToProjectPath(path.basename(staticLibPath), projectData);
            project.removeFramework(relativeStaticLibPath);
            const headersSubpath = path.join("include", path.basename(staticLibPath, ".a"));
            const relativeHeaderSearchPath = path.join(this.getLibSubpathRelativeToProjectPath(headersSubpath, projectData));
            project.removeFromHeaderSearchPaths({
                relativePath: relativeHeaderSearchPath,
            });
        });
        this.savePbxProj(project, projectData);
    }
    generateModulemap(headersFolderPath, libraryName) {
        const headersFilter = (fileName, containingFolderPath) => path.extname(fileName) === ".h" &&
            this.$fs.getFsStats(path.join(containingFolderPath, fileName)).isFile();
        const headersFolderContents = this.$fs.readDirectory(headersFolderPath);
        let headers = _(headersFolderContents)
            .filter((item) => headersFilter(item, headersFolderPath))
            .value();
        if (!headers.length) {
            this.$fs.deleteFile(path.join(headersFolderPath, "module.modulemap"));
            return;
        }
        headers = _.map(headers, (value) => `header "${value}"`);
        const modulemap = `module ${libraryName} { explicit module ${libraryName} { ${headers.join(" ")} } }`;
        this.$fs.writeFile(path.join(headersFolderPath, "module.modulemap"), modulemap);
    }
    mergeProjectXcconfigFiles(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const platformData = this.getPlatformData(projectData);
            const pluginsXcconfigFilePaths = _.values(this.$xcconfigService.getPluginsXcconfigFilePaths(platformData.projectRoot));
            for (const pluginsXcconfigFilePath of pluginsXcconfigFilePaths) {
                this.$fs.deleteFile(pluginsXcconfigFilePath);
            }
            const allPlugins = this.getAllProductionPlugins(projectData);
            for (const plugin of allPlugins) {
                const pluginPlatformsFolderPath = plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
                const pluginXcconfigFilePath = path.join(pluginPlatformsFolderPath, constants_2.BUILD_XCCONFIG_FILE_NAME);
                if (this.$fs.exists(pluginXcconfigFilePath)) {
                    for (const pluginsXcconfigFilePath of pluginsXcconfigFilePaths) {
                        yield this.$xcconfigService.mergeFiles(pluginXcconfigFilePath, pluginsXcconfigFilePath);
                    }
                }
            }
            const appResourcesXcconfigPath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants_2.BUILD_XCCONFIG_FILE_NAME);
            if (this.$fs.exists(appResourcesXcconfigPath)) {
                for (const pluginsXcconfigFilePath of pluginsXcconfigFilePaths) {
                    yield this.$xcconfigService.mergeFiles(appResourcesXcconfigPath, pluginsXcconfigFilePath);
                }
            }
            for (const pluginsXcconfigFilePath of pluginsXcconfigFilePaths) {
                if (!this.$fs.exists(pluginsXcconfigFilePath)) {
                    this.$fs.writeFile(pluginsXcconfigFilePath, "");
                }
            }
            for (const pluginsXcconfigFilePath of pluginsXcconfigFilePaths) {
                const entitlementsPropertyValue = this.$xcconfigService.readPropertyValue(pluginsXcconfigFilePath, constants.CODE_SIGN_ENTITLEMENTS);
                if (entitlementsPropertyValue === null &&
                    this.$fs.exists(this.$iOSEntitlementsService.getPlatformsEntitlementsPath(projectData))) {
                    const tempEntitlementsDir = yield this.$tempService.mkdirSync("entitlements");
                    const tempEntitlementsFilePath = path.join(tempEntitlementsDir, "set-entitlements.xcconfig");
                    const entitlementsRelativePath = this.$iOSEntitlementsService.getPlatformsEntitlementsRelativePath(projectData);
                    this.$fs.writeFile(tempEntitlementsFilePath, `CODE_SIGN_ENTITLEMENTS = ${entitlementsRelativePath}${os_1.EOL}`);
                    yield this.$xcconfigService.mergeFiles(tempEntitlementsFilePath, pluginsXcconfigFilePath);
                }
            }
        });
    }
    getBuildXCConfigFilePath(projectData) {
        const buildXCConfig = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants_2.BUILD_XCCONFIG_FILE_NAME);
        return buildXCConfig;
    }
    validateApplicationIdentifier(projectData) {
        const infoPlistPath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, this.getPlatformData(projectData).configurationFileName);
        const mergedPlistPath = this.getPlatformData(projectData)
            .configurationFilePath;
        if (!this.$fs.exists(infoPlistPath) || !this.$fs.exists(mergedPlistPath)) {
            return;
        }
        const infoPlist = plist.parse(this.$fs.readText(infoPlistPath));
        const mergedPlist = plist.parse(this.$fs.readText(mergedPlistPath));
        if (infoPlist.CFBundleIdentifier &&
            infoPlist.CFBundleIdentifier !== mergedPlist.CFBundleIdentifier) {
            this.$logger.warn(`[WARNING]: The CFBundleIdentifier key inside the 'Info.plist' will be overriden by the 'id' set inside the "${constants_2.CONFIG_FILE_NAME_DISPLAY}".`);
        }
    }
    applyPluginsCocoaPods(pluginsData, projectData, platformData) {
        return __awaiter(this, void 0, void 0, function* () {
            for (const pluginIndex in pluginsData) {
                const pluginData = pluginsData[pluginIndex];
                if (this.$fs.exists(pluginData.pluginPlatformsFolderPath(platformData.normalizedPlatformName))) {
                    yield this.$cocoapodsService.applyPodfileToProject(pluginData.name, this.$cocoapodsService.getPluginPodfilePath(pluginData), projectData, platformData);
                }
            }
        });
    }
}
IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__";
IOSProjectService.IOS_PLATFORM_NAME = "ios";
__decorate([
    helpers_2.hook("buildIOS")
], IOSProjectService.prototype, "buildProject", null);
exports.IOSProjectService = IOSProjectService;
yok_1.injector.register("iOSProjectService", IOSProjectService);
