"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getGitHubToken = getGitHubToken;
exports.fetchGitHub = fetchGitHub;
exports.resolveRefToSha = resolveRefToSha;
exports.clearRefCache = clearRefCache;
exports.getRefCacheSize = getRefCacheSize;
/**
 * GitHub utilities for Socket projects.
 * Provides GitHub API integration for repository operations.
 *
 * Authentication:
 * - getGitHubToken: Retrieve GitHub token from environment variables
 * - fetchGitHub: Authenticated GitHub API requests with rate limit handling
 *
 * Ref Resolution:
 * - resolveRefToSha: Convert tags/branches to commit SHAs (with memoization and persistent cache)
 * - clearRefCache: Clear the memoization cache
 *
 * Caching:
 * - In-memory cache for fast repeated lookups within the same process
 * - Persistent file-based cache (TTL: 5 minutes) for cross-invocation reuse
 * - Disable with DISABLE_GITHUB_CACHE env var
 *
 * Rate Limiting:
 * - Automatic rate limit detection and error messages
 * - Cache to minimize API calls
 */
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const fs_1 = require("./fs");
const paths_1 = require("./paths");
const GITHUB_API_BASE = 'https://api.github.com';
// 5 minutes.
const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;
// In-memory cache for resolved SHAs to minimize API calls within same process.
const refCache = new Map();
// Cache directory path.
// Lazily resolved.
let _githubCachePath;
/**
 * Get GitHub token from environment variables.
 */
function getGitHubToken() {
    const { env } = process;
    return (env['GITHUB_TOKEN'] ||
        env['GH_TOKEN'] ||
        env['SOCKET_CLI_GITHUB_TOKEN'] ||
        undefined);
}
/**
 * Fetch data from GitHub API with rate limit handling.
 */
async function fetchGitHub(url, options) {
    const opts = { __proto__: null, ...options };
    const token = opts.token || getGitHubToken();
    const headers = {
        Accept: 'application/vnd.github.v3+json',
        'User-Agent': 'socket-registry-github-client',
        ...opts.headers,
    };
    if (token) {
        headers['Authorization'] = `Bearer ${token}`;
    }
    const response = await fetch(url, { headers });
    if (!response.ok) {
        if (response.status === 403) {
            const rateLimit = response.headers.get('X-RateLimit-Remaining');
            if (rateLimit === '0') {
                const resetTime = response.headers.get('X-RateLimit-Reset');
                const resetDate = resetTime
                    ? new Date(Number(resetTime) * 1000)
                    : undefined;
                const error = new Error(`GitHub API rate limit exceeded${resetDate ? `. Resets at ${resetDate.toLocaleString()}` : ''}. Use GITHUB_TOKEN environment variable to increase rate limit.`);
                error.status = 403;
                error.resetTime = resetDate;
                throw error;
            }
        }
        throw new Error(`GitHub API error ${response.status}: ${response.statusText}`);
    }
    return (await response.json());
}
/**
 * Get the GitHub cache directory path.
 */
function getGithubCachePath(customPath) {
    if (customPath) {
        return customPath;
    }
    if (_githubCachePath === undefined) {
        _githubCachePath = (0, paths_1.getSocketRegistryGithubCacheDir)();
    }
    return _githubCachePath;
}
/**
 * Read cached data from file system.
 */
async function readCache(key, ttlMs, cachePath) {
    try {
        const cacheDir = getGithubCachePath(cachePath);
        const cacheJsonPath = node_path_1.join(cacheDir, `${key}.json`);
        const stat = (0, fs_1.safeStatsSync)(cacheJsonPath);
        if (stat) {
            const isExpired = Date.now() - Number(stat.mtimeMs) > ttlMs;
            if (!isExpired) {
                return await (0, fs_1.readJson)(cacheJsonPath);
            }
        }
    }
    catch { }
    return undefined;
}
/**
 * Write data to cache file system.
 */
async function writeCache(key, data, cachePath) {
    try {
        const cacheDir = getGithubCachePath(cachePath);
        if (!(0, node_fs_1.existsSync)(cacheDir)) {
            await (0, promises_1.mkdir)(cacheDir, { recursive: true });
        }
        const cacheJsonPath = node_path_1.join(cacheDir, `${key}.json`);
        await (0, fs_1.writeJson)(cacheJsonPath, data);
    }
    catch { }
}
/**
 * Cache fetch wrapper with TTL support.
 */
async function cacheFetch(key, fetcher, options) {
    const opts = { __proto__: null, ...options };
    const ttl = opts.ttl ?? DEFAULT_CACHE_TTL_MS;
    // Optionally disable cache.
    if (process.env['DISABLE_GITHUB_CACHE']) {
        return await fetcher();
    }
    let data = (await readCache(key, ttl, opts.cachePath));
    if (!data) {
        data = await fetcher();
        await writeCache(key, data, opts.cachePath);
    }
    return data;
}
/**
 * Resolve a git ref (tag, branch, or commit SHA) to its full commit SHA.
 * Results are cached in-memory and on disk (with TTL) to minimize API calls.
 */
async function resolveRefToSha(owner, repo, ref, options) {
    const opts = {
        __proto__: null,
        cache: true,
        ttl: DEFAULT_CACHE_TTL_MS,
        ...options,
    };
    const cacheKey = `${owner}/${repo}@${ref}`;
    // Check in-memory cache first if caching is enabled.
    if (opts.cache && refCache.has(cacheKey)) {
        return refCache.get(cacheKey);
    }
    // Use cacheFetch to check disk cache and fetch if needed.
    const sha = await cacheFetch(cacheKey, async () => {
        const fetchOptions = {
            token: opts.token,
        };
        try {
            // Try as a tag first.
            const tagUrl = `${GITHUB_API_BASE}/repos/${owner}/${repo}/git/refs/tags/${ref}`;
            const tagData = await fetchGitHub(tagUrl, fetchOptions);
            // Tag might point to a tag object or directly to a commit.
            if (tagData.object.type === 'tag') {
                // Dereference the tag object to get the commit.
                const tagObject = await fetchGitHub(tagData.object.url, fetchOptions);
                return tagObject.object.sha;
            }
            return tagData.object.sha;
        }
        catch {
            // Not a tag, try as a branch.
            try {
                const branchUrl = `${GITHUB_API_BASE}/repos/${owner}/${repo}/git/refs/heads/${ref}`;
                const branchData = await fetchGitHub(branchUrl, fetchOptions);
                return branchData.object.sha;
            }
            catch {
                // Try without refs/ prefix (for commit SHAs or other refs).
                try {
                    const commitUrl = `${GITHUB_API_BASE}/repos/${owner}/${repo}/commits/${ref}`;
                    const commitData = await fetchGitHub(commitUrl, fetchOptions);
                    return commitData.sha;
                }
                catch (e) {
                    throw new Error(`failed to resolve ref "${ref}" for ${owner}/${repo}: ${e instanceof Error ? e.message : String(e)}`);
                }
            }
        }
    }, {
        cachePath: opts.cachePath,
        ttl: opts.ttl,
    });
    // Update in-memory cache if caching is enabled.
    if (opts.cache) {
        refCache.set(cacheKey, sha);
    }
    return sha;
}
/**
 * Clear the ref resolution cache.
 */
function clearRefCache() {
    refCache.clear();
}
/**
 * Get the current size of the ref cache.
 */
function getRefCacheSize() {
    return refCache.size;
}
