import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
import { mkdir, writeFile } from "fs/promises";
import { join, resolve } from "path";
import { randomBytes } from "crypto";
//#region src/channels/attachments/inbound-persist.ts
/**
* Persist inbound channel / Web UI uploads under the agent home dir so the markdown
* workspace stays user-visible; session transcripts reference stable relative paths.
*/
init_logger();
const log = createLogger("InboundPersist");
/** New layout: `<agentHome>/inbound/<session>/` — rel paths use this prefix. */
const INBOUND_REL_ROOT = "inbound";
function sanitizeSessionSegment(sessionKey) {
	return sessionKey.replace(/[^a-zA-Z0-9_.-]+/g, "_").slice(0, 180) || "session";
}
function sanitizeFilename(name) {
	return (name.replace(/[/\\?%*:|"<>]/g, "_").trim() || "file").slice(0, 200);
}
function isImageAttachment(att) {
	return att.type === "image" || att.type === "photo" || att.mimeType?.startsWith("image/") === true;
}
/** Decode base64 or data-URL payload for inbound attachments (also used by voice STT). */
function decodeInboundAttachmentBase64(data) {
	const trimmed = data.trim();
	const b64 = trimmed.startsWith("data:") ? trimmed.split(/base64,/)[1] ?? trimmed : trimmed;
	return Buffer.from(b64.replace(/\s/g, ""), "base64");
}
function resolveUnderRoot(root, rel, requiredPrefix) {
	const normalized = rel.replace(/\\/g, "/").replace(/^\/+/, "");
	if (normalized.includes("..") || !normalized.startsWith(requiredPrefix)) return null;
	const abs = resolve(root, ...normalized.split("/"));
	const rootResolved = resolve(root);
	if (!abs.startsWith(rootResolved)) return null;
	return abs;
}
/**
* Write non-image attachments with binary data to disk; returns a shallow copy
* of each attachment with `workspaceRelativePath` set (POSIX-style, `/` separators).
*/
async function persistInboundAttachmentsToWorkspace(agentHomeRoot, sessionKey, attachments) {
	if (!attachments?.length) return attachments;
	const sessionSeg = sanitizeSessionSegment(sessionKey);
	const inboundAbs = resolve(agentHomeRoot, INBOUND_REL_ROOT, sessionSeg);
	await mkdir(inboundAbs, { recursive: true });
	const out = [];
	for (const att of attachments) {
		if (att.workspaceRelativePath) {
			out.push({ ...att });
			continue;
		}
		if (isImageAttachment(att)) {
			out.push({ ...att });
			continue;
		}
		if (!att.data || att.data.length === 0) {
			out.push({ ...att });
			continue;
		}
		try {
			const buf = decodeInboundAttachmentBase64(att.data);
			const fname = `${randomBytes(8).toString("hex")}_${sanitizeFilename(att.name || "file")}`;
			await writeFile(join(inboundAbs, fname), buf);
			const workspaceRelativePath = [
				INBOUND_REL_ROOT,
				sessionSeg,
				fname
			].join("/");
			log.debug({
				sessionKey,
				workspaceRelativePath,
				bytes: buf.length
			}, "Inbound file persisted");
			out.push({
				...att,
				workspaceRelativePath,
				size: att.size ?? buf.length
			});
		} catch (err) {
			log.warn({
				err,
				sessionKey,
				name: att.name
			}, "Failed to persist inbound attachment");
			out.push({ ...att });
		}
	}
	return out;
}
/**
* Build transcript text for a non-image file for the LLM (includes machine-readable path lines).
*/
function formatInboundFileTextBlock(att, storageRootAbs) {
	const head = `[File: ${att.name || "unknown"} (${att.mimeType || "unknown type"}, ${att.size ?? 0} bytes)]`;
	if (!att.workspaceRelativePath) return head;
	const rel = att.workspaceRelativePath.replace(/\\/g, "/");
	return `${head}\nxopc-path:rel:${rel}\nxopc-path:abs:${resolve(storageRootAbs, ...rel.split("/").filter(Boolean))}`;
}
/**
* Remove inbound file transcript blocks from a string (e.g. auto session titles).
* Matches Web UI `stripInboundFileMachineText`, plus bare `[File: …]` lines when paths are absent.
*/
function stripInboundFileMetadataFromText(text) {
	if (!text.includes("[File:") && !text.includes("xopc-path:")) return text;
	let out = text;
	out = out.replace(/\s*\[File:[^\]]+\]\s*\r?\nxopc-path:rel:[^\r\n]+\r?\n\s*xopc-path:abs:[^\r\n]+/g, "");
	out = out.replace(/\s*\[File:[^\]]+\]\s+xopc-path:rel:\S+\s+xopc-path:abs:\S+/g, "");
	out = out.replace(/\s*\[File:[^\]]+\]\s*xopc-path:rel:\S+\s*xopc-path:abs:\S+/g, "");
	out = out.replace(/\s*\[File:[^\]]+\]\s*/g, " ");
	return out.replace(/\n{3,}/g, "\n\n").replace(/\s{2,}/g, " ").trim();
}
/**
* Resolve a stored relative path under `inbound/`.
*/
function resolveSafeInboundFilePath(roots, relRaw) {
	const rel = relRaw.replace(/\\/g, "/").replace(/^\/+/, "");
	if (rel.includes("..")) return null;
	if (rel.startsWith(`inbound/`)) return resolveUnderRoot(roots.agentHome, rel, `${INBOUND_REL_ROOT}/`);
	return null;
}
//#endregion
export { INBOUND_REL_ROOT, decodeInboundAttachmentBase64, formatInboundFileTextBlock, persistInboundAttachmentsToWorkspace, resolveSafeInboundFilePath, stripInboundFileMetadataFromText };

//# sourceMappingURL=inbound-persist.js.map