"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CopilotToken = void 0;
exports.containsInternalOrg = containsInternalOrg;
exports.containsVSCodeOrg = containsVSCodeOrg;
exports.validateTokenEnvelope = validateTokenEnvelope;
exports.isTokenEnvelope = isTokenEnvelope;
exports.isErrorEnvelope = isErrorEnvelope;
exports.isStandardErrorEnvelope = isStandardErrorEnvelope;
exports.createTestExtendedTokenInfo = createTestExtendedTokenInfo;
const validator_1 = require("../../configuration/common/validator");
/**
 * A function used to determine if the org list contains an internal organization
 * @param orgList The list of organizations the user is a member of
 * Whether or not it contains an internal org
 */
function containsInternalOrg(orgList) {
    return containsGitHubOrg(orgList) || containsMicrosoftOrg(orgList);
}
/**
 * A function used to determine if the org list contains a GitHub organization
 * @param orgList The list of organizations the user is a member of
 * Whether or not it contains a GitHub org
 */
function containsGitHubOrg(orgList) {
    const GITHUB_ORGANIZATIONS = ['4535c7beffc844b46bb1ed4aa04d759a'];
    // Check if the user is part of an allowed organization.
    for (const org of orgList) {
        if (GITHUB_ORGANIZATIONS.includes(org)) {
            return true;
        }
    }
    return false;
}
/**
 * A function used to determine if the org list contains a Microsoft organization
 * @param orgList The list of organizations the user is a member of
 * Whether or not it contains a Microsoft org
 */
function containsMicrosoftOrg(orgList) {
    const MICROSOFT_ORGANIZATIONS = ['a5db0bcaae94032fe715fb34a5e4bce2', '7184f66dfcee98cb5f08a1cb936d5225',
        '1cb18ac6eedd49b43d74a1c5beb0b955', 'ea9395b9a9248c05ee6847cbd24355ed'];
    // Check if the user is part of a Microsoft organization.
    for (const org of orgList) {
        if (MICROSOFT_ORGANIZATIONS.includes(org)) {
            return true;
        }
    }
    return false;
}
/**
 * A function used to determine if the org list contains a VS Code organization
 * @param orgList The list of organizations the user is a member of
 * Whether or not it contains a VS Code org
 */
