
import { joinPath, isEqualOrParent } from '../../../base/common/resources.js';
import Severity$1 from '../../../base/common/severity.js';
import { localize } from '../../../nls.js';
import { semverExports } from '../../../../../../_virtual/semver.js';
import { parseApiProposals } from './extensions.js';
import { allApiProposals } from './extensionsApiProposals.js';

const VERSION_REGEXP = /^(\^|>=)?((\d+)|x)\.((\d+)|x)\.((\d+)|x)(\-.*)?$/;
const NOT_BEFORE_REGEXP = /^-(\d{4})(\d{2})(\d{2})(\d{2})?(\d{2})?$/;
function isValidVersionStr(version) {
    version = version.trim();
    return (version === "*" || VERSION_REGEXP.test(version));
}
function parseVersion(version) {
    if (!isValidVersionStr(version)) {
        return null;
    }
    version = version.trim();
    if (version === "*") {
        return {
            hasCaret: false,
            hasGreaterEquals: false,
            majorBase: 0,
            majorMustEqual: false,
            minorBase: 0,
            minorMustEqual: false,
            patchBase: 0,
            patchMustEqual: false,
            preRelease: null
        };
    }
    const m = version.match(VERSION_REGEXP);
    if (!m) {
        return null;
    }
    return {
        hasCaret: m[1] === "^",
        hasGreaterEquals: m[1] === ">=",
        majorBase: m[2] === "x" ? 0 : parseInt(m[2], 10),
        majorMustEqual: (m[2] === "x" ? false : true),
        minorBase: m[4] === "x" ? 0 : parseInt(m[4], 10),
        minorMustEqual: (m[4] === "x" ? false : true),
        patchBase: m[6] === "x" ? 0 : parseInt(m[6], 10),
        patchMustEqual: (m[6] === "x" ? false : true),
        preRelease: m[8] || null
    };
}
function normalizeVersion(version) {
    if (!version) {
        return null;
    }
    const majorBase = version.majorBase;
    const majorMustEqual = version.majorMustEqual;
    const minorBase = version.minorBase;
    let minorMustEqual = version.minorMustEqual;
    const patchBase = version.patchBase;
    let patchMustEqual = version.patchMustEqual;
    if (version.hasCaret) {
        if (majorBase === 0) {
            patchMustEqual = false;
        } else {
            minorMustEqual = false;
            patchMustEqual = false;
        }
    }
    let notBefore = 0;
    if (version.preRelease) {
        const match = NOT_BEFORE_REGEXP.exec(version.preRelease);
        if (match) {
            const [, year, month, day, hours, minutes] = match;
            notBefore = Date.UTC(
                Number(year),
                Number(month) - 1,
                Number(day),
                Number(hours) || 0,
                Number(minutes) || 0
            );
        }
    }
    return {
        majorBase: majorBase,
        majorMustEqual: majorMustEqual,
        minorBase: minorBase,
        minorMustEqual: minorMustEqual,
        patchBase: patchBase,
        patchMustEqual: patchMustEqual,
        isMinimum: version.hasGreaterEquals,
        notBefore
    };
}
function isValidVersion(_inputVersion, _inputDate, _desiredVersion) {
    let version;
    if (typeof _inputVersion === "string") {
        version = normalizeVersion(parseVersion(_inputVersion));
    } else {
        version = _inputVersion;
    }
    let productTs;
    if (_inputDate instanceof Date) {
        productTs = _inputDate.getTime();
    } else if (typeof _inputDate === "string") {
        productTs = ( new Date(_inputDate)).getTime();
    }
    let desiredVersion;
    if (typeof _desiredVersion === "string") {
        desiredVersion = normalizeVersion(parseVersion(_desiredVersion));
    } else {
        desiredVersion = _desiredVersion;
    }
    if (!version || !desiredVersion) {
        return false;
    }
    const majorBase = version.majorBase;
    const minorBase = version.minorBase;
    const patchBase = version.patchBase;
    let desiredMajorBase = desiredVersion.majorBase;
    let desiredMinorBase = desiredVersion.minorBase;
    let desiredPatchBase = desiredVersion.patchBase;
    const desiredNotBefore = desiredVersion.notBefore;
    let majorMustEqual = desiredVersion.majorMustEqual;
    let minorMustEqual = desiredVersion.minorMustEqual;
    let patchMustEqual = desiredVersion.patchMustEqual;
    if (desiredVersion.isMinimum) {
        if (majorBase > desiredMajorBase) {
            return true;
        }
        if (majorBase < desiredMajorBase) {
            return false;
        }
        if (minorBase > desiredMinorBase) {
            return true;
        }
        if (minorBase < desiredMinorBase) {
            return false;
        }
        if (productTs && productTs < desiredNotBefore) {
            return false;
        }
        return patchBase >= desiredPatchBase;
    }
    if (majorBase === 1 && desiredMajorBase === 0 && (!majorMustEqual || !minorMustEqual || !patchMustEqual)) {
        desiredMajorBase = 1;
        desiredMinorBase = 0;
        desiredPatchBase = 0;
        majorMustEqual = true;
        minorMustEqual = false;
        patchMustEqual = false;
    }
    if (majorBase < desiredMajorBase) {
        return false;
    }
    if (majorBase > desiredMajorBase) {
        return (!majorMustEqual);
    }
    if (minorBase < desiredMinorBase) {
        return false;
    }
    if (minorBase > desiredMinorBase) {
        return (!minorMustEqual);
    }
    if (patchBase < desiredPatchBase) {
        return false;
    }
    if (patchBase > desiredPatchBase) {
        return (!patchMustEqual);
    }
    if (productTs && productTs < desiredNotBefore) {
        return false;
    }
    return true;
}
function validateExtensionManifest(
    productVersion,
    productDate,
    extensionLocation,
    extensionManifest,
    extensionIsBuiltin,
    validateApiVersion
) {
    const validations = [];
    if (typeof extensionManifest.publisher !== "undefined" && typeof extensionManifest.publisher !== "string") {
        validations.push([Severity$1.Error, ( localize(1988, "property publisher must be of type `string`."))]);
        return validations;
    }
    if (typeof extensionManifest.name !== "string") {
        validations.push([Severity$1.Error, ( localize(1989, "property `{0}` is mandatory and must be of type `string`", "name"))]);
        return validations;
    }
    if (typeof extensionManifest.version !== "string") {
        validations.push([Severity$1.Error, ( localize(
            1990,
            "property `{0}` is mandatory and must be of type `string`",
            "version"
        ))]);
        return validations;
    }
    if (!extensionManifest.engines) {
        validations.push([Severity$1.Error, ( localize(
            1991,
            "property `{0}` is mandatory and must be of type `object`",
            "engines"
        ))]);
        return validations;
    }
    if (typeof extensionManifest.engines.vscode !== "string") {
        validations.push([Severity$1.Error, ( localize(
            1992,
            "property `{0}` is mandatory and must be of type `string`",
            "engines.vscode"
        ))]);
        return validations;
    }
    if (typeof extensionManifest.extensionDependencies !== "undefined") {
        if (!isStringArray(extensionManifest.extensionDependencies)) {
            validations.push([Severity$1.Error, ( localize(
                1993,
                "property `{0}` can be omitted or must be of type `string[]`",
                "extensionDependencies"
            ))]);
            return validations;
        }
    }
    if (typeof extensionManifest.extensionAffinity !== "undefined") {
        if (!isStringArray(extensionManifest.extensionAffinity)) {
            validations.push([Severity$1.Error, ( localize(
                1994,
                "property `{0}` can be omitted or must be of type `string[]`",
                "extensionAffinity"
            ))]);
            return validations;
        }
    }
    if (typeof extensionManifest.activationEvents !== "undefined") {
        if (!isStringArray(extensionManifest.activationEvents)) {
            validations.push([Severity$1.Error, ( localize(
                1995,
                "property `{0}` can be omitted or must be of type `string[]`",
                "activationEvents"
            ))]);
            return validations;
        }
        if (typeof extensionManifest.main === "undefined" && typeof extensionManifest.browser === "undefined") {
            validations.push([Severity$1.Error, ( localize(
                1996,
                "property `{0}` should be omitted if the extension doesn't have a `{1}` or `{2}` property.",
                "activationEvents",
                "main",
                "browser"
            ))]);
            return validations;
        }
    }
    if (typeof extensionManifest.extensionKind !== "undefined") {
        if (typeof extensionManifest.main === "undefined") {
            validations.push([Severity$1.Warning, ( localize(
                1997,
                "property `{0}` can be defined only if property `main` is also defined.",
                "extensionKind"
            ))]);
        }
    }
    if (typeof extensionManifest.main !== "undefined") {
        if (typeof extensionManifest.main !== "string") {
            validations.push([Severity$1.Error, ( localize(1998, "property `{0}` can be omitted or must be of type `string`", "main"))]);
            return validations;
        } else {
            const mainLocation = joinPath(extensionLocation, extensionManifest.main);
            if (!isEqualOrParent(mainLocation, extensionLocation)) {
                validations.push([Severity$1.Warning, ( localize(
                    1999,
                    "Expected `main` ({0}) to be included inside extension's folder ({1}). This might make the extension non-portable.",
                    mainLocation.path,
                    extensionLocation.path
                ))]);
            }
        }
    }
    if (typeof extensionManifest.browser !== "undefined") {
        if (typeof extensionManifest.browser !== "string") {
            validations.push([Severity$1.Error, ( localize(
                2000,
                "property `{0}` can be omitted or must be of type `string`",
                "browser"
            ))]);
            return validations;
        } else {
            const browserLocation = joinPath(extensionLocation, extensionManifest.browser);
            if (!isEqualOrParent(browserLocation, extensionLocation)) {
                validations.push([Severity$1.Warning, ( localize(
                    2001,
                    "Expected `browser` ({0}) to be included inside extension's folder ({1}). This might make the extension non-portable.",
                    browserLocation.path,
                    extensionLocation.path
                ))]);
            }
        }
    }
    if (!semverExports.valid(extensionManifest.version)) {
        validations.push([Severity$1.Error, ( localize(2002, "Extension version is not semver compatible."))]);
        return validations;
    }
    const notices = [];
    const validExtensionVersion = isValidExtensionVersion(
        productVersion,
        productDate,
        extensionManifest,
        extensionIsBuiltin,
        notices
    );
    if (!validExtensionVersion) {
        for (const notice of notices) {
            validations.push([Severity$1.Error, notice]);
        }
    }
    if (validateApiVersion && extensionManifest.enabledApiProposals?.length) {
        const incompatibleNotices = [];
        if (!areApiProposalsCompatible([...extensionManifest.enabledApiProposals], incompatibleNotices)) {
            for (const notice of incompatibleNotices) {
                validations.push([Severity$1.Error, notice]);
            }
        }
    }
    return validations;
}
function isValidExtensionVersion(
    productVersion,
    productDate,
    extensionManifest,
    extensionIsBuiltin,
    notices
) {
    if (extensionIsBuiltin || (typeof extensionManifest.main === "undefined" && typeof extensionManifest.browser === "undefined")) {
        return true;
    }
    return isVersionValid(productVersion, productDate, extensionManifest.engines.vscode, notices);
}
function isEngineValid(engine, version, date) {
    return engine === "*" || isVersionValid(version, date, engine);
}
function areApiProposalsCompatible(apiProposals, arg1) {
    if (apiProposals.length === 0) {
        return true;
    }
    const notices = Array.isArray(arg1) ? arg1 : undefined;
    const productApiProposals = (Array.isArray(arg1) ? undefined : arg1) ?? allApiProposals;
    const incompatibleProposals = [];
    const parsedProposals = parseApiProposals(apiProposals);
    for (const {
        proposalName,
        version
    } of parsedProposals) {
        if (!version) {
            continue;
        }
        const existingProposal = productApiProposals[proposalName];
        if (existingProposal?.version !== version) {
            incompatibleProposals.push(proposalName);
        }
    }
    if (incompatibleProposals.length) {
        if (notices) {
            if (incompatibleProposals.length === 1) {
                notices.push(( localize(
                    2003,
                    "This extension is using the API proposal '{0}' that is not compatible with the current version of VS Code.",
                    incompatibleProposals[0]
                )));
            } else {
                notices.push(( localize(
                    2004,
                    "This extension is using the API proposals {0} and '{1}' that are not compatible with the current version of VS Code.",
                    ( incompatibleProposals.slice(0, incompatibleProposals.length - 1).map(p => `'${p}'`)).join(", "),
                    incompatibleProposals[incompatibleProposals.length - 1]
                )));
            }
        }
        return false;
    }
    return true;
}
function isVersionValid(currentVersion, date, requestedVersion, notices = []) {
    const desiredVersion = normalizeVersion(parseVersion(requestedVersion));
    if (!desiredVersion) {
        notices.push(( localize(
            2005,
            "Could not parse `engines.vscode` value {0}. Please use, for example: ^1.22.0, ^1.22.x, etc.",
            requestedVersion
        )));
        return false;
    }
    if (desiredVersion.majorBase === 0) {
        if (!desiredVersion.majorMustEqual || !desiredVersion.minorMustEqual) {
            notices.push(( localize(
                2006,
                "Version specified in `engines.vscode` ({0}) is not specific enough. For vscode versions before 1.0.0, please define at a minimum the major and minor desired version. E.g. ^0.10.0, 0.10.x, 0.11.0, etc.",
                requestedVersion
            )));
            return false;
        }
    } else {
        if (!desiredVersion.majorMustEqual) {
            notices.push(( localize(
                2007,
                "Version specified in `engines.vscode` ({0}) is not specific enough. For vscode versions after 1.0.0, please define at a minimum the major desired version. E.g. ^1.10.0, 1.10.x, 1.x.x, 2.x.x, etc.",
                requestedVersion
            )));
            return false;
        }
    }
    if (!isValidVersion(currentVersion, date, desiredVersion)) {
        notices.push(( localize(
            2008,
            "Extension is not compatible with Code {0}. Extension requires: {1}.",
            currentVersion,
            requestedVersion
        )));
        return false;
    }
    return true;
}
function isStringArray(arr) {
    if (!Array.isArray(arr)) {
        return false;
    }
    for (let i = 0, len = arr.length; i < len; i++) {
        if (typeof arr[i] !== "string") {
            return false;
        }
    }
    return true;
}

export { areApiProposalsCompatible, isEngineValid, isValidExtensionVersion, isValidVersion, isValidVersionStr, normalizeVersion, parseVersion, validateExtensionManifest };
