import { sqlite } from "../../../../expose.js";
import { logger } from "../../../../logger/index.js";
import { PackageCacheBase } from "./base.js";
import { ensureDir } from "../../../fs/index.js";
import fs from "fs-extra";
import upath from "upath";
import { promisify } from "node:util";
import zlib, { constants } from "node:zlib";
//#region lib/util/cache/package/impl/sqlite.ts
const { exists } = fs;
const brotliCompress = promisify(zlib.brotliCompress);
const brotliDecompress = promisify(zlib.brotliDecompress);
function compress(input) {
	return brotliCompress(JSON.stringify(input), { params: {
		[constants.BROTLI_PARAM_MODE]: constants.BROTLI_MODE_TEXT,
		[constants.BROTLI_PARAM_QUALITY]: 3
	} });
}
async function decompress(input) {
	const jsonStr = (await brotliDecompress(input)).toString("utf8");
	return JSON.parse(jsonStr);
}
var PackageCacheSqlite = class PackageCacheSqlite extends PackageCacheBase {
	static busyTimeoutMs = 100;
	static async create(cacheDir) {
		const Sqlite = await sqlite();
		const sqliteDir = upath.join(cacheDir, "renovate/renovate-cache-sqlite");
		await ensureDir(sqliteDir);
		const sqliteFile = upath.join(sqliteDir, "db.sqlite");
		if (await exists(sqliteFile)) logger.debug(`Using SQLite package cache: ${sqliteFile}`);
		else logger.debug(`Creating SQLite package cache: ${sqliteFile}`);
		return new PackageCacheSqlite(new Sqlite(sqliteFile));
	}
	upsertStatement;
	getStatement;
	deleteExpiredRows;
	countStatement;
	client;
	constructor(client) {
		super();
		this.client = client;
		client.pragma("journal_mode = WAL");
		client.pragma("encoding = 'UTF-8'");
		client.pragma(`busy_timeout = ${PackageCacheSqlite.busyTimeoutMs}`);
		client.prepare(`
          CREATE TABLE IF NOT EXISTS package_cache (
            namespace TEXT NOT NULL,
            key TEXT NOT NULL,
            expiry INTEGER NOT NULL,
            data BLOB NOT NULL,
            PRIMARY KEY (namespace, key)
          )
        `).run();
		client.prepare("CREATE INDEX IF NOT EXISTS expiry ON package_cache (expiry)").run();
		this.upsertStatement = client.prepare(`
      INSERT INTO package_cache (namespace, key, data, expiry)
      VALUES (@namespace, @key, @data, unixepoch() + @ttlSeconds)
      ON CONFLICT (namespace, key) DO UPDATE SET
        data = @data,
        expiry = unixepoch() + @ttlSeconds
    `);
		this.getStatement = client.prepare(`
          SELECT data FROM package_cache
          WHERE
            namespace = @namespace AND key = @key AND expiry > unixepoch()
        `).pluck(true);
		this.deleteExpiredRows = client.prepare(`
      DELETE FROM package_cache
      WHERE expiry <= unixepoch()
    `);
		this.countStatement = client.prepare("SELECT COUNT(*) FROM package_cache").pluck(true);
	}
	async set(namespace, key, value, hardTtlMinutes) {
		try {
			const compressedData = await compress(value);
			const ttlSeconds = hardTtlMinutes * 60;
			this.upsertStatement.run({
				namespace,
				key,
				data: compressedData,
				ttlSeconds
			});
		} catch (err) {
			logger.once.warn({ err }, "Error while setting SQLite cache value");
		}
	}
	async get(namespace, key) {
		try {
			const data = this.getStatement.get({
				namespace,
				key
			});
			if (!data) {
				logger.trace({
					namespace,
					key
				}, "Cache miss");
				return;
			}
			return await decompress(data);
		} catch (err) {
			logger.once.warn({ err }, "Error while reading SQLite cache value");
			return;
		}
	}
	destroy() {
		try {
			const startTime = Date.now();
			const totalCount = this.countStatement.get();
			const { changes: deletedCount } = this.deleteExpiredRows.run();
			const durationMs = Date.now() - startTime;
			logger.debug(`SQLite package cache: deleted ${deletedCount} of ${totalCount} entries in ${durationMs}ms`);
		} catch (err) {
			logger.warn({ err }, "SQLite package cache cleanup failed");
		}
		try {
			this.client.close();
		} catch (err) {
			logger.warn({ err }, "SQLite package cache close failed");
		}
		return Promise.resolve();
	}
};
//#endregion
export { PackageCacheSqlite };

//# sourceMappingURL=sqlite.js.map