function containsVSCodeOrg(orgList) {
    const VSCODE_ORGANIZATIONS = ['551cca60ce19654d894e786220822482'];
    // Check if the user is part of a VS Code organization.
    for (const org of orgList) {
        if (VSCODE_ORGANIZATIONS.includes(org)) {
            return true;
        }
    }
    return false;
}
class CopilotToken {
    constructor(_info) {
        this._info = _info;
        this.tokenMap = this.parseToken(_info.token);
    }
    parseToken(token) {
        const result = new Map();
        const firstPart = token?.split(':')[0];
        const fields = firstPart?.split(';');
        for (const field of fields) {
            const [key, value] = field.split('=');
            result.set(key, value);
        }
        return result;
    }
    get token() {
        return this._info.token;
    }
    get sku() {
        return this._info.sku;
    }
    /**
     * Evaluates `has_cfi_access?` which is defined as `!has_cfb_access? && !has_cfe_access?`
     * (cfb = copilot for business, cfe = copilot for enterprise).
     * So it's also true for copilot free users.
     */
    get isIndividual() {
        return this._info.individual ?? false;
    }
    get organizationList() {
        return this._info.organization_list || [];
    }
    /**
     * Returns the list of organization logins that provide Copilot access to the user.
     * These are the organizations through which the user has a Copilot subscription (Business/Enterprise).
     */
    get organizationLoginList() {
        return this._info.organization_login_list || [];
    }
    get enterpriseList() {
        return this._info.enterprise_list || [];
    }
    get endpoints() {
        return this._info.endpoints;
    }
    get isInternal() {
        return containsInternalOrg(this.organizationList);
    }
    get isMicrosoftInternal() {
        return containsMicrosoftOrg(this.organizationList);
    }
    get isGitHubInternal() {
        return containsGitHubOrg(this.organizationList);
    }
    get isFreeUser() {
        return this.sku === 'free_limited_copilot';
    }
    get isNoAuthUser() {
        return this.sku === 'no_auth_limited_copilot';
    }
    get isManagedPlan() {
        const plan = this.copilotPlan;
        return plan === 'business' || plan === 'enterprise';
    }
    get isUsageBasedBilling() {
        return this._info.token_based_billing === true;
    }
    get isChatQuotaExceeded() {
        return this.isFreeUser && (this._info.limited_user_quotas?.chat ?? 1) <= 0;
    }
    get isCompletionsQuotaExceeded() {
        return this.isFreeUser && (this._info.limited_user_quotas?.completions ?? 1) <= 0;
    }
    get codeQuoteEnabled() {
        return this._info.code_quote_enabled ?? false;
    }
    get isVscodeTeamMember() {
        return this._info.isVscodeTeamMember || containsVSCodeOrg(this.organizationList);
    }
    get codexAgentEnabled() {
        return this._info.codex_agent_enabled ?? false;
    }
    get copilotPlan() {
        if (this.isFreeUser) {
            return 'free';
        }
        const plan = this._info.copilot_plan;
        switch (plan) {
            case 'individual':
            case 'individual_pro':
            case 'individual_max':
            case 'business':
            case 'enterprise':
                return plan;
            default:
                // Default to 'individual' for unexpected values
                return 'individual';
        }
    }
    /**
     * Returns the raw copilot_plan string from the token payload without normalization.
     * Used when the exact plan value must be preserved (e.g. for per-SKU routing overrides
     * where individual_edu, individual_pro_plus, etc. need distinct handling).
     */
    get rawCopilotPlan() {
        if (this.isFreeUser) {
            return 'free';
        }
        return this._info.copilot_plan ?? 'individual';
    }
    get quotaInfo() {
        return { quota_snapshots: this._info.quota_snapshots, quota_reset_date: this._info.quota_reset_date };
    }
    get tokenBasedBilling() {
        return this._info.token_based_billing;
    }
    get username() {
        return this._info.username;
    }
    isTelemetryEnabled() {
        if (this._isTelemetryEnabled === undefined) {
            this._isTelemetryEnabled = this._info.telemetry === 'enabled';
        }
        return this._isTelemetryEnabled;
    }
    isPublicSuggestionsEnabled() {
        if (this._isPublicSuggestionsEnabled === undefined) {
            this._isPublicSuggestionsEnabled = this._info.public_suggestions === 'enabled';
        }
        return this._isPublicSuggestionsEnabled;
    }
    isCopilotIgnoreEnabled() {
        return this._info.copilotignore_enabled ?? false;
    }
    get isCopilotCodeReviewEnabled() {
        return this._info.code_review_enabled ?? (this.getTokenValue('ccr') === '1');
    }
    isEditorPreviewFeaturesEnabled() {
        // Editor preview features are disabled if the flag is present and set to 0
        return this.getTokenValue('editor_preview_features') !== '0';
    }
    isBlackbirdExternalIndexingEnabled() {
        return this.getTokenValue('blackbird_external_indexing') === '1';
    }
    isMcpEnabled() {
        // MCP is disabled if the flag is present and set to 0
        return this.getTokenValue('mcp') !== '0';
    }
    isClientBYOKEnabled() {
        return this.getTokenValue('client_byok') === '1';
    }
    getTokenValue(key) {
        return this.tokenMap.get(key);
    }
    isExpandedClientSideIndexingEnabled() {
        return this._info.blackbird_clientside_indexing === true;
    }
    isFcv1() {
        return this.tokenMap.get('fcv1') === '1';
    }
    /**
     * Is snippy in blocking mode
     */
    isSn() {
        return this.tokenMap.get('sn') === '1';
    }
}
exports.CopilotToken = CopilotToken;
//#region Validators
const notificationEnvelopeValidator = (0, validator_1.vObj)({
    message: (0, validator_1.vRequired)((0, validator_1.vString)()),
    notification_id: (0, validator_1.vRequired)((0, validator_1.vString)()),
    title: (0, validator_1.vRequired)((0, validator_1.vString)()),
    url: (0, validator_1.vRequired)((0, validator_1.vString)()),
});
const errorEnvelopeValidator = (0, validator_1.vObj)({
    can_signup_for_limited: (0, validator_1.vNullable)((0, validator_1.vBoolean)()),
    error_details: (0, validator_1.vRequired)(notificationEnvelopeValidator),
    message: (0, validator_1.vRequired)((0, validator_1.vString)()),
    reason: (0, validator_1.vString)(),
});
const tokenEnvelopeValidator = (0, validator_1.vObj)({
    token: (0, validator_1.vRequired)((0, validator_1.vString)()),
    expires_at: (0, validator_1.vRequired)((0, validator_1.vNumber)()),
    refresh_in: (0, validator_1.vRequired)((0, validator_1.vNumber)()),
    sku: (0, validator_1.vString)(),
    individual: (0, validator_1.vBoolean)(),
    blackbird_clientside_indexing: (0, validator_1.vBoolean)(),
    code_quote_enabled: (0, validator_1.vBoolean)(),
    code_review_enabled: (0, validator_1.vBoolean)(),
    codesearch: (0, validator_1.vBoolean)(),
    copilotignore_enabled: (0, validator_1.vBoolean)(),
    public_suggestions: (0, validator_1.vEnum)('enabled', 'disabled', 'unconfigured'),
    telemetry: (0, validator_1.vEnum)('enabled', 'disabled'),
    endpoints: (0, validator_1.vObj)({
        api: (0, validator_1.vString)(),
        'origin-tracker': (0, validator_1.vString)(),
        proxy: (0, validator_1.vString)(),
        telemetry: (0, validator_1.vString)(),
    }),
    enterprise_list: (0, validator_1.vNullable)((0, validator_1.vArray)((0, validator_1.vNumber)())),
    limited_user_quotas: (0, validator_1.vNullable)((0, validator_1.vObj)({
        chat: (0, validator_1.vRequired)((0, validator_1.vNumber)()),
        completions: (0, validator_1.vRequired)((0, validator_1.vNumber)()),
    })),
    limited_user_reset_date: (0, validator_1.vNullable)((0, validator_1.vNumber)()),
    organization_list: (0, validator_1.vArray)((0, validator_1.vString)())
});
const standardErrorEnvelopeValidator = (0, validator_1.vObj)({
    message: (0, validator_1.vRequired)((0, validator_1.vString)()),
    documentation_url: (0, validator_1.vRequired)((0, validator_1.vString)()),
    status: (0, validator_1.vRequired)((0, validator_1.vString)()),
});
/**
 * Fallback validator that only checks the critical fields required for token functionality.
 * Used when the strict validator fails, allowing the client to continue working even if
 * the server adds/changes non-critical fields.
 */
