import { eo as resolveAgentWorkspaceDir } from "./runtime-x71O5s-N.js";
import "./github-copilot-token-CVC_19vl.js";
import { t as resolveMemorySearchConfig } from "./memory-search-BRc1w0pH.js";
import { c as normalizeExtraMemoryPaths, l as normalizeRelPath, n as chunkMarkdown, o as isMemoryPath, s as listMemoryFiles, t as buildFileEntry } from "./internal-B5WwMXvo.js";
import path from "node:path";
import fs from "node:fs/promises";

//#region src/memory/filesystem-manager.ts
const SNIPPET_MAX_CHARS = 700;
var FilesystemMemorySearchManager = class FilesystemMemorySearchManager {
	constructor(cfg, agentId, workspaceDir, settings, unavailableReason) {
		this.cfg = cfg;
		this.agentId = agentId;
		this.workspaceDir = workspaceDir;
		this.settings = settings;
		this.unavailableReason = unavailableReason;
		this.snapshot = {
			files: 0,
			chunks: 0
		};
	}
	static async create(params) {
		const settings = resolveMemorySearchConfig(params.cfg, params.agentId);
		if (!settings) return null;
		const manager = new FilesystemMemorySearchManager(params.cfg, params.agentId, resolveAgentWorkspaceDir(params.cfg, params.agentId), settings, params.unavailableReason);
		await manager.refreshSnapshot();
		return manager;
	}
	async search(query, opts) {
		const queryTokens = tokenize(query);
		if (queryTokens.length === 0) return [];
		const files = await this.listFiles();
		const rows = [];
		for (const absPath of files) {
			const entry = await buildFileEntry(absPath, this.workspaceDir);
			if (!entry) continue;
			const content = await fs.readFile(entry.absPath, "utf-8");
			for (const chunk of chunkMarkdown(content, this.settings.chunking)) {
				const score = scoreChunk(queryTokens, chunk.text);
				if (score <= 0 || score < (opts?.minScore ?? 0)) continue;
				rows.push({
					path: entry.path,
					startLine: chunk.startLine,
					endLine: chunk.endLine,
					score,
					snippet: truncateSnippet(chunk.text),
					source: "memory",
					citation: `${entry.path}#L${chunk.startLine}-L${chunk.endLine}`
				});
			}
		}
		rows.sort((left, right) => right.score - left.score || left.path.localeCompare(right.path));
		return rows.slice(0, Math.max(1, opts?.maxResults ?? 5));
	}
	async readFile(params) {
		const relPath = normalizeRelPath(params.relPath);
		const absPath = path.resolve(this.workspaceDir, relPath);
		if (!await this.isAllowedReadPath(relPath, absPath)) throw new Error("path required");
		let content = "";
		try {
			content = await fs.readFile(absPath, "utf-8");
		} catch (err) {
			if (isMissingPathError(err)) return {
				text: "",
				path: relPath
			};
			throw err;
		}
		if (!params.from && !params.lines) return {
			text: content,
			path: relPath
		};
		const lines = content.split("\n");
		const start = Math.max(1, params.from ?? 1);
		const count = Math.max(1, params.lines ?? lines.length);
		return {
			text: lines.slice(start - 1, start - 1 + count).join("\n"),
			path: relPath
		};
	}
	status() {
		return {
			backend: "builtin",
			provider: "filesystem",
			model: "keyword",
			requestedProvider: this.settings.provider,
			files: this.snapshot.files,
			chunks: this.snapshot.chunks,
			dirty: false,
			workspaceDir: this.workspaceDir,
			sources: ["memory"],
			extraPaths: this.settings.extraPaths,
			sourceCounts: [{
				source: "memory",
				files: this.snapshot.files,
				chunks: this.snapshot.chunks
			}],
			fts: {
				enabled: true,
				available: true
			},
			vector: {
				enabled: false,
				available: false
			},
			cache: { enabled: false },
			custom: {
				searchMode: "filesystem-keyword-fallback",
				sqliteUnavailable: true,
				sqliteUnavailableReason: this.unavailableReason
			}
		};
	}
	async sync(params) {
		await this.refreshSnapshot();
		params?.progress?.({
			completed: this.snapshot.files,
			total: this.snapshot.files
		});
	}
	async probeEmbeddingAvailability() {
		return {
			ok: false,
			error: "SQLite unavailable; filesystem keyword memory fallback is active."
		};
	}
	async probeVectorAvailability() {
		return false;
	}
	async close() {}
	async refreshSnapshot() {
		const files = await this.listFiles();
		let chunks = 0;
		for (const absPath of files) try {
			chunks += chunkMarkdown(await fs.readFile(absPath, "utf-8"), this.settings.chunking).length;
		} catch {}
		this.snapshot = {
			files: files.length,
			chunks
		};
	}
	async listFiles() {
		if (!this.settings.sources.includes("memory")) return [];
		return await listMemoryFiles(this.workspaceDir, this.settings.extraPaths);
	}
	async isAllowedReadPath(relPath, absPath) {
		if (isMemoryPath(relPath) && isPathInside(absPath, this.workspaceDir)) return absPath.endsWith(".md");
		for (const extraPath of normalizeExtraMemoryPaths(this.workspaceDir, this.settings.extraPaths)) {
			if (!isPathInside(absPath, extraPath) && absPath !== extraPath) continue;
			try {
				const stat = await fs.lstat(absPath);
				if (!stat.isSymbolicLink() && stat.isFile() && absPath.endsWith(".md")) return true;
			} catch {}
		}
		return false;
	}
};
function tokenize(value) {
	return Array.from(new Set(value.toLowerCase().split(/[^a-z0-9_]+/u).map((token) => token.trim()).filter((token) => token.length >= 2)));
}
function scoreChunk(queryTokens, text) {
	const lower = text.toLowerCase();
	const textTokens = tokenize(lower);
	if (textTokens.length === 0) return 0;
	let hits = 0;
	for (const token of queryTokens) if (textTokens.includes(token) || lower.includes(token)) hits += 1;
	return hits / queryTokens.length;
}
function truncateSnippet(value) {
	const compact = value.replace(/\s+/g, " ").trim();
	if (compact.length <= SNIPPET_MAX_CHARS) return compact;
	return `${compact.slice(0, SNIPPET_MAX_CHARS - 1)}…`;
}
function isPathInside(child, parent) {
	const relative = path.relative(path.resolve(parent), path.resolve(child));
	return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
}
function isMissingPathError(err) {
	return err !== null && typeof err === "object" && "code" in err && err.code === "ENOENT";
}

//#endregion
export { FilesystemMemorySearchManager };