"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: Object.getOwnPropertyDescriptor(all, name).get
    });
}
_export(exports, {
    get bootAsync () {
        return bootAsync;
    },
    get bootDeviceAsync () {
        return bootDeviceAsync;
    },
    get getBootedSimulatorsAsync () {
        return getBootedSimulatorsAsync;
    },
    get getContainerPathAsync () {
        return getContainerPathAsync;
    },
    get getDevicesAsync () {
        return getDevicesAsync;
    },
    get getInfoPlistValueAsync () {
        return getInfoPlistValueAsync;
    },
    get installAsync () {
        return installAsync;
    },
    get isDeviceBootedAsync () {
        return isDeviceBootedAsync;
    },
    get isOSType () {
        return isOSType;
    },
    get openAppIdAsync () {
        return openAppIdAsync;
    },
    get openUrlAsync () {
        return openUrlAsync;
    },
    get simctlAsync () {
        return simctlAsync;
    },
    get uninstallAsync () {
        return uninstallAsync;
    }
});
function _spawnasync() {
    const data = /*#__PURE__*/ _interop_require_default(require("@expo/spawn-async"));
    _spawnasync = function() {
        return data;
    };
    return data;
}
function _bplistcreator() {
    const data = /*#__PURE__*/ _interop_require_default(require("bplist-creator"));
    _bplistcreator = function() {
        return data;
    };
    return data;
}
function _fs() {
    const data = /*#__PURE__*/ _interop_require_default(require("fs"));
    _fs = function() {
        return data;
    };
    return data;
}
function _os() {
    const data = /*#__PURE__*/ _interop_require_default(require("os"));
    _os = function() {
        return data;
    };
    return data;
}
function _path() {
    const data = /*#__PURE__*/ _interop_require_default(require("path"));
    _path = function() {
        return data;
    };
    return data;
}
const _xcrun = require("./xcrun");
const _log = /*#__PURE__*/ _interop_require_wildcard(require("../../../log"));
const _errors = require("../../../utils/errors");
const _fn = require("../../../utils/fn");
const _plist = require("../../../utils/plist");
const _profile = require("../../../utils/profile");
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
function _getRequireWildcardCache(nodeInterop) {
    if (typeof WeakMap !== "function") return null;
    var cacheBabelInterop = new WeakMap();
    var cacheNodeInterop = new WeakMap();
    return (_getRequireWildcardCache = function(nodeInterop) {
        return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
    })(nodeInterop);
}
function _interop_require_wildcard(obj, nodeInterop) {
    if (!nodeInterop && obj && obj.__esModule) {
        return obj;
    }
    if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
        return {
            default: obj
        };
    }
    var cache = _getRequireWildcardCache(nodeInterop);
    if (cache && cache.has(obj)) {
        return cache.get(obj);
    }
    var newObj = {
        __proto__: null
    };
    var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
    for(var key in obj){
        if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
            var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
            if (desc && (desc.get || desc.set)) {
                Object.defineProperty(newObj, key, desc);
            } else {
                newObj[key] = obj[key];
            }
        }
    }
    newObj.default = obj;
    if (cache) {
        cache.set(obj, newObj);
    }
    return newObj;
}
const debug = require('debug')('expo:simctl');
function isOSType(value) {
    if (!value || typeof value !== 'string') return false;
    const knownTypes = [
        'iOS',
        'tvOS',
        'watchOS',
        'macOS'
    ];
    if (!knownTypes.includes(value)) {
        _log.warn(`Unknown OS type: ${value}. Expected one of: ${knownTypes.join(', ')}`);
    }
    return true;
}
async function getContainerPathAsync(device, { appId }) {
    try {
        const { stdout } = await simctlAsync([
            'get_app_container',
            resolveId(device),
            appId
        ]);
        return stdout.trim();
    } catch (error) {
        var _error_stderr;
        if ((_error_stderr = error.stderr) == null ? void 0 : _error_stderr.match(/No such file or directory/)) {
            return null;
        }
        throw error;
    }
}
async function getInfoPlistValueAsync(device, { appId, key, containerPath }) {
    const ensuredContainerPath = containerPath ?? await getContainerPathAsync(device, {
        appId
    });
    if (ensuredContainerPath) {
        try {
            const { output } = await (0, _spawnasync().default)('defaults', [
                'read',
                `${ensuredContainerPath}/Info`,
                key
            ], {
                stdio: 'pipe'
            });
            return output.join('\n').trim();
        } catch  {
            return null;
        }
    }
    return null;
}
/** Rewrite the simulator permissions to allow opening deep links without needing to prompt the user first. */ async function updateSimulatorLinkingPermissionsAsync(device, { url, appId }) {
    if (!device.udid || !appId) {
        debug('Skipping deep link permissions as missing properties could not be found:', {
            url,
            appId,
            udid: device.udid
        });
        return;
    }
    debug('Rewriting simulator permissions to support deep linking:', {
        url,
        appId,
        udid: device.udid
    });
    let scheme;
    try {
        // Attempt to extract the scheme from the URL.
        scheme = new URL(url).protocol.slice(0, -1);
    } catch (error) {
        debug(`Could not parse the URL scheme: ${error.message}`);
        return;
    }
    // Get the hard-coded path to the simulator's scheme approval plist file.
    const plistPath = _path().default.join(_os().default.homedir(), `Library/Developer/CoreSimulator/Devices`, device.udid, `data/Library/Preferences/com.apple.launchservices.schemeapproval.plist`);
    const plistData = _fs().default.existsSync(plistPath) ? await (0, _plist.parsePlistAsync)(plistPath) : // Can be tested by launching a new simulator or by deleting the file and relaunching the simulator.
    {};
    debug('Allowed links:', plistData);
    const key = `com.apple.CoreSimulator.CoreSimulatorBridge-->${scheme}`;
    // Replace any existing value for the scheme with the new appId.
    plistData[key] = appId;
    debug('Allowing deep link:', {
        key,
        appId
    });
    try {
        const data = (0, _bplistcreator().default)(plistData);
        // Write the updated plist back to disk
        await _fs().default.promises.writeFile(plistPath, data);
    } catch (error) {
        _log.warn(`Could not update simulator linking permissions: ${error.message}`);
    }
}
const updateSimulatorLinkingPermissionsAsyncMemo = (0, _fn.memoize)(updateSimulatorLinkingPermissionsAsync);
async function openUrlAsync(device, options) {
    if (options.appId) {
        await (0, _profile.profile)(updateSimulatorLinkingPermissionsAsyncMemo, 'updateSimulatorLinkingPermissionsAsync')({
            udid: device.udid
        }, options);
    }
    try {
        // Skip logging since this is likely to fail.
        await simctlAsync([
            'openurl',
            resolveId(device),
            options.url
        ]);
    } catch (error) {
        var _error_stderr;
        if (!((_error_stderr = error.stderr) == null ? void 0 : _error_stderr.match(/Unable to lookup in current state: Shut/))) {
            throw error;
        }
        // If the device was in a weird in-between state ("Shutting Down" or "Shutdown"), then attempt to reboot it and try again.
        // This can happen when quitting the Simulator app, and immediately pressing `i` to reopen the project.
        // First boot the simulator
        await bootDeviceAsync({
            udid: resolveId(device)
        });
        // Finally, try again...
        return await openUrlAsync(device, options);
    }
}
async function openAppIdAsync(device, options) {
    const results = await openAppIdInternalAsync(device, options);
    // Similar to 194, this is a conformance issue which indicates that the given device has no app that can handle our launch request.
    if (results.status === 4) {
        throw new _errors.CommandError('APP_NOT_INSTALLED', results.stderr);
    }
    return results;
}
async function openAppIdInternalAsync(device, options) {
    try {
        return await simctlAsync([
            'launch',
            resolveId(device),
            options.appId
        ]);
    } catch (error) {
        if ('status' in error) {
            return error;
        }
        throw error;
    }
}
async function bootAsync(device) {
    await bootDeviceAsync(device);
    return isDeviceBootedAsync(device);
}
async function getBootedSimulatorsAsync() {
    const simulatorDeviceInfo = await getRuntimesAsync('devices');
    return Object.values(simulatorDeviceInfo.devices).flatMap((runtime)=>runtime.filter((device)=>device.state === 'Booted'));
}
async function isDeviceBootedAsync(device) {
    // Simulators can be booted even if the app isn't running :(
    const devices = await getBootedSimulatorsAsync();
    if (device.udid) {
        return devices.find((bootedDevice)=>bootedDevice.udid === device.udid) ?? null;
    }
    return devices[0] ?? null;
}
async function bootDeviceAsync(device) {
    try {
        // Skip logging since this is likely to fail.
        await simctlAsync([
            'boot',
            device.udid
        ]);
    } catch (error) {
        var _error_stderr;
        if (!((_error_stderr = error.stderr) == null ? void 0 : _error_stderr.match(/Unable to boot device in current state: Booted/))) {
            throw error;
        }
    }
}
async function installAsync(device, options) {
    return simctlAsync([
        'install',
        resolveId(device),
        options.filePath
    ]);
}
async function uninstallAsync(device, options) {
    return simctlAsync([
        'uninstall',
        resolveId(device),
        options.appId
    ]);
}
function parseSimControlJSONResults(input) {
    try {
        return JSON.parse(input);
    } catch (error) {
        // Nov 15, 2020: Observed this can happen when opening the simulator and the simulator prompts the user to update the xcode command line tools.
        // Unexpected token I in JSON at position 0
        if (error.message.includes('Unexpected token')) {
            _log.error(`Apple's simctl returned malformed JSON:\n${input}`);
        }
        throw error;
    }
}
/** Get all runtime devices given a certain type. */ async function getRuntimesAsync(type, query) {
    const result = await simctlAsync([
        'list',
        type,
        '--json',
        query
    ]);
    const info = parseSimControlJSONResults(result.stdout);
    for (const runtime of Object.keys(info.devices)){
        // Given a string like 'com.apple.CoreSimulator.SimRuntime.tvOS-13-4'
        const runtimeSuffix = runtime.split('com.apple.CoreSimulator.SimRuntime.').pop();
        // Create an array [tvOS, 13, 4]
        const [osType, ...osVersionComponents] = runtimeSuffix.split('-');
        // Join the end components [13, 4] -> '13.4'
        const osVersion = osVersionComponents.join('.');
        const sims = info.devices[runtime];
        if (sims) {
            for (const device of sims){
                device.runtime = runtime;
                device.osVersion = osVersion;
                device.windowName = `${device.name} (${osVersion})`;
                device.osType = osType;
            }
        }
    }
    return info;
}
async function getDevicesAsync() {
    const simulatorDeviceInfo = await getRuntimesAsync('devices');
    return Object.values(simulatorDeviceInfo.devices).flat();
}
async function simctlAsync(args, options) {
    try {
        return await (0, _xcrun.xcrunAsync)([
            'simctl',
            ...args
        ], options);
    } catch (error) {
        if ((0, _xcrun.isSpawnResultError)(error)) {
        // TODO: Add more tips.
        // if (error.status === 115) {
        // }
        }
        throw error;
    }
}
function resolveId(device) {
    return device.udid ?? 'booted';
}

//# sourceMappingURL=simctl.js.map