import * as cache from '@actions/cache';
import * as core from '@actions/core';
import * as glob from '@actions/glob';
import { createHash } from 'node:crypto';
import { mkdir, readFile } from 'node:fs/promises';
const SAFE_CACHE_NAME = /^[A-Za-z0-9._-]+$/;
const MAX_CACHE_KEY_LENGTH = 512;
const CACHE_KEY_HASH_LENGTH = 16;
function keyHashFragment(value) {
    return createHash('sha256').update(value).digest('hex').slice(0, CACHE_KEY_HASH_LENGTH);
}
function trimKeyWithHash(key, maxLength = MAX_CACHE_KEY_LENGTH) {
    if (key.length <= maxLength)
        return key;
    const suffix = `-${keyHashFragment(key)}`;
    const headLength = Math.max(1, maxLength - suffix.length);
    return `${key.slice(0, headLength)}${suffix}`;
}
function normalizeCacheName(name) {
    if (SAFE_CACHE_NAME.test(name))
        return name;
    const normalized = name
        .replace(/[^A-Za-z0-9._-]+/g, '-')
        .replace(/-+/g, '-')
        .replace(/^-+|-+$/g, '');
    return normalized || 'cache';
}
export class GitHubCacheCapability {
    pendingCaches = [];
    async mount(ref) {
        const path = ref.mountPath?.trim();
        if (!path) {
            throw new Error(`Cache "${ref.name}" has no mount path`);
        }
        const normalizedName = normalizeCacheName(ref.name);
        const key = await this.computeKey(ref, normalizedName);
        const restoreKeys = this.computeRestoreKeys(normalizedName);
        await mkdir(path, { recursive: true });
        let restoredKey;
        try {
            restoredKey = await cache.restoreCache([path], key, restoreKeys);
        }
        catch (error) {
            core.warning(`Cache restore skipped for "${normalizedName}" (${key}): ${this.formatError(error)}`);
            return false;
        }
        const hit = restoredKey !== undefined;
        if (hit) {
            core.info(`Cache hit: ${restoredKey}`);
        }
        else {
            core.info(`Cache miss, will save as: ${key}`);
        }
        this.trackPendingCache(path, key, restoredKey);
        return hit;
    }
    trackPendingCache(path, key, restoredKey) {
        if (!path || !key) {
            core.warning('Skipping cache save registration because path or key is empty');
            return;
        }
        this.pendingCaches.push({ paths: [path], key, restoredKey });
        process.env.KITSI_PENDING_CACHES = JSON.stringify(this.pendingCaches);
    }
    async computeKey(ref, normalizedName) {
        const parts = [process.env.RUNNER_OS ?? 'unknown', normalizedName];
        const key = ref.key;
        if (key?.files && key.files.length > 0) {
            const hash = await this.hashFiles(key.files);
            parts.push(hash.slice(0, 16));
        }
        if (key?.env && key.env.length > 0) {
            const hash = this.hashEnv(key.env);
            parts.push(hash.slice(0, 16));
        }
        if (key?.value) {
            const hash = this.hashValue(key.value);
            parts.push(hash.slice(0, 16));
        }
        return trimKeyWithHash(parts.join('-'));
    }
    async hashFiles(patterns) {
        const globber = await glob.create(patterns.join('\n'));
        const files = await globber.glob();
        const hash = createHash('sha256');
        for (const file of files.sort()) {
            const contents = await readFile(file);
            hash.update(file);
            hash.update(contents);
        }
        return hash.digest('hex');
    }
    hashEnv(names) {
        const hash = createHash('sha256');
        for (const name of names.sort()) {
            hash.update(name);
            hash.update('=');
            hash.update(process.env[name] ?? '__unset__');
        }
        return hash.digest('hex');
    }
    hashValue(value) {
        return createHash('sha256').update(value).digest('hex');
    }
    computeRestoreKeys(normalizedName) {
        const prefixBase = `${process.env.RUNNER_OS ?? 'unknown'}-${normalizedName}`;
        const prefix = `${trimKeyWithHash(prefixBase, MAX_CACHE_KEY_LENGTH - 1)}-`;
        return [prefix];
    }
    formatError(error) {
        if (error instanceof Error) {
            return error.message;
        }
        return String(error);
    }
    getPendingCaches() {
        return this.pendingCaches;
    }
}
//# sourceMappingURL=cache.js.map