"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());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.listDevices = listDevices;
exports.runIOS = runIOS;
exports.runAndroid = runAndroid;
exports.buildAndroid = buildAndroid;
exports.runMetro = runMetro;
exports.killMetro = killMetro;
exports.npmInstall = npmInstall;
exports.podInstall = podInstall;
exports.installReleaseBuild = installReleaseBuild;
exports.narrowOSName = narrowOSName;
exports.narrowBuildMode = narrowBuildMode;
exports.buildApp = buildApp;
exports.verifyExisting = verifyExisting;
exports.getGitShas = getGitShas;
exports.currentPluginsBundle = currentPluginsBundle;
exports.currentNavigatorsBundle = currentNavigatorsBundle;
exports.getMobileBundle = getMobileBundle;
exports.extractSeed = extractSeed;
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const js_sha256_1 = require("js-sha256");
const path_1 = __importDefault(require("path"));
const unzipper_1 = __importDefault(require("unzipper"));
const os_1 = __importDefault(require("os"));
const child_process_1 = require("child_process");
const utils_1 = require("../utils");
const axios_1 = __importDefault(require("axios"));
const websocket_1 = require("../websocket");
const cliconfig_1 = require("../database/cliconfig");
function parseIOSDevices(text) {
    let lines = text.split('\n');
    let devices = [];
    try {
        let currentLabel = "";
        for (let line of lines) {
            const trimmed = line.trim();
            if (trimmed.startsWith('==')) {
                currentLabel = trimmed;
            }
            else if (trimmed) {
                const udid = trimmed.slice(trimmed.lastIndexOf('(') + 1, trimmed.lastIndexOf(')'));
                devices.push({
                    id: udid,
                    type: currentLabel,
                    name: trimmed.replace(`(${udid})`, '')
                });
            }
        }
    }
    catch (err) {
        console.error("Parse error for ios devices: ", err, text);
    }
    return devices;
}
function listDevices() {
    return __awaiter(this, void 0, void 0, function* () {
        // get ios simulators and devices
        const { stdout, stderr: xctraceerr } = yield (0, utils_1.exec)(`xcrun xctrace list devices`);
        if (xctraceerr) {
            (0, websocket_1.sendLog)(xctraceerr.toString());
        }
        const result = {
            ios: [],
            android: []
        };
        try {
            result.ios = parseIOSDevices(stdout.toString());
        }
        catch (err) {
            console.error(err);
            (0, websocket_1.sendLog)("Failed to parse ios devices" + err);
        }
        // get android simulators
        const { stdout: emulatorStdout, stderr: emulatorStderr } = yield (0, utils_1.exec)(`emulator -list-avds`);
        if (emulatorStderr) {
            (0, websocket_1.sendLog)(emulatorStderr.toString());
        }
        // get android devices
        try {
            const { stdout, stderr } = yield (0, utils_1.exec)(`adb devices`);
            if (stderr) {
                (0, websocket_1.sendLog)(stderr);
            }
            const lines = stdout.toString().split('\n');
            for (let i = 1; i < lines.length; i++) {
                const line = lines[i];
                const trimmed = line.trim();
                if (trimmed) {
                    const [id, type] = trimmed.split('\t');
                    result.android.push({
                        id,
                        type,
                        name: id
                    });
                }
            }
        }
        catch (err) {
            console.error("Failed to get android devices", err);
            (0, websocket_1.sendLog)("Failed to get android devices");
        }
        return result;
    });
}
function clearWatchmanWatches() {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            (0, websocket_1.sendLog)("Clearing watchman watches");
            const { stdout, stderr } = yield (0, utils_1.exec)('watchman watch-del-all');
            (0, websocket_1.sendLog)(stdout.toString());
            (0, websocket_1.sendLog)(stderr.toString());
        }
        catch (err) {
            console.error(err);
            (0, websocket_1.sendLog)("Failed to clear watchman watches: " + (err === null || err === void 0 ? void 0 : err.message));
        }
    });
}
function ensureMetro(repoPath) {
    return __awaiter(this, void 0, void 0, function* () {
        const metroProcs = (0, cliconfig_1.getCurrentMetroProc)();
        let alreadyRunning = false;
        if (metroProcs && metroProcs.length > 0) {
            (0, websocket_1.sendLog)("Killing existing metro process");
            // kill processes 
            for (let proc of metroProcs) {
                if (proc.repoPath !== repoPath) {
                    (0, websocket_1.sendLog)(`Killing metro process for ${repoPath}`);
                    // i don't know why i have to do this
                    const pid = parseInt(proc.pid.toString());
                    try {
                        process.kill(pid);
                    }
                    catch (err) {
                        console.error("error when sending kill signal to existing metro process", err);
                    }
                    // delete processes from db
                    (0, cliconfig_1.deleteMetroProc)(proc.repoPath);
                }
                else {
                    try {
                        process.kill(proc.pid, 0);
                        alreadyRunning = true;
                    }
                    catch (err) {
                        console.error(`Health check of existing metro process failed`, err);
                        (0, cliconfig_1.deleteMetroProc)(proc.repoPath);
                        (0, websocket_1.sendLog)(`Health check of existing metro process failed`);
                    }
                }
            }
        }
        if (!alreadyRunning) {
            yield clearWatchmanWatches();
            yield new Promise((resolve) => setTimeout(resolve, 5000));
            // create metro process and add to db
            yield runMetro(repoPath);
        }
    });
}
function runIOS(appId, deviceId) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a, _b;
        (0, websocket_1.sendLog)("Attempting to start ios debugging");
        const openedApp = (0, cliconfig_1.getOpenApp)(appId);
        if (openedApp) {
            (0, websocket_1.sendLog)("Found an open app: " + openedApp.repoPath);
            yield ensureMetro(openedApp.repoPath);
            const extraArgs = [];
            if (deviceId !== 'none') {
                extraArgs.push('--udid');
                extraArgs.push(deviceId);
            }
            (0, websocket_1.sendLog)("Starting react-native process");
            const installProc = (0, child_process_1.spawn)(`node`, ['./node_modules/.bin/react-native', 'run-ios', ...extraArgs], { cwd: openedApp.repoPath });
            (_a = installProc.stdout) === null || _a === void 0 ? void 0 : _a.on('data', data => {
                (0, websocket_1.sendLog)(data.toString());
            });
            (_b = installProc.stderr) === null || _b === void 0 ? void 0 : _b.on('data', data => {
                (0, websocket_1.sendLog)(data.toString());
            });
            installProc.on('close', code => {
                (0, websocket_1.sendLog)(`Runner exited with status ${code}`);
            });
        }
        else {
            (0, websocket_1.sendLog)("No opened app found for: " + appId);
            throw new Error("Couldn't launch an app that is not opened");
        }
    });
}
function runAndroid(appId, deviceId) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a, _b;
        (0, websocket_1.sendLog)("Attempting to start android debugging");
        const openedApp = (0, cliconfig_1.getOpenApp)(appId);
        if (openedApp) {
            (0, websocket_1.sendLog)("Found an open app: " + openedApp.repoPath);
            yield ensureMetro(openedApp.repoPath);
            const extraArgs = [];
            if (deviceId !== 'none') {
                extraArgs.push('--deviceId');
                extraArgs.push(deviceId);
            }
            (0, websocket_1.sendLog)("Starting react-native process");
            const installProc = (0, child_process_1.spawn)(`node`, ['./node_modules/.bin/react-native', 'run-android', ...extraArgs], { cwd: openedApp.repoPath });
            (_a = installProc.stdout) === null || _a === void 0 ? void 0 : _a.on('data', data => {
                (0, websocket_1.sendLog)(data.toString());
            });
            (_b = installProc.stderr) === null || _b === void 0 ? void 0 : _b.on('data', data => {
                (0, websocket_1.sendLog)(data.toString());
            });
            installProc.on('close', code => {
                (0, websocket_1.sendLog)(`Runner exited with status ${code}`);
            });
        }
        else {
            (0, websocket_1.sendLog)("No opened app found for: " + appId);
            throw new Error("Couldn't launch an app that is not opened");
        }
    });
}
function buildAndroid(appId, deviceId) {
    return __awaiter(this, void 0, void 0, function* () {
        const openedApp = (0, cliconfig_1.getOpenApp)(appId);
        if (openedApp) {
            (0, websocket_1.sendLog)("Found an open app");
            yield new Promise((resolve, reject) => {
                var _a, _b;
                const buildProc = (0, child_process_1.spawn)(`node`, ['./node_modules/.bin/react-native',
                    'build-android',
                    '--tasks',
                    'assembleRelease'
                ], { cwd: openedApp.repoPath });
                (_a = buildProc.stdout) === null || _a === void 0 ? void 0 : _a.on('data', data => {
                    (0, websocket_1.sendLog)(data.toString());
                });
                (_b = buildProc.stderr) === null || _b === void 0 ? void 0 : _b.on('data', data => {
                    (0, websocket_1.sendLog)(data.toString());
                });
                buildProc.on('close', code => {
                    (0, websocket_1.sendLog)(`Builder exited with status ${code}`);
                    if (code === 0) {
                        resolve(code);
                    }
                    else {
                        reject(code);
                    }
                });
            });
            const buildPath = path_1.default.resolve(openedApp.repoPath, 'android/app/build/outputs/apk/release/app-release.apk');
            if (deviceId !== 'none') {
                (0, websocket_1.sendLog)(`Attempting to install from ${buildPath} to ${deviceId}`);
                const { stdout, stderr } = yield (0, utils_1.exec)(`adb -s ${deviceId} install ${buildPath}`);
                (0, websocket_1.sendLog)(stdout.toString());
                (0, websocket_1.sendLog)(stderr.toString());
            }
            else {
                (0, websocket_1.sendLog)(`Apk should be at: ${buildPath}`);
            }
        }
        else {
            console.error("No opened app found for: " + appId);
            throw new Error("Couldn't build an app that is not opened");
        }
    });
}
function runMetro(repoPath) {
    return __awaiter(this, void 0, void 0, function* () {
        (0, websocket_1.sendLog)("Creating new metro process");
        const startP = new Promise((resolve, reject) => {
            var _a, _b;
            const metroProcess = (0, child_process_1.spawn)(`node`, ['./node_modules/.bin/react-native', 'start'], { cwd: repoPath });
            const requiredLine = "Dev server ready";
            let monitor = '';
            let resolved = false;
            (_a = metroProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', data => {
                const newLog = data.toString();
                (0, websocket_1.sendLog)(newLog);
                if (!resolved) {
                    monitor += newLog;
                    if (newLog.includes(requiredLine)) {
                        monitor = '';
                        resolve(0);
                    }
                }
            });
            (_b = metroProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', data => {
                (0, websocket_1.sendLog)(data.toString());
            });
            metroProcess.on('close', code => {
                if (!resolved) {
                    reject(code);
                }
                (0, websocket_1.sendLog)(`Metro has stopped with code ${code}`);
            });
            if (metroProcess.pid) {
                (0, websocket_1.sendLog)("persisting metro pid: " + metroProcess.pid);
                (0, cliconfig_1.createMetroProc)(metroProcess.pid, repoPath);
            }
            else {
                (0, websocket_1.sendLog)("There is no pid received after spawning metro");
            }
        });
        return startP;
    });
}
function killMetro() {
    return __awaiter(this, void 0, void 0, function* () {
        const metroProcs = (0, cliconfig_1.getCurrentMetroProc)();
        (0, websocket_1.sendLog)("Killing existing metro process");
        if (metroProcs) {
            (0, websocket_1.sendLog)(`Found ${metroProcs.length} metro processes`);
            // kill processes 
            for (let proc of metroProcs) {
                // i don't know why i have to do this
                const pid = parseInt(proc.pid.toString());
                (0, websocket_1.sendLog)(`Killing process: ${pid}`);
                process.kill(pid);
                // delete processes from db
                (0, websocket_1.sendLog)(`Deleting all persisted metro for ${proc.repoPath}`);
                (0, cliconfig_1.deleteMetroProc)(proc.repoPath);
            }
            // wait for all kill signals to finish
            yield new Promise((resolve) => setTimeout(resolve, 3000));
            (0, websocket_1.sendLog)(`clear watchman caches`);
            yield clearWatchmanWatches();
            (0, websocket_1.sendLog)("Killing metro finished");
        }
    });
}
function npmInstall(dirPath) {
    return new Promise((resolve, reject) => {
        const process = (0, child_process_1.spawn)(`npm`, ['i', '--verbose'], { cwd: dirPath });
        process.stdout.on('data', data => {
            (0, websocket_1.sendLog)(data.toString());
        });
        process.stderr.on('data', data => {
            (0, websocket_1.sendLog)(data.toString());
        });
        process.on('close', code => {
            if (code === 0) {
                (0, websocket_1.sendLog)('node_modules installed successfully!');
                resolve(code);
            }
            else {
                (0, websocket_1.sendLog)('node_modules installtion exited unsuccessfully');
                reject(code);
            }
        });
    });
}
function podInstall(dirpath) {
    return new Promise((resolve, reject) => {
        const process = (0, child_process_1.spawn)(`pod`, ['install'], { cwd: path_1.default.resolve(dirpath, 'ios') });
        process.stderr.on('data', data => {
            (0, websocket_1.sendLog)(data.toString());
        });
        process.stdout.on('data', data => {
            (0, websocket_1.sendLog)(data.toString());
        });
        process.on('close', code => {
            if (code === 0) {
                (0, websocket_1.sendLog)('pods installed successfully!');
                resolve(code);
            }
            else {
                (0, websocket_1.sendLog)('pod install failed');
                reject(code);
            }
        });
    });
}
function installReleaseBuild(openedApp, deviceId) {
    return __awaiter(this, void 0, void 0, function* () {
        (0, websocket_1.sendLog)(`Starting release build installation for ${deviceId}`);
        return new Promise((resolve, reject) => {
            const process = (0, child_process_1.spawn)(`node`, ['./node_modules/.bin/react-native', 'run-ios', '--mode', 'Release', '--verbose', '--udid', deviceId], { cwd: openedApp.repoPath, detached: true });
            process.stderr.on('data', data => {
                (0, websocket_1.sendLog)(data.toString());
            });
            process.stdout.on('data', data => {
                (0, websocket_1.sendLog)(data.toString());
            });
            process.on('close', code => {
                if (code === 0) {
                    (0, websocket_1.sendLog)(`build finished for target ${deviceId}`);
                    resolve(code);
                }
                else {
                    (0, websocket_1.sendLog)('build failed with error!');
                    reject(code);
                }
            });
        });
    });
}
function archiveIOS(openedApp, configuration) {
    return __awaiter(this, void 0, void 0, function* () {
        return new Promise((resolve, reject) => {
            const process = (0, child_process_1.spawn)('xcodebuild', ['archive',
                '-sdk', 'iphoneos',
                '-workspace', path_1.default.resolve(openedApp.repoPath, 'ios/apptileSeed.xcworkspace'),
                '-scheme', 'apptileSeed',
                '-configuration', configuration,
                '-archivePath', path_1.default.resolve(openedApp.repoPath, 'ios/apptileSeed.xcarchive'),
                '-allowProvisioningUpdates'
            ], { cwd: openedApp.repoPath });
            process.stderr.on('data', data => {
                (0, websocket_1.sendLog)(data.toString());
            });
            process.stdout.on('data', data => {
                (0, websocket_1.sendLog)(data.toString());
            });
            process.on('close', code => {
                if (code === 0) {
                    (0, websocket_1.sendLog)('build finished!');
                    resolve(code);
                }
                else {
                    (0, websocket_1.sendLog)('build failed with error!');
                    reject(code);
                }
            });
        });
    });
}
function exportIPA(openedApp) {
    return __awaiter(this, void 0, void 0, function* () {
        return new Promise((resolve, reject) => {
            const process = (0, child_process_1.spawn)('xcodebuild', ['-exportArchive',
                '-archivePath', path_1.default.resolve(openedApp.repoPath, 'ios/apptileSeed.xcarchive'),
                '-exportPath', path_1.default.resolve(openedApp.repoPath, 'iosbuild'),
                '-exportOptionsPlist', path_1.default.resolve(openedApp.repoPath, 'ExportOptions.plist'),
                '-allowProvisioningUpdates',
                '-verbose'
            ], { cwd: openedApp.repoPath });
            process.stderr.on('data', data => {
                (0, websocket_1.sendLog)(data.toString());
            });
            process.stdout.on('data', data => {
                (0, websocket_1.sendLog)(data.toString());
            });
            process.on('close', code => {
                if (code === 0) {
                    (0, websocket_1.sendLog)('export successful!');
                    resolve(code);
                }
                else {
                    (0, websocket_1.sendLog)('export failed with error!');
                    reject(code);
                }
            });
        });
    });
}
function updateAppConfig(openedApp) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a, _b;
        (0, websocket_1.sendLog)("Downloading latest appconfig");
        const rawConfig = yield (0, promises_1.readFile)(path_1.default.resolve(openedApp.repoPath, 'apptile.config.json'), { encoding: 'utf8' });
        const config = JSON.parse(rawConfig);
        const { data: manifest } = yield axios_1.default.get(`${config.APPTILE_BACKEND_URL}/api/v2/app/${openedApp.appId}/manifest`);
        const publishedCommit = manifest.forks[0].publishedCommitId;
        const iosBundle = manifest.codeArtefacts.find((it) => it.type === 'ios-jsbundle');
        const androidBundle = manifest.codeArtefacts.find((it) => it.type === 'android-jsbundle');
        const appConfigUrl = `${config.APPCONFIG_SERVER_URL}/${openedApp.appId}/main/main/${publishedCommit}.json`;
        (0, websocket_1.sendLog)(`Published appconfig is at ${appConfigUrl}`);
        if (publishedCommit) {
            try {
                const appConfigPath = path_1.default.resolve(openedApp.repoPath, 'ios/appConfig.json');
                (0, websocket_1.sendLog)(`Writing published appconfig at ${appConfigPath}`);
                const writer = (0, node_fs_1.createWriteStream)(appConfigPath);
                const response = yield (0, axios_1.default)({
                    method: 'get',
                    url: appConfigUrl,
                    responseType: 'stream'
                });
                response.data.pipe(writer);
                yield new Promise((resolve, reject) => {
                    writer.on('finish', resolve);
                    writer.on('error', reject);
                });
                const bundleTrackerPath = path_1.default.resolve(openedApp.repoPath, 'ios/localBundleTracker.json');
                (0, websocket_1.sendLog)(`Writing bundleTracker at: ${bundleTrackerPath}`);
                yield (0, promises_1.writeFile)(bundleTrackerPath, `{"publishedCommitId": ${publishedCommit}, "iosBundleId": ${(_a = iosBundle === null || iosBundle === void 0 ? void 0 : iosBundle.id) !== null && _a !== void 0 ? _a : "null"}, "androidBundleId": ${(_b = androidBundle === null || androidBundle === void 0 ? void 0 : androidBundle.id) !== null && _b !== void 0 ? _b : "null"}}`);
            }
            catch (err) {
                (0, websocket_1.sendLog)(`Error during appconfig download: ${err.message}
${err.stack}`);
                throw (err);
            }
        }
        else {
            (0, websocket_1.sendLog)(`Publish app once before building!`);
            throw new Error("Published appconfig not found! Stopping build.");
        }
    });
}
function narrowOSName(val) {
    return (val === "ios") || (val === "android");
}
function narrowBuildMode(val) {
    return (val === "debug") || (val === "release") || (val === "distributable");
}
// TODO(gaurav) update the appconfig and bundletracker. The thing is not written for android yet
function buildApp(appId, os, mode, deviceId) {
    return __awaiter(this, void 0, void 0, function* () {
        const openedApp = (0, cliconfig_1.getOpenApp)(appId);
        try {
            if ((os === 'ios') && (['debug', 'release'].includes(mode)) && openedApp) {
                // sendLog("updating appconfig");
                // await updateAppConfig(openedApp);
                (0, websocket_1.sendLog)("Starting ios build");
                if (deviceId) {
                    yield installReleaseBuild(openedApp, deviceId);
                }
                else {
                    (0, websocket_1.sendLog)('DeviceId must be given for release build installtion');
                    throw new Error("No deviceId found");
                }
                // await archiveIOS(openedApp, mode === 'debug' ? 'Debug' : 'Release')
                // sendLog("Build finished! Exporting");
                // await exportIPA(openedApp);
                // sendLog("IPA exported!");
            }
            else if ((os === 'android') && (['release'].includes(mode)) && openedApp) {
                // sendLog("Updating appconfig");
                // await updateAppConfig(openedApp);
                (0, websocket_1.sendLog)("Starting android build");
                buildAndroid(appId, deviceId || 'none');
            }
            else {
                (0, websocket_1.sendLog)(`Cannot build variant ${os}, ${mode}`);
            }
        }
        catch (err) {
            console.error("App build failed");
            (0, websocket_1.sendLog)(`Build Failed!! ${err === null || err === void 0 ? void 0 : err.message} ${err === null || err === void 0 ? void 0 : err.stack}`);
        }
    });
}
function verifyExisting(at, appid, sdkpath) {
    return __awaiter(this, void 0, void 0, function* () {
        (0, websocket_1.sendLog)(`Checking if the existing project in ${at} is linkable`);
        // Check that remoteCode folder and subfolders exists 
        // Check that variables file has the correct appid and sdkpath
        (0, websocket_1.sendLog)(`Existing project verified!`);
    });
}
function getGitShas(appId) {
    return __awaiter(this, void 0, void 0, function* () {
        const openedApp = (0, cliconfig_1.getOpenApp)(appId);
        if (openedApp) {
            try {
                const { stdout: sdkStdOut, stderr: stderr1 } = yield (0, utils_1.exec)('git log -n 1 --format=format:%H', { cwd: path_1.default.resolve(openedApp.repoPath, '../ReactNativeTSProjeect') });
                if (stderr1) {
                    throw new Error(stderr1);
                }
                const sdksha = sdkStdOut.toString().trim();
                const { stdout: appStdOut, stderr } = yield (0, utils_1.exec)('git log -n 1 --format=format:%H', { cwd: path_1.default.resolve(openedApp.repoPath, 'remoteCode') });
                if (stderr) {
                    throw new Error(stderr);
                }
                const gitsha = appStdOut.toString().trim();
                return { err: false, gitsha, sdksha };
            }
            catch (err) {
                return { err };
            }
        }
        else {
            return { err: "Link not found!" };
        }
    });
}
function currentPluginsBundle(appid) {
    return __awaiter(this, void 0, void 0, function* () {
        const openedApp = (0, cliconfig_1.getOpenApp)(appid);
        if (!openedApp) {
            return { err: `Link not found for ${appid}` };
        }
        else {
            const entries = yield (0, promises_1.readdir)(path_1.default.resolve(openedApp.repoPath, 'remoteCode/plugins'), { withFileTypes: true });
            const plugins = entries.filter(it => it.isDirectory()).map(it => it.name);
            const hash = (0, js_sha256_1.sha256)(plugins.join('_')).toString();
            const generatedPluginBundle = path_1.default.resolve(openedApp.repoPath, 'remoteCode/generated', hash, 'dist/bundle.js');
            try {
                yield (0, promises_1.stat)(generatedPluginBundle);
            }
            catch (err) {
                if ((err === null || err === void 0 ? void 0 : err.code) === 'ENOENT') {
                    return { err: false, res: '' };
                }
                else {
                    return { err };
                }
            }
            return { err: false, res: generatedPluginBundle };
        }
    });
}
function currentNavigatorsBundle(appid) {
    return __awaiter(this, void 0, void 0, function* () {
        const openedApp = (0, cliconfig_1.getOpenApp)(appid);
        if (!openedApp) {
            return { err: `Link not found for ${appid}` };
        }
        else {
            const entries = yield (0, promises_1.readdir)(path_1.default.resolve(openedApp.repoPath, 'remoteCode/navigators'), { withFileTypes: true });
            const navigators = entries.filter(it => it.isDirectory()).map(it => it.name);
            let hash = (0, js_sha256_1.sha256)('navigators_' + navigators.join('_')).toString();
            const generatedNavsBundle = path_1.default.resolve(openedApp.repoPath, 'remoteCode/generated', hash, 'dist/bundle.js');
            try {
                yield (0, promises_1.stat)(generatedNavsBundle);
            }
            catch (err) {
                if ((err === null || err === void 0 ? void 0 : err.code) === 'ENOENT') {
                    return { err: false, res: '' };
                }
                else {
                    return { err };
                }
            }
            return { err: false, res: generatedNavsBundle };
        }
    });
}
function getMobileBundle(appId, bundleName, os) {
    return __awaiter(this, void 0, void 0, function* () {
        const openedApp = (0, cliconfig_1.getOpenApp)(appId);
        if (openedApp) {
            try {
                const assetZip = path_1.default.resolve(openedApp.repoPath, `remoteCode/generated/bundles/${os}`, bundleName, 'bundle.zip');
                yield (0, promises_1.stat)(assetZip);
                return { err: false, assetZip };
            }
            catch (err) {
                return { err };
            }
        }
        else {
            return { err: new Error("link not found") };
        }
    });
}
function extractSeed(to, appid, sdkpath) {
    return __awaiter(this, void 0, void 0, function* () {
        console.log("Starting extraction");
        (0, websocket_1.sendLog)(`Starting project creation at ${to}`);
        const seedZip = path_1.default.resolve(__dirname, '../templates/apptileSeed.zip');
        return new Promise((resolve, reject) => {
            (0, node_fs_1.createReadStream)(seedZip).pipe(unzipper_1.default.Extract({ path: path_1.default.resolve(to) }))
                .on('close', () => __awaiter(this, void 0, void 0, function* () {
                (0, websocket_1.sendLog)("Seed unzipped to location. Setting up variables.");
                try {
                    const nets = os_1.default.networkInterfaces();
                    const localIpInterface = Object.values(nets).flatMap(it => it).filter((it) => (it.family === 'IPv4') && (it.address !== '127.0.0.1') && (it.netmask !== '255.255.0.0'));
                    if (localIpInterface.length !== 1) {
                        throw new Error("Could not determine local ip address");
                    }
                    const ip = localIpInterface[0].address;
                    const variablesFilePath = path_1.default.resolve(to, 'variables.js');
                    let variables = yield (0, promises_1.readFile)(variablesFilePath, { encoding: 'utf8' });
                    // change this to a patterned replace so that existing project can be linked
                    variables = variables.replace(/__APP_ID__/g, appid);
                    variables = variables.replace(/__PLUGIN_SERVER_URL__/g, `http://${ip}:3100/plugin-server`);
                    variables = variables.replace(/__PLUGIN_SOCKET_URL__/g, `ws://${ip}:3100/plugin-server/healthcheck`);
                    variables = variables.replace(/__SDK_PATH__/g, sdkpath);
                    yield (0, promises_1.writeFile)(variablesFilePath, variables);
                    const tsconfigPath = path_1.default.resolve(to, 'tsconfig.json');
                    let tsconfig = yield (0, promises_1.readFile)(tsconfigPath, { encoding: 'utf8' });
                    tsconfig = tsconfig.replace(/__SDK_PATH__/g, sdkpath);
                    yield (0, promises_1.writeFile)(tsconfigPath, tsconfig);
                    const appDelegatePath = path_1.default.resolve(to, 'ios/apptileSeed/AppDelegate.mm');
                    let appDelegate = yield (0, promises_1.readFile)(appDelegatePath, { encoding: 'utf8' });
                    appDelegate = appDelegate.replace('__METRO_IP__', `http://${ip}:8081`);
                    yield (0, promises_1.writeFile)(appDelegatePath, appDelegate);
                    (0, websocket_1.sendLog)("Start installation");
                    // Non blocking installs
                    npmInstall(to)
                        .then(() => podInstall(to))
                        .catch(err => {
                        console.error("error during pod install", err);
                        (0, websocket_1.sendLog)(`Pod install failed: ${err.message} ${err.stack}`);
                    });
                    resolve({});
                }
                catch (err) {
                    (0, websocket_1.sendLog)(`Failed to replace variables ${err}`);
                    reject(err);
                }
            }))
                .on('error', (err) => {
                (0, websocket_1.sendLog)(`Failed when trying to unzip seed ${err.message}`);
                console.error(err);
                reject(err);
            });
        });
    });
}
