import { readFile } from "fs/promises";
import { basename, join } from "path";
import { createReadStream, existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from "fs";
import { gunzip } from "zlib";
import { promisify } from "util";
import { createInterface } from "readline";
import { Readable } from "stream";
//#region src/utils/logger/log-store.ts
/**
* Log Store - Enhanced File-based Log Storage
* 
* Features:
* - Query logs across multiple files with filtering
* - Support for compressed (.gz) log files
* - Pagination and sorting
* - Statistics and analytics
* - Safe log cleanup with actual deletion
*/
const gunzipAsync = promisify(gunzip);
const LOG_DIR = process.env.XOPC_LOG_DIR || join(process.env.HOME || ".", ".xopc", "logs");
function ensureLogDir() {
	if (!existsSync(LOG_DIR)) mkdirSync(LOG_DIR, { recursive: true });
}
/**
* Get all log files (including compressed)
*/
function getLogFiles() {
	ensureLogDir();
	return readdirSync(LOG_DIR).filter((f) => f.endsWith(".log") || f.endsWith(".log.gz")).map((f) => {
		const filePath = join(LOG_DIR, f);
		const stats = statSync(filePath);
		let type = "app";
		if (f.includes("error")) type = "error";
		else if (f.includes("audit")) type = "audit";
		else if (f.includes("access")) type = "access";
		return {
			name: f,
			path: filePath,
			size: stats.size,
			created: stats.birthtime.toISOString(),
			modified: stats.mtime.toISOString(),
			type
		};
	}).sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
}
/**
* Get log file path for a specific date and type
*/
function getLogPath(date = /* @__PURE__ */ new Date(), type = "app") {
	ensureLogDir();
	const dateStr = date.toISOString().split("T")[0];
	return join(LOG_DIR, `${type}-${dateStr}.log`);
}
/**
* Get available log files for a date range
*/
function getLogFilesForRange(from, to) {
	return getLogFiles().filter((f) => {
		const match = f.name.match(/(\d{4}-\d{2}-\d{2})/);
		if (!match) return false;
		const fileDate = new Date(match[1]);
		return fileDate >= from && fileDate <= to;
	});
}
/**
* Parse a single log line (JSON format from pino)
*/
function parseLogLine(line, source, lineNumber) {
	const trimmed = line.trim();
	if (!trimmed) return null;
	try {
		const parsed = JSON.parse(trimmed);
		const levelNum = typeof parsed.level === "number" ? parsed.level : 30;
		return {
			timestamp: parsed.time || parsed.timestamp || "",
			level: {
				10: "trace",
				20: "debug",
				30: "info",
				40: "warn",
				50: "error",
				60: "fatal"
			}[levelNum] || (parsed.level?.toString() || "info").toLowerCase(),
			message: parsed.msg || parsed.message || "",
			module: parsed.module,
			prefix: parsed.prefix,
			service: parsed.service,
			extension: parsed.extension,
			requestId: parsed.requestId,
			sessionId: parsed.sessionId,
			_source: source,
			_lineNumber: lineNumber,
			...parsed
		};
	} catch {
		const match = trimmed.match(/^\[([^\]]+)\]\s+(\w+):\s+(.+)$/);
		if (match) return {
			timestamp: match[1],
			level: match[2].toLowerCase(),
			message: match[3],
			_source: source,
			_lineNumber: lineNumber
		};
		return {
			timestamp: (/* @__PURE__ */ new Date()).toISOString(),
			level: "info",
			message: trimmed,
			_source: source,
			_lineNumber: lineNumber
		};
	}
}
/**
* Create a readable stream for a log file (handles .gz files)
*/
async function createLogFileStream(filePath) {
	if (filePath.endsWith(".gz")) {
		const decompressed = await gunzipAsync(await readFile(filePath));
		return Readable.from(decompressed.toString("utf-8").split("\n"));
	}
	return createReadStream(filePath, { encoding: "utf-8" });
}
/**
* Stream and filter log entries from a file
*/
async function* streamLogFile(filePath, query = {}) {
	if (!existsSync(filePath)) return;
	const fileName = basename(filePath);
	let stream;
	try {
		stream = await createLogFileStream(filePath);
	} catch {
		return;
	}
	const rl = createInterface({
		input: stream,
		crlfDelay: Infinity
	});
	let lineNumber = 0;
	try {
		for await (const line of rl) {
			lineNumber++;
			const entry = parseLogLine(line, fileName, lineNumber);
			if (!entry) continue;
			if (!matchesQuery(entry, query)) continue;
			yield entry;
		}
	} finally {
		rl.close();
	}
}
/**
* Check if a log entry matches the query filters
*/
function matchesQuery(entry, query) {
	if (query.levels?.length && !query.levels.includes(entry.level)) return false;
	if (query.from) {
		const fromDate = new Date(query.from);
		if (new Date(entry.timestamp) < fromDate) return false;
	}
	if (query.to) {
		const toDate = new Date(query.to);
		if (new Date(entry.timestamp) > toDate) return false;
	}
	if (query.q) {
		const keyword = query.q.toLowerCase();
		if (![
			entry.message,
			entry.module,
			entry.prefix,
			entry.service,
			entry.extension,
			entry.requestId,
			entry.sessionId
		].filter(Boolean).map(String).join(" ").toLowerCase().includes(keyword)) return false;
	}
	if (query.module && entry.module !== query.module) return false;
	if (query.extension && entry.extension !== query.extension) return false;
	if (query.service && entry.service !== query.service) return false;
	if (query.requestId && entry.requestId !== query.requestId) return false;
	if (query.sessionId && entry.sessionId !== query.sessionId) return false;
	return true;
}
/** Cap matched rows before sort/slice so query stays bounded on huge log dirs. */
const QUERY_LOGS_MAX_MATCHED = 25e3;
/**
* Query logs across multiple files
*/
async function queryLogs(query = {}) {
	ensureLogDir();
	const results = [];
	let relevantFiles = getLogFiles();
	if (query.from || query.to) relevantFiles = getLogFilesForRange(query.from ? new Date(query.from) : /* @__PURE__ */ new Date(0), query.to ? new Date(query.to) : /* @__PURE__ */ new Date());
	for (const file of relevantFiles) {
		if (results.length >= QUERY_LOGS_MAX_MATCHED) break;
		for await (const entry of streamLogFile(file.path, query)) {
			results.push(entry);
			if (results.length >= QUERY_LOGS_MAX_MATCHED) break;
		}
	}
	const order = query.order || "desc";
	results.sort((a, b) => {
		const diff = new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
		return order === "desc" ? diff : -diff;
	});
	const offset = query.offset || 0;
	const limit = query.limit || 100;
	return results.slice(offset, offset + limit).map(({ _source, _lineNumber, ...rest }) => rest);
}
/**
* Get recent logs (convenience method)
*/
async function getRecentLogs(options) {
	return queryLogs({
		levels: options?.level ? [options.level] : void 0,
		limit: options?.limit || 50,
		module: options?.module,
		order: "desc"
	});
}
/**
* Search logs by keyword
*/
async function searchLogs(keyword, options) {
	return queryLogs({
		q: keyword,
		...options,
		limit: options?.limit || 100
	});
}
/**
* Get logs for a specific request/session
*/
async function getLogsByContext(contextType, contextValue, limit = 100) {
	return queryLogs({
		[contextType]: contextValue,
		limit,
		order: "asc"
	});
}
/**
* Get available log levels from actual log data
*/
async function getLogLevelsFromData() {
	const levels = /* @__PURE__ */ new Set();
	const files = getLogFiles().slice(0, 3);
	for (const file of files) for await (const entry of streamLogFile(file.path, { limit: 500 })) levels.add(entry.level);
	return Array.from(levels).sort();
}
/**
* Get available modules from log data
*/
async function getLogModules() {
	const modules = /* @__PURE__ */ new Set();
	const files = getLogFiles().slice(0, 7);
	for (const file of files) for await (const entry of streamLogFile(file.path, { limit: 1e3 })) {
		if (entry.module) modules.add(entry.module);
		if (entry.prefix) modules.add(entry.prefix);
	}
	return Array.from(modules).filter(Boolean).sort();
}
/**
* Get log statistics by level (sampled from recent files)
*/
async function getLogStats() {
	const files = getLogFiles();
	const byLevel = {
		trace: 0,
		debug: 0,
		info: 0,
		warn: 0,
		error: 0,
		fatal: 0,
		silent: 0
	};
	for (const file of files.slice(0, 7)) for await (const entry of streamLogFile(file.path, { limit: 1e3 })) if (entry.level in byLevel) byLevel[entry.level]++;
	return { byLevel };
}
/**
* Clean old logs (actually deletes files)
*/
function cleanOldLogs(keepDays = 7) {
	const cutoff = /* @__PURE__ */ new Date();
	cutoff.setDate(cutoff.getDate() - keepDays);
	let deleted = 0;
	let freedBytes = 0;
	const errors = [];
	const files = getLogFiles();
	for (const file of files) if (new Date(file.modified) < cutoff) try {
		const size = file.size;
		unlinkSync(file.path);
		deleted++;
		freedBytes += size;
	} catch (err) {
		errors.push(`Failed to delete ${file.name}: ${err}`);
	}
	return {
		deleted,
		freedBytes,
		errors
	};
}
const LOG_LEVELS = [
	"trace",
	"debug",
	"info",
	"warn",
	"error",
	"fatal"
];
/**
* Get available log levels
*/
function getLogLevels() {
	return [...LOG_LEVELS];
}
//#endregion
export { LOG_DIR, cleanOldLogs, getLogFiles, getLogLevels, getLogLevelsFromData, getLogModules, getLogPath, getLogStats, getLogsByContext, getRecentLogs, queryLogs, searchLogs };

//# sourceMappingURL=log-store.js.map