"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.BaseOctoKitService = exports.PermissiveAuthRequiredError = exports.VSCodeTeamId = exports.IOctoKitService = exports.IGithubRepositoryService = void 0;
const services_1 = require("../../../util/common/services");
const buffer_1 = require("../../../util/vs/base/common/buffer");
const githubAPI_1 = require("./githubAPI");
exports.IGithubRepositoryService = (0, services_1.createServiceIdentifier)('IGithubRepositoryService');
exports.IOctoKitService = (0, services_1.createServiceIdentifier)('IOctoKitService');
exports.VSCodeTeamId = 1682102;
class PermissiveAuthRequiredError extends Error {
    constructor() {
        super('Permissive authentication is required');
        this.name = 'PermissiveAuthRequiredError';
    }
}
exports.PermissiveAuthRequiredError = PermissiveAuthRequiredError;
/**
 * The same as {@link OctoKitService} but doesn't require the AuthService.
 * This is because we want to call certain Octokit method inside the Authservice and must
 * avoid a circular dependency.
 * Note: Only OctoKitService is exposed on the accessor to avoid confusion.
 */
class BaseOctoKitService {
    static { this._outageStatusCacheTTL = 5 * 60 * 1000; } // 5 minutes
    static { this._userReposScopeCacheTTL = 5 * 60 * 1000; } // 5 minutes
    constructor(_capiClientService, _fetcherService, _logService, _telemetryService) {
        this._capiClientService = _capiClientService;
        this._fetcherService = _fetcherService;
        this._logService = _logService;
        this._telemetryService = _telemetryService;
    }
    async getCurrentAuthedUserWithToken(token) {
        return this._makeGHAPIRequest('user', 'GET', token, undefined, undefined, 'github-rest-get-user');
    }
    async getGitHubOutageStatus() {
        const now = Date.now();
        if (this._cachedOutageStatus && (now - this._cachedOutageStatus.timestamp) < BaseOctoKitService._outageStatusCacheTTL) {
            return this._cachedOutageStatus.value;
        }
        try {
            // See docs at https://www.githubstatus.com/api/
            const response = await this._fetcherService.fetch('https://www.githubstatus.com/api/v2/status.json', { method: 'GET', callSite: 'github-status' });
            const data = await response.json();
            const status = data?.status?.indicator;
            let result;
            switch (status) {
                case 'none':
                    result = 0 /* GitHubOutageStatus.None */;
                    break;
                case 'minor':
                    result = 1 /* GitHubOutageStatus.Minor */;
                    break;
                case 'major':
                    result = 2 /* GitHubOutageStatus.Major */;
                    break;
                case 'critical':
                    result = 3 /* GitHubOutageStatus.Critical */;
                    break;
                default:
                    result = 0 /* GitHubOutageStatus.None */;
                    break;
            }
            this._cachedOutageStatus = { value: result, timestamp: now };
            return result;
        }
        catch {
            // Cache the failure as None so callers don't re-attempt on every invocation
            this._cachedOutageStatus = { value: 0 /* GitHubOutageStatus.None */, timestamp: now };
            return 0 /* GitHubOutageStatus.None */;
        }
    }
    async _makeGHAPIRequest(routeSlug, method, token, body, options, callSite = 'github-api-rest') {
        return (0, githubAPI_1.makeGitHubAPIRequest)(this._fetcherService, this._logService, this._telemetryService, this._capiClientService.dotcomAPIURL, routeSlug, method, token, { body, version: '2022-11-28', silent404: options?.silent404, callSite });
    }
    async getOpenPullRequestForUserWithToken(owner, repo, user, token) {
        const query = `repo:${owner}/${repo} is:open involves:${user}`;
        return (0, githubAPI_1.makeSearchGraphQLRequest)(this._fetcherService, this._logService, this._telemetryService, this._capiClientService.dotcomAPIURL, token, query);
    }
    async findPullRequestByHeadBranchWithToken(owner, repo, headBranch, token) {
        const query = `repo:${owner}/${repo} head:${headBranch} is:pr`;
        const results = await (0, githubAPI_1.makeSearchGraphQLRequest)(this._fetcherService, this._logService, this._telemetryService, this._capiClientService.dotcomAPIURL, token, query, 5);
        return results.find(pr => pr.headRefName === headBranch);
    }
    async addPullRequestCommentWithToken(pullRequestId, commentBody, token) {
        return (0, githubAPI_1.addPullRequestCommentGraphQLRequest)(this._fetcherService, this._logService, this._telemetryService, this._capiClientService.dotcomAPIURL, token, pullRequestId, commentBody);
    }
    async createPullRequestWithToken(owner, repo, title, body, head, base, draft, token) {
        const response = await this._makeGHAPIRequest(`repos/${owner}/${repo}/pulls`, 'POST', token, {
            title,
            body,
            head,
            base,
            draft,
        });
        if (!response?.html_url || typeof response.number !== 'number') {
            throw new Error(`Failed to create pull request for ${owner}/${repo}`);
        }
        return {
            url: response.html_url,
            number: response.number,
        };
    }
    async getPullRequestFromSessionWithToken(globalId, token) {
        return (0, githubAPI_1.getPullRequestFromGlobalId)(this._fetcherService, this._logService, this._telemetryService, this._capiClientService.dotcomAPIURL, token, globalId);
    }
    async getPullRequestFilesWithToken(owner, repo, pullNumber, token) {
        const result = await (0, githubAPI_1.makeGitHubAPIRequest)(this._fetcherService, this._logService, this._telemetryService, this._capiClientService.dotcomAPIURL, `repos/${owner}/${repo}/pulls/${pullNumber}/files`, 'GET', token, { version: '2022-11-28', callSite: 'github-rest-get-pr-files' });
        return result || [];
    }
    async closePullRequestWithToken(owner, repo, pullNumber, token) {
        return (0, githubAPI_1.closePullRequest)(this._fetcherService, this._logService, this._telemetryService, this._capiClientService.dotcomAPIURL, token, owner, repo, pullNumber);
    }
    async getFileContentWithToken(owner, repo, ref, path, token) {
        const route = `repos/${owner}/${repo}/contents/${path}?ref=${encodeURIComponent(ref)}`;
        const response = await (0, githubAPI_1.makeGitHubAPIRequest)(this._fetcherService, this._logService, this._telemetryService, this._capiClientService.dotcomAPIURL, route, 'GET', token, { callSite: 'github-rest-get-file-content' });
        if (!response || Array.isArray(response)) {
            throw new Error('Unable to fetch file content');
        }
        const typedResponse = response;
        if (typedResponse.content && typedResponse.encoding === 'base64') {
            return (0, buffer_1.decodeBase64)(typedResponse.content.replace(/\n/g, '')).toString();
        }
        if (typedResponse.sha) {
            const blob = await this.getBlobContentWithToken(owner, repo, typedResponse.sha, token);
            if (blob) {
                return blob;
            }
        }
        this._logService.error(`Failed to get file content for ${owner}/${repo}/${path} at ref ${ref}`);
        return '';
    }
    async getUserOrganizationsWithToken(token, pageSize = 100) {
        const result = await this._makeGHAPIRequest(`user/orgs?per_page=${pageSize}`, 'GET', token, undefined, undefined, 'github-rest-get-user-orgs');
        if (!result || !Array.isArray(result)) {
            return [];
        }
        return result.map((org) => org.login);
    }
    async isUserMemberOfOrgWithToken(org, token) {
        try {
            // GET /user/memberships/orgs/{org} returns 200 if the user is a member, 404 otherwise
            const result = await this._makeGHAPIRequest(`user/memberships/orgs/${encodeURIComponent(org)}`, 'GET', token, undefined, undefined, 'github-rest-check-org-membership');
            // If we get a result with state 'active' or 'pending', user is a member
            return result && (result.state === 'active' || result.state === 'pending');
        }
        catch {
            // 404 or other error means user is not a member
            return false;
        }
    }
    async getOrganizationRepositoriesWithToken(org, token, pageSize = 100) {
        const result = await this._makeGHAPIRequest(`orgs/${org}/repos?per_page=${pageSize}&sort=updated`, 'GET', token, undefined, { silent404: true }, 'github-rest-get-org-repos');
        if (!result || !Array.isArray(result) || result.length === 0) {
            return [];
        }
        return result.map((repo) => repo.name);
    }
    async getUserRepositoriesWithToken(token, query) {
        const trimmedQuery = query?.trim();
        // If query provided, use GitHub search API scoped to the user and their orgs.
        // Without a user:/org: scope, /search/repositories does not return private repos.
        if (trimmedQuery) {
            return this.searchUserRepositoriesWithToken(token, trimmedQuery);
        }
        // Fetch the most recently updated repos with push access
        const result = await this._makeGHAPIRequest('user/repos?per_page=100&sort=updated&affiliation=owner,collaborator,organization_member', 'GET', token, undefined, undefined, 'github-rest-get-user-repos');
        if (!result || !Array.isArray(result)) {
            return [];
        }
        return result
            .filter((repo) => repo.permissions?.push)
            .map((repo) => ({
            owner: repo.owner.login,
            name: repo.name
        }));
    }
    async searchUserRepositoriesWithToken(token, query) {
        const scope = await this._getUserReposSearchScope(token);
        if (!scope) {
            return [];
        }
        // `user:<login>` plus `org:<org>` qualifiers are ORed by the search API, so a
        // single call covers the authed user's own repos and any org repos they belong
        // to (including private ones).
        const searchQuery = encodeURIComponent(`${query} in:name fork:true ${scope}`);
        const result = await this._makeGHAPIRequest(`search/repositories?q=${searchQuery}&sort=updated&per_page=100`, 'GET', token, undefined, undefined, 'github-rest-search-repos');
        if (!result || !Array.isArray(result.items)) {
            return [];
        }
        return result.items
            .filter((repo) => repo.permissions?.push)
            .map((repo) => ({
            owner: repo.owner.login,
            name: repo.name
        }));
    }
    async _getUserReposSearchScope(token) {
        const now = Date.now();
        if (this._cachedUserReposScope
            && this._cachedUserReposScope.token === token
            && (now - this._cachedUserReposScope.timestamp) < BaseOctoKitService._userReposScopeCacheTTL) {
            return this._cachedUserReposScope.qualifiers;
        }
        const [user, orgs] = await Promise.all([
            this._makeGHAPIRequest('user', 'GET', token, undefined, undefined, 'github-rest-get-user'),
            this.getUserOrganizationsWithToken(token)
        ]);
        const qualifiers = [];
        if (user?.login) {
            qualifiers.push(`user:${user.login}`);
        }
        for (const org of orgs) {
            qualifiers.push(`org:${org}`);
        }
        if (qualifiers.length === 0) {
            return undefined;
        }
        const joined = qualifiers.join(' ');
        this._cachedUserReposScope = { token, qualifiers: joined, timestamp: now };
        return joined;
    }
    async getRecentlyCommittedReposWithToken(token) {
        // First, get the authenticated user's login
        const user = await this._makeGHAPIRequest('user', 'GET', token, undefined, undefined, 'github-rest-get-user');
        if (!user || !user.login) {
            return [];
        }
        // Fetch recent events for the user (includes push events)
        const events = await this._makeGHAPIRequest(`users/${user.login}/events?per_page=100`, 'GET', token, undefined, undefined, 'github-rest-get-user-events');
        if (!events || !Array.isArray(events)) {
            return [];
        }
        // Extract unique repos from PushEvent entries, preserving order (most recent first)
        const repoSet = new Map();
        for (const event of events) {
            if (event.type === 'PushEvent' && event.repo?.name) {
                const [owner, name] = event.repo.name.split('/');
                if (owner && name && !repoSet.has(event.repo.name)) {
                    repoSet.set(event.repo.name, { owner, name });
                }
            }
        }
        const items = Array.from(repoSet.values());
        return items || [];
    }
    async getBlobContentWithToken(owner, repo, sha, token) {
        const blobRoute = `repos/${owner}/${repo}/git/blobs/${sha}`;
        const blobResponse = await (0, githubAPI_1.makeGitHubAPIRequest)(this._fetcherService, this._logService, this._telemetryService, this._capiClientService.dotcomAPIURL, blobRoute, 'GET', token, { version: '2022-11-28', callSite: 'github-rest-get-blob' });
        if (!blobResponse || Array.isArray(blobResponse)) {
            return undefined;
        }
        const typedBlob = blobResponse;
        if (typedBlob.content && typedBlob.encoding === 'base64') {
            return (0, buffer_1.decodeBase64)(typedBlob.content.replace(/\n/g, '')).toString();
        }
        return undefined;
    }
}
exports.BaseOctoKitService = BaseOctoKitService;
//# sourceMappingURL=githubService.js.map