import { y as resolveStateDir } from "./paths-CQv1_CDw.js";
import { r as logVerbose } from "./globals-2IPr8jQ_.js";
import { c as kindFromMime } from "./mime-DppuT-pZ.js";
import { r as stripInlineDirectiveTagsForDelivery } from "./directive-tags-DBD-mk-0.js";
import { t as createMessageReceiptFromOutboundResults } from "./receipt-KEYzve-j.js";
import { t as resolveOutboundAttachmentFromUrl } from "./outbound-attachment-Kca3Y9Hp.js";
import "./runtime-env-BlzZ_kGU.js";
import "./state-paths-CdKTFFfY.js";
import { t as resolveMarkdownTableMode } from "./markdown-tables-BmJHt0es.js";
import { t as convertMarkdownTables } from "./tables-By41L_33.js";
import { t as requireRuntimeConfig } from "./plugin-config-runtime-Ch_Z6D0D.js";
import "./media-runtime-BZyRVi-b.js";
import "./text-chunking-CuxNcZmc.js";
import "./markdown-table-runtime-CuMTkKNm.js";
import "./channel-message-GOML_zy1.js";
import { a as resolveIMessageAccount } from "./accounts-BET9ibA4.js";
import { t as createIMessageRpcClient } from "./client-DWCNprRX.js";
import { c as parseIMessageTarget, o as normalizeIMessageHandle, t as formatIMessageChatTarget } from "./targets-7lgbAW-k.js";
import { r as rememberIMessageReplyCache } from "./monitor-reply-cache-DERyDHI3.js";
import { i as appendIMessageApprovalReactionHintForOutboundMessage, s as registerIMessageApprovalReactionTargetForOutboundMessage } from "./normalize-DzKnkoZh.js";
import { t as extractMarkdownFormatRuns } from "./markdown-format-Cqti91oT.js";
import fs from "node:fs";
import path from "node:path";
import { spawn } from "node:child_process";
//#region extensions/imessage/src/monitor/persisted-echo-cache.ts
const PERSISTED_ECHO_TTL_MS = 720 * 60 * 1e3;
const MAX_PERSISTED_ECHO_ENTRIES = 256;
const PERSISTED_ECHO_DIR_MODE = 448;
const PERSISTED_ECHO_FILE_MODE = 384;
function resolvePersistedEchoPath() {
	return path.join(resolveStateDir(), "imessage", "sent-echoes.jsonl");
}
function clampPersistedEchoModes(filePath) {
	try {
		fs.chmodSync(path.dirname(filePath), PERSISTED_ECHO_DIR_MODE);
		fs.chmodSync(filePath, PERSISTED_ECHO_FILE_MODE);
	} catch {}
}
function normalizeText(text) {
	return text?.replace(/\r\n?/g, "\n").trim() || void 0;
}
function normalizeMessageId(messageId) {
	const normalized = messageId?.trim();
	if (!normalized || normalized === "ok" || normalized === "unknown") return;
	return normalized;
}
function parseEntry(line) {
	try {
		const parsed = JSON.parse(line);
		if (typeof parsed.scope !== "string" || typeof parsed.timestamp !== "number") return null;
		return {
			scope: parsed.scope,
			text: typeof parsed.text === "string" ? parsed.text : void 0,
			messageId: typeof parsed.messageId === "string" ? parsed.messageId : void 0,
			timestamp: parsed.timestamp
		};
	} catch {
		return null;
	}
}
let mirror = null;
let persistenceFailureLogged = false;
function reportFailure(scope, err) {
	if (persistenceFailureLogged) return;
	persistenceFailureLogged = true;
	logVerbose(`imessage echo-cache: ${scope} disabled after first failure: ${String(err)}`);
}
function loadMirrorIfStale() {
	const filePath = resolvePersistedEchoPath();
	let mtimeMs;
	try {
		mtimeMs = fs.statSync(filePath).mtimeMs;
	} catch (err) {
		if (err?.code !== "ENOENT") reportFailure("stat", err);
		mirror = {
			entries: [],
			mtimeMs: 0
		};
		return;
	}
	if (mirror && mirror.mtimeMs === mtimeMs) return;
	let raw;
	try {
		raw = fs.readFileSync(filePath, "utf8");
	} catch (err) {
		reportFailure("read", err);
		mirror = {
			entries: [],
			mtimeMs
		};
		return;
	}
	const cutoff = Date.now() - PERSISTED_ECHO_TTL_MS;
	mirror = {
		entries: raw.split(/\n+/).map(parseEntry).filter((entry) => Boolean(entry && entry.timestamp >= cutoff)).slice(-256),
		mtimeMs
	};
}
function readRecentEntries() {
	loadMirrorIfStale();
	return mirror?.entries ?? [];
}
const COMPACT_AT_ENTRY_COUNT = MAX_PERSISTED_ECHO_ENTRIES * 2;
function compactRecentEntries(entries) {
	const filePath = resolvePersistedEchoPath();
	try {
		fs.mkdirSync(path.dirname(filePath), {
			recursive: true,
			mode: PERSISTED_ECHO_DIR_MODE
		});
		fs.writeFileSync(filePath, entries.map((entry) => JSON.stringify(entry)).join("\n") + (entries.length ? "\n" : ""), {
			encoding: "utf8",
			mode: PERSISTED_ECHO_FILE_MODE
		});
		clampPersistedEchoModes(filePath);
	} catch (err) {
		reportFailure("compact", err);
		return;
	}
	let mtimeMs = 0;
	try {
		mtimeMs = fs.statSync(filePath).mtimeMs;
	} catch {}
	mirror = {
		entries: [...entries],
		mtimeMs
	};
}
function appendEntry(entry) {
	const filePath = resolvePersistedEchoPath();
	try {
		fs.mkdirSync(path.dirname(filePath), {
			recursive: true,
			mode: PERSISTED_ECHO_DIR_MODE
		});
		fs.appendFileSync(filePath, `${JSON.stringify(entry)}\n`, {
			encoding: "utf8",
			mode: PERSISTED_ECHO_FILE_MODE
		});
		clampPersistedEchoModes(filePath);
	} catch (err) {
		reportFailure("append", err);
		return;
	}
	let mtimeMs = 0;
	try {
		mtimeMs = fs.statSync(filePath).mtimeMs;
	} catch {}
	if (mirror) mirror = {
		entries: [...mirror.entries, entry],
		mtimeMs
	};
	else mirror = {
		entries: [entry],
		mtimeMs
	};
}
function rememberPersistedIMessageEcho(params) {
	const entry = {
		scope: params.scope,
		text: normalizeText(params.text),
		messageId: normalizeMessageId(params.messageId),
		timestamp: Date.now()
	};
	if (!entry.text && !entry.messageId) return;
	loadMirrorIfStale();
	appendEntry(entry);
	const total = mirror?.entries.length ?? 0;
	const cutoff = Date.now() - PERSISTED_ECHO_TTL_MS;
	const oldestStale = mirror?.entries[0] && mirror.entries[0].timestamp < cutoff;
	if (total > COMPACT_AT_ENTRY_COUNT || oldestStale) compactRecentEntries((mirror?.entries ?? []).filter((e) => e.timestamp >= cutoff).slice(-256));
}
function hasPersistedIMessageEcho(params) {
	const text = normalizeText(params.text);
	const messageId = normalizeMessageId(params.messageId);
	if (!text && !messageId) return false;
	for (const entry of readRecentEntries()) {
		if (entry.scope !== params.scope) continue;
		if (messageId && entry.messageId === messageId) return true;
		if (text && entry.text === text) return true;
	}
	return false;
}
//#endregion
//#region extensions/imessage/src/send.ts
const MAX_REPLY_TO_ID_LENGTH = 256;
function stripUnsafeReplyTagChars(value) {
	let next = "";
	for (const ch of value) {
		const code = ch.charCodeAt(0);
		if (code >= 0 && code <= 31 || code === 127 || ch === "[" || ch === "]") continue;
		next += ch;
	}
	return next;
}
function sanitizeReplyToId(rawReplyToId) {
	const trimmed = rawReplyToId?.trim();
	if (!trimmed) return;
	const sanitized = stripUnsafeReplyTagChars(trimmed).trim();
	if (!sanitized) return;
	if (sanitized.length > MAX_REPLY_TO_ID_LENGTH) return sanitized.slice(0, MAX_REPLY_TO_ID_LENGTH);
	return sanitized;
}
function resolveMessageId(result) {
	if (!result) return null;
	const raw = typeof result.messageId === "string" && result.messageId.trim() || typeof result.message_id === "string" && result.message_id.trim() || typeof result.id === "string" && result.id.trim() || typeof result.guid === "string" && result.guid.trim() || (typeof result.message_id === "number" ? String(result.message_id) : null) || (typeof result.id === "number" ? String(result.id) : null);
	return raw ? raw.trim() : null;
}
function resolveOutboundMessageGuid(result) {
	if (!result) return null;
	const candidates = [
		result.guid,
		result.messageId,
		result.message_id,
		result.id
	];
	for (const value of candidates) {
		if (typeof value !== "string") continue;
		const trimmed = value.trim();
		if (trimmed && !/^\d+$/.test(trimmed)) return trimmed;
	}
	return null;
}
function resolveOutboundEchoText(text, mediaContentType) {
	if (text.trim()) return text;
	const kind = kindFromMime(mediaContentType ?? void 0);
	if (!kind) return;
	return kind === "image" ? "<media:image>" : `<media:${kind}>`;
}
function createIMessageSendReceipt(params) {
	const messageId = params.messageId.trim();
	const results = messageId && messageId !== "unknown" && messageId !== "ok" ? [{
		channel: "imessage",
		messageId,
		meta: { targetKind: params.target.kind }
	}] : [];
	if (results[0]) {
		if (params.target.kind === "chat_id") results[0].chatId = String(params.target.chatId);
		else if (params.target.kind === "chat_guid") results[0].conversationId = params.target.chatGuid;
		else if (params.target.kind === "chat_identifier") results[0].conversationId = params.target.chatIdentifier;
	}
	const receiptParams = {
		results,
		kind: params.kind
	};
	if (params.replyToId) receiptParams.replyToId = params.replyToId;
	return createMessageReceiptFromOutboundResults(receiptParams);
}
function resolveOutboundEchoScope(params) {
	if (params.target.kind === "chat_id") return `${params.accountId}:${formatIMessageChatTarget(params.target.chatId)}`;
	if (params.target.kind === "chat_guid") return `${params.accountId}:chat_guid:${params.target.chatGuid}`;
	if (params.target.kind === "chat_identifier") return `${params.accountId}:chat_identifier:${params.target.chatIdentifier}`;
	return `${params.accountId}:imessage:${params.target.to}`;
}
function buildIMessageCliJsonArgs(args, dbPath) {
	const trimmedDbPath = dbPath?.trim();
	return [
		...args,
		...trimmedDbPath ? ["--db", trimmedDbPath] : [],
		"--json"
	];
}
function resolveIMessageCliFailure(result) {
	if (result.success !== false) return null;
	return typeof result.error === "string" && result.error.trim() ? result.error.trim() : "iMessage action failed";
}
async function runIMessageCliJson(cliPath, dbPath, args, timeoutMs) {
	return await new Promise((resolve, reject) => {
		const child = spawn(cliPath, buildIMessageCliJsonArgs(args, dbPath), { stdio: [
			"ignore",
			"pipe",
			"pipe"
		] });
		let stdout = "";
		let stderr = "";
		let killEscalation = null;
		const timer = timeoutMs && timeoutMs > 0 ? setTimeout(() => {
			child.kill("SIGTERM");
			killEscalation = setTimeout(() => {
				try {
					child.kill("SIGKILL");
				} catch {}
			}, 2e3);
			reject(/* @__PURE__ */ new Error(`iMessage action timed out after ${timeoutMs}ms`));
		}, timeoutMs) : null;
		child.stdout.setEncoding("utf8");
		child.stderr.setEncoding("utf8");
		child.stdout.on("data", (chunk) => {
			stdout += chunk;
		});
		child.stderr.on("data", (chunk) => {
			stderr += chunk;
		});
		child.on("error", (error) => {
			if (timer) clearTimeout(timer);
			if (killEscalation) clearTimeout(killEscalation);
			reject(error);
		});
		child.on("close", (code) => {
			if (timer) clearTimeout(timer);
			if (killEscalation) clearTimeout(killEscalation);
			const last = stdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).at(-1);
			let parsed = null;
			if (last) try {
				const json = JSON.parse(last);
				if (json && typeof json === "object" && !Array.isArray(json)) parsed = json;
			} catch {}
			if (code === 0 && parsed) {
				const failure = resolveIMessageCliFailure(parsed);
				if (failure) {
					reject(new Error(failure));
					return;
				}
				resolve(parsed);
				return;
			}
			if (parsed && typeof parsed.error === "string" && parsed.error.trim()) {
				reject(new Error(parsed.error.trim()));
				return;
			}
			const detail = stderr.trim() || stdout.trim() || `imsg exited with code ${code}`;
			reject(new Error(detail));
		});
	});
}
function stringValue(value) {
	return typeof value === "string" && value.trim() ? value.trim() : void 0;
}
function isAttachmentCommandFallbackError(error) {
	const message = error instanceof Error ? error.message : String(error);
	return /(?:unknown|unrecognized|invalid|unsupported)\s+(?:command|subcommand)|not a recognized command|send-attachment.*(?:not found|unsupported|unavailable)|private api bridge.*unavailable|requires the imsg private api bridge|run imsg launch/iu.test(message);
}
async function resolveAttachmentChatGuid(params) {
	if (params.target.kind === "chat_guid") return params.target.chatGuid;
	if (params.target.kind !== "chat_id") return null;
	const result = await params.runCliJson([
		"group",
		"--chat-id",
		String(params.target.chatId)
	]);
	return stringValue(result.guid) ?? stringValue(result.chat_guid) ?? null;
}
async function trySendAttachmentForExplicitChat(params) {
	let attachmentChatGuid = null;
	try {
		attachmentChatGuid = await resolveAttachmentChatGuid({
			target: params.target,
			runCliJson: params.runCliJson
		});
	} catch (error) {
		if (isAttachmentCommandFallbackError(error)) return null;
		throw error;
	}
	if (!attachmentChatGuid) return null;
	let result;
	try {
		result = await params.runCliJson([
			"send-attachment",
			"--chat",
			attachmentChatGuid,
			"--file",
			params.filePath,
			"--transport",
			"auto"
		]);
	} catch (error) {
		if (isAttachmentCommandFallbackError(error)) return null;
		throw error;
	}
	const failure = resolveIMessageCliFailure(result);
	if (failure) {
		const error = new Error(failure);
		if (isAttachmentCommandFallbackError(error)) return null;
		throw error;
	}
	const resolvedId = resolveMessageId(result);
	const approvalBindingMessageId = resolveOutboundMessageGuid(result);
	const messageId = resolvedId ?? (result.ok || result.success ? "ok" : "unknown");
	const echoScope = resolveOutboundEchoScope({
		accountId: params.accountId,
		target: params.target
	});
	if (echoScope) rememberPersistedIMessageEcho({
		scope: echoScope,
		text: params.echoText,
		messageId: resolvedId ?? void 0
	});
	if (resolvedId) rememberIMessageReplyCache({
		accountId: params.accountId,
		messageId: resolvedId,
		chatGuid: params.target.kind === "chat_guid" ? params.target.chatGuid : attachmentChatGuid,
		chatId: params.target.kind === "chat_id" ? params.target.chatId : void 0,
		timestamp: Date.now(),
		isFromMe: true
	});
	return {
		messageId,
		...approvalBindingMessageId ? { guid: approvalBindingMessageId } : {},
		sentText: "",
		...params.echoText ? { echoText: params.echoText } : {},
		receipt: createIMessageSendReceipt({
			messageId,
			target: params.target,
			kind: "media"
		})
	};
}
async function sendMessageIMessage(to, text, opts) {
	const cfg = requireRuntimeConfig(opts.config, "iMessage send");
	const account = opts.account ?? resolveIMessageAccount({
		cfg,
		accountId: opts.accountId
	});
	const cliPath = opts.cliPath?.trim() || account.config.cliPath?.trim() || "imsg";
	const dbPath = opts.dbPath?.trim() || account.config.dbPath?.trim();
	const target = parseIMessageTarget(opts.chatId ? formatIMessageChatTarget(opts.chatId) : to);
	const service = opts.service ?? (target.kind === "handle" ? target.service : void 0) ?? account.config.service;
	const region = opts.region?.trim() || account.config.region?.trim() || "US";
	const maxBytes = typeof opts.maxBytes === "number" ? opts.maxBytes : typeof account.config.mediaMaxMb === "number" ? account.config.mediaMaxMb * 1024 * 1024 : 16 * 1024 * 1024;
	let message = text ? appendIMessageApprovalReactionHintForOutboundMessage(text) : "";
	let filePath;
	let mediaContentType;
	if (opts.mediaUrl?.trim()) {
		const resolved = await (opts.resolveAttachmentImpl ?? resolveOutboundAttachmentFromUrl)(opts.mediaUrl.trim(), maxBytes, {
			localRoots: opts.mediaLocalRoots,
			readFile: opts.mediaReadFile
		});
		filePath = resolved.path;
		mediaContentType = resolved.contentType ?? void 0;
	}
	if (!message.trim() && !filePath) throw new Error("iMessage send requires text or media");
	if (message.trim()) {
		const tableMode = resolveMarkdownTableMode({
			cfg,
			channel: "imessage",
			accountId: account.accountId
		});
		message = convertMarkdownTables(message, tableMode);
	}
	message = stripInlineDirectiveTagsForDelivery(message).text;
	if (!message.trim() && !filePath) throw new Error("iMessage send requires text or media");
	const formatted = message.trim() ? extractMarkdownFormatRuns(message) : {
		text: message,
		ranges: []
	};
	message = formatted.text;
	if (!message.trim() && !filePath) throw new Error("iMessage send requires text or media");
	const echoText = resolveOutboundEchoText(message, filePath ? mediaContentType : void 0);
	const resolvedReplyToId = sanitizeReplyToId(opts.replyToId);
	const runCliJson = opts.runCliJson ?? ((args) => runIMessageCliJson(cliPath, dbPath, args, opts.timeoutMs));
	if (filePath && !message.trim() && !resolvedReplyToId) {
		const attachmentResult = await trySendAttachmentForExplicitChat({
			accountId: account.accountId,
			target,
			filePath,
			echoText,
			runCliJson
		});
		if (attachmentResult) return attachmentResult;
	}
	const params = {
		text: message,
		service: service || "auto",
		region
	};
	if (resolvedReplyToId) params.reply_to = resolvedReplyToId;
	if (formatted.ranges.length > 0) params.formatting = formatted.ranges;
	if (filePath) params.file = filePath;
	if (target.kind === "chat_id") params.chat_id = target.chatId;
	else if (target.kind === "chat_guid") params.chat_guid = target.chatGuid;
	else if (target.kind === "chat_identifier") params.chat_identifier = target.chatIdentifier;
	else params.to = target.to;
	const client = opts.client ?? (opts.createClient ? await opts.createClient({
		cliPath,
		dbPath
	}) : await createIMessageRpcClient({
		cliPath,
		dbPath
	}));
	const shouldClose = !opts.client;
	try {
		const result = await client.request("send", params, { timeoutMs: opts.timeoutMs });
		const resolvedId = resolveMessageId(result);
		const messageId = resolvedId ?? (result?.ok ? "ok" : "unknown");
		const approvalBindingMessageId = resolveOutboundMessageGuid(result);
		const echoScope = resolveOutboundEchoScope({
			accountId: account.accountId,
			target
		});
		if (echoScope) rememberPersistedIMessageEcho({
			scope: echoScope,
			text: echoText,
			messageId: resolvedId ?? void 0
		});
		if (resolvedId) {
			rememberIMessageReplyCache({
				accountId: account.accountId,
				messageId: resolvedId,
				chatGuid: target.kind === "chat_guid" ? target.chatGuid : void 0,
				chatIdentifier: target.kind === "chat_identifier" ? target.chatIdentifier : target.kind === "handle" ? `${target.service === "sms" ? "SMS" : "iMessage"};-;${target.to}` : void 0,
				chatId: target.kind === "chat_id" ? target.chatId : void 0,
				timestamp: Date.now(),
				isFromMe: true
			});
			if (message) {
				if (approvalBindingMessageId) {
					const handleForKey = target.kind === "handle" ? normalizeIMessageHandle(target.to) : void 0;
					const conversation = {
						...target.kind === "chat_guid" ? { chatGuid: target.chatGuid } : {},
						...target.kind === "chat_identifier" ? { chatIdentifier: target.chatIdentifier } : {},
						...target.kind === "chat_id" ? { chatId: target.chatId } : {},
						...handleForKey ? { handle: handleForKey } : {}
					};
					registerIMessageApprovalReactionTargetForOutboundMessage({
						accountId: account.accountId,
						conversation,
						messageId: approvalBindingMessageId,
						text: message
					});
				}
			}
		}
		return {
			messageId,
			...approvalBindingMessageId ? { guid: approvalBindingMessageId } : {},
			sentText: message,
			...echoText ? { echoText } : {},
			receipt: createIMessageSendReceipt({
				messageId,
				target,
				kind: filePath ? "media" : "text",
				...resolvedReplyToId ? { replyToId: resolvedReplyToId } : {}
			})
		};
	} finally {
		if (shouldClose) await client.stop();
	}
}
//#endregion
export { hasPersistedIMessageEcho as n, sendMessageIMessage as t };
