"use strict";
/*
 * Copyright 2016 balena.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.statuses = exports.COMPATIBILITY_STATUS_TYPES = exports.LARGE_DRIVE_SIZE = void 0;
exports.isSystemDrive = isSystemDrive;
exports.isSourceDrive = isSourceDrive;
exports.isDriveLargeEnough = isDriveLargeEnough;
exports.isDriveValid = isDriveValid;
exports.isDriveSizeRecommended = isDriveSizeRecommended;
exports.isDriveSizeLarge = isDriveSizeLarge;
exports.getDriveImageCompatibilityStatuses = getDriveImageCompatibilityStatuses;
exports.getListDriveImageCompatibilityStatuses = getListDriveImageCompatibilityStatuses;
exports.hasDriveImageCompatibilityStatus = hasDriveImageCompatibilityStatus;
const lodash_1 = require("lodash");
const pathIsInside = __importStar(require("path-is-inside"));
const messages = __importStar(require("./messages"));
/**
 * @summary The default unknown size for things such as images and drives
 */
const UNKNOWN_SIZE = 0;
/**
 * @summary Check if a drive is a system drive
 */
function isSystemDrive(drive) {
    return Boolean(drive.isSystem);
}
function sourceIsInsideDrive(source, drive) {
    for (const mountpoint of drive.mountpoints || []) {
        if (pathIsInside(source, mountpoint.path)) {
            return true;
        }
    }
    return false;
}
/**
 * @summary Check if a drive is source drive
 *
 * @description
 * In the context of Etcher, a source drive is a drive
 * containing the image.
 */
function isSourceDrive(drive, selection) {
    if (selection) {
        if (selection.drive) {
            return selection.drive.device === drive.device;
        }
        if (selection.path) {
            return sourceIsInsideDrive(selection.path, drive);
        }
    }
    return false;
}
/**
 * @summary Check if a drive is large enough for an image
 */
function isDriveLargeEnough(drive, image) {
    const driveSize = drive.size || UNKNOWN_SIZE;
    if (image === undefined) {
        return true;
    }
    if (image.isSizeEstimated) {
        // If the drive size is smaller than the original image size, and
        // the final image size is just an estimation, then we stop right
        // here, based on the assumption that the final size will never
        // be less than the original size.
        if (driveSize < (image.compressedSize || UNKNOWN_SIZE)) {
            return false;
        }
        // If the final image size is just an estimation then consider it
        // large enough. In the worst case, the user gets an error saying
        // the drive has ran out of space, instead of prohibiting the flash
        // at all, when the estimation may be wrong.
        return true;
    }
    return driveSize >= (image.size || UNKNOWN_SIZE);
}
/**
 * @summary Check if a drive is valid, i.e. large enough for an image
 */
function isDriveValid(drive, image, write = true) {
    return (!write ||
        (!drive.disabled &&
            isDriveLargeEnough(drive, image) &&
            !isSourceDrive(drive, image)));
}
/**
 * @summary Check if a drive meets the recommended drive size suggestion
 *
 * @description
 * If the image doesn't have a recommended size, this function returns true.
 */
function isDriveSizeRecommended(drive, image) {
    const driveSize = drive.size || UNKNOWN_SIZE;
    return driveSize >= (image?.recommendedDriveSize || UNKNOWN_SIZE);
}
/**
 * @summary 128GB
 */
exports.LARGE_DRIVE_SIZE = 128e9;
/**
 * @summary Check whether a drive's size is 'large'
 */
function isDriveSizeLarge(drive) {
    const driveSize = drive.size || UNKNOWN_SIZE;
    return driveSize > exports.LARGE_DRIVE_SIZE;
}
/**
 * @summary Drive/image compatibility status types.
 *
 * @description
 * Status types classifying what kind of message it is, i.e. error, warning.
 */
exports.COMPATIBILITY_STATUS_TYPES = {
    WARNING: 1,
    ERROR: 2,
};
exports.statuses = {
    locked: {
        type: exports.COMPATIBILITY_STATUS_TYPES.ERROR,
        message: messages.compatibility.locked(),
    },
    system: {
        type: exports.COMPATIBILITY_STATUS_TYPES.WARNING,
        message: messages.compatibility.system(),
    },
    containsImage: {
        type: exports.COMPATIBILITY_STATUS_TYPES.ERROR,
        message: messages.compatibility.containsImage(),
    },
    large: {
        type: exports.COMPATIBILITY_STATUS_TYPES.WARNING,
        message: messages.compatibility.largeDrive(),
    },
    small: {
        type: exports.COMPATIBILITY_STATUS_TYPES.ERROR,
        message: messages.compatibility.tooSmall(),
    },
    sizeNotRecommended: {
        type: exports.COMPATIBILITY_STATUS_TYPES.WARNING,
        message: messages.compatibility.sizeNotRecommended(),
    },
};
/**
 * @summary Get drive/image compatibility in an object
 *
 * @description
 * Given an image and a drive, return their compatibility status object
 * containing the status type (ERROR, WARNING), and accompanying
 * status message.
 *
 * @returns {Object[]} list of compatibility status objects
 */
function getDriveImageCompatibilityStatuses(drive, image, write) {
    const statusList = [];
    // Mind the order of the if-statements if you modify.
    if (drive.isReadOnly && write) {
        statusList.push({
            type: exports.COMPATIBILITY_STATUS_TYPES.ERROR,
            message: messages.compatibility.locked(),
        });
    }
    if (!(0, lodash_1.isNil)(drive) &&
        !(0, lodash_1.isNil)(drive.size) &&
        !isDriveLargeEnough(drive, image)) {
        statusList.push(exports.statuses.small);
    }
    else {
        // Avoid showing "large drive" with "system drive" status
        if (isSystemDrive(drive)) {
            statusList.push(exports.statuses.system);
        }
        else if (isDriveSizeLarge(drive)) {
            statusList.push(exports.statuses.large);
        }
        if (isSourceDrive(drive, image)) {
            statusList.push(exports.statuses.containsImage);
        }
        if (image !== undefined &&
            !(0, lodash_1.isNil)(drive) &&
            !isDriveSizeRecommended(drive, image)) {
            statusList.push(exports.statuses.sizeNotRecommended);
        }
    }
    return statusList;
}
/**
 * @summary Get drive/image compatibility status for many drives
 *
 * @description
 * Given an image and a list of drives, return all compatibility status objects,
 * containing the status type (ERROR, WARNING), and accompanying status message.
 */
function getListDriveImageCompatibilityStatuses(drives, image, write) {
    return drives.flatMap((drive) => {
        return getDriveImageCompatibilityStatuses(drive, image, write);
    });
}
/**
 * @summary Does the drive/image pair have at least one compatibility status?
 *
 * @description
 * Given an image and a drive, return whether they have a connected compatibility status object.
 */
function hasDriveImageCompatibilityStatus(drive, image, write) {
    return Boolean(getDriveImageCompatibilityStatuses(drive, image, write).length);
}