const tokenEnvelopeCriticalValidator = (0, validator_1.vObj)({
    token: (0, validator_1.vRequired)((0, validator_1.vString)()),
    expires_at: (0, validator_1.vRequired)((0, validator_1.vNumber)()),
    refresh_in: (0, validator_1.vRequired)((0, validator_1.vNumber)()),
});
/**
 * Validates a token envelope using a two-tier strategy:
 * 1. First tries strict validation against the full schema.
 * 2. If that fails, falls back to validating only critical fields (token, expires_at, refresh_in).
 *
 * This allows the client to continue working even if the server changes non-critical fields,
 * while providing telemetry data to track schema drift.
 */
function validateTokenEnvelope(obj) {
    const strictResult = tokenEnvelopeValidator.validate(obj);
    if (strictResult.error === undefined) {
        return { valid: true, strategy: 'strict', envelope: strictResult.content };
    }
    const strictError = strictResult.error.message;
    const fallbackResult = tokenEnvelopeCriticalValidator.validate(obj);
    if (fallbackResult.error === undefined) {
        return {
            valid: true,
            strategy: 'fallback',
            strictError,
            // Use the full payload, not the validator result, to preserve all server fields
            envelope: obj
        };
    }
    return {
        valid: false,
        strategy: 'failed',
        strictError,
        fallbackError: fallbackResult.error.message,
    };
}
function isTokenEnvelope(obj) {
    return validateTokenEnvelope(obj).valid;
}
function isErrorEnvelope(obj) {
    return errorEnvelopeValidator.validate(obj).error === undefined;
}
function isStandardErrorEnvelope(obj) {
    return standardErrorEnvelopeValidator.validate(obj).error === undefined;
}
/**
 * Creates a minimal ExtendedTokenInfo for testing purposes.
 * All required TokenEnvelope fields are populated with sensible defaults.
 */
function createTestExtendedTokenInfo(overrides) {
    return {
        // Required token envelope fields
        token: 'test-token',
        expires_at: 0,
        refresh_in: 0,
        sku: 'free_limited_copilot',
        individual: true,
        // Feature flags
        blackbird_clientside_indexing: false,
        code_quote_enabled: false,
        code_review_enabled: false,
        codesearch: false,
        copilotignore_enabled: false,
        // Consent settings
        public_suggestions: 'enabled',
        telemetry: 'enabled',
        // Extended fields
        username: 'testuser',
        isVscodeTeamMember: false,
        copilot_plan: 'free',
        organization_login_list: [],
        // Apply overrides
        ...overrides,
    };
}
//# sourceMappingURL=copilotToken.js.map