import { InvalidUtf8TextDocumentError, TextDocumentControlCharacterError } from "../services/blob-storage/file-security-errors.js";
import { ALLOWED_TEXT_DOCUMENT_MIME_TYPES, canonicalizeMimeType, getExtensionFromMimeType, normalizeMimeType } from "@inkeep/agents-core/constants/allowed-file-formats";

//#region src/domains/run/utils/text-document-attachments.ts
function isDisallowedTextControlCharacter(codePoint) {
	return codePoint >= 0 && codePoint <= 8 || codePoint === 11 || codePoint === 12 || codePoint >= 14 && codePoint <= 31 || codePoint === 127;
}
function hasDisallowedControlCharacters(value) {
	for (const char of value) {
		const codePoint = char.codePointAt(0);
		if (codePoint === void 0 || isDisallowedTextControlCharacter(codePoint)) return true;
	}
	return false;
}
function isTextDocumentMimeType(mimeType) {
	if (!mimeType) return false;
	const canonical = canonicalizeMimeType(mimeType);
	return ALLOWED_TEXT_DOCUMENT_MIME_TYPES.has(canonical);
}
function getDefaultTextDocumentFilename(mimeType) {
	const extension = getExtensionFromMimeType(normalizeMimeType(mimeType));
	return `unnamed.${extension === "bin" ? "txt" : extension}`;
}
function decodeTextDocumentBytes(data) {
	let decoded;
	try {
		decoded = new TextDecoder("utf-8", { fatal: true }).decode(data);
	} catch {
		throw new InvalidUtf8TextDocumentError();
	}
	if (hasDisallowedControlCharacters(decoded)) throw new TextDocumentControlCharacterError();
	return decoded.replace(/\r\n?/g, "\n");
}
function buildTextAttachmentBlock(params) {
	const mimeType = normalizeMimeType(params.mimeType);
	const filename = params.filename?.trim() || getDefaultTextDocumentFilename(mimeType);
	return [
		`<attached_file filename=${JSON.stringify(filename)} media_type=${JSON.stringify(mimeType)}>`,
		params.content,
		"</attached_file>"
	].join("\n");
}
function buildDecodedTextAttachmentBlock(params) {
	const content = decodeTextDocumentBytes(params.data);
	return buildTextAttachmentBlock({
		mimeType: params.mimeType,
		content,
		filename: params.filename
	});
}
const UNAVAILABLE_ATTACHMENT_PLACEHOLDER = "[Attachment unavailable]";
/**
* Convenience wrapper around `buildTextAttachmentBlock` for the failure path —
* produces an `<attached_file>...[Attachment unavailable]...</attached_file>` block
* so the model still sees that an attachment existed even when its bytes are lost.
*/
function buildUnavailableTextAttachmentBlock(params) {
	return buildTextAttachmentBlock({
		mimeType: params.mimeType,
		content: UNAVAILABLE_ATTACHMENT_PLACEHOLDER,
		filename: params.filename
	});
}
/**
* Self-closing `<attached_file ... />` marker used when the attachment's content is not
* (or can no longer be) inlined — e.g. images/PDFs in conversation history, or a text
* attachment whose bytes failed to decode. The marker preserves provenance for the model
* so prior attachments don't silently disappear on resume. When `artifactId` and
* `toolCallId` are provided, the marker is self-describing enough for the model (or
* downstream tools) to fetch the attachment from the artifact ledger.
*/
function buildAttachedFileMarker(params) {
	const mimeType = params.mimeType ? normalizeMimeType(params.mimeType) : "";
	const filename = params.filename?.trim() || void 0;
	const artifactId = params.artifactId?.trim() || void 0;
	const toolCallId = params.toolCallId?.trim() || void 0;
	const attrs = [];
	if (filename) attrs.push(`filename=${JSON.stringify(filename)}`);
	if (mimeType) attrs.push(`media_type=${JSON.stringify(mimeType)}`);
	if (artifactId) attrs.push(`artifact_id=${JSON.stringify(artifactId)}`);
	if (toolCallId) attrs.push(`tool_call_id=${JSON.stringify(toolCallId)}`);
	return `<attached_file${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} />`;
}

//#endregion
export { UNAVAILABLE_ATTACHMENT_PLACEHOLDER, buildAttachedFileMarker, buildDecodedTextAttachmentBlock, buildTextAttachmentBlock, buildUnavailableTextAttachmentBlock, decodeTextDocumentBytes, getDefaultTextDocumentFilename, isTextDocumentMimeType };