import { DEFAULT_ACCOUNT_ID, emptyPluginConfigSchema, formatPairingApproveHint, missingTargetError } from "minion/plugin-sdk";
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, normalizeAccountId } from "minion/plugin-sdk/account-id";
import { createReplyPrefixOptions, readJsonBodyWithLimit, registerWebhookTarget, rejectNonPostWebhookRequest, requestBodyErrorToText, resolveWebhookPath, resolveWebhookTargets } from "openclaw/plugin-sdk";

//#region \0rolldown/runtime.js
var __defProp = Object.defineProperty;
var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
var __exportAll = (all, no_symbols) => {
	let target = {};
	for (var name in all) {
		__defProp(target, name, {
			get: all[name],
			enumerable: true
		});
	}
	if (!no_symbols) {
		__defProp(target, Symbol.toStringTag, { value: "Module" });
	}
	return target;
};

//#endregion
//#region extensions/wati/src/accounts.ts
const ENV_WATI_API_URL = "WATI_API_URL";
const ENV_WATI_API_TOKEN = "WATI_API_TOKEN";
function listConfiguredAccountIds(cfg) {
	const accounts = cfg.channels?.wati;
	if (!accounts?.accounts || typeof accounts.accounts !== "object") return [];
	return Object.keys(accounts.accounts).filter(Boolean);
}
function listWatiAccountIds(cfg) {
	const ids = listConfiguredAccountIds(cfg);
	if (ids.length === 0) return [DEFAULT_ACCOUNT_ID$1];
	return ids.toSorted((a, b) => a.localeCompare(b));
}
function resolveDefaultWatiAccountId(cfg) {
	const channel = cfg.channels?.wati;
	if (channel?.defaultAccount?.trim()) return channel.defaultAccount.trim();
	const ids = listWatiAccountIds(cfg);
	if (ids.includes(DEFAULT_ACCOUNT_ID$1)) return DEFAULT_ACCOUNT_ID$1;
	return ids[0] ?? DEFAULT_ACCOUNT_ID$1;
}
function resolveAccountConfig(cfg, accountId) {
	const watiSection = cfg.channels?.wati;
	if (!watiSection?.accounts || typeof watiSection.accounts !== "object") return;
	return watiSection.accounts[accountId];
}
function mergeWatiAccountConfig(cfg, accountId) {
	const { accounts: _ignored, defaultAccount: _ignored2, ...base } = cfg.channels?.wati ?? {};
	const account = resolveAccountConfig(cfg, accountId) ?? {};
	return {
		...base,
		...account
	};
}
function resolveApiCredentials(accountId, account) {
	if (account.apiUrl?.trim() && account.apiToken?.trim()) return {
		apiUrl: account.apiUrl.trim(),
		apiToken: account.apiToken.trim()
	};
	if (accountId === DEFAULT_ACCOUNT_ID$1) {
		const envUrl = process.env[ENV_WATI_API_URL]?.trim();
		const envToken = process.env[ENV_WATI_API_TOKEN]?.trim();
		if (envUrl && envToken) return {
			apiUrl: envUrl,
			apiToken: envToken
		};
	}
	return {
		apiUrl: account.apiUrl?.trim() || void 0,
		apiToken: account.apiToken?.trim() || void 0
	};
}
function resolveWatiAccount(params) {
	const accountId = normalizeAccountId(params.accountId);
	const baseEnabled = (params.cfg.channels?.wati)?.enabled !== false;
	const merged = mergeWatiAccountConfig(params.cfg, accountId);
	const accountEnabled = merged.enabled !== false;
	const enabled = baseEnabled && accountEnabled;
	const credentials = resolveApiCredentials(accountId, merged);
	return {
		accountId,
		name: merged.name?.trim() || void 0,
		enabled,
		apiUrl: credentials.apiUrl,
		apiToken: credentials.apiToken,
		config: merged
	};
}

//#endregion
//#region extensions/wati/src/targets.ts
/**
* WATI phone number targeting and E.164 normalization.
*/
/**
* Normalize a phone number to E.164 format.
* Strips common prefixes (wati:, whatsapp:, +) and non-digit characters,
* then prepends "+".
*/
function normalizeE164(raw) {
	const digits = raw.trim().replace(/^(wati|whatsapp):/i, "").trim().replace(/[^\d+]/g, "");
	if (digits.startsWith("+")) return `+${digits.slice(1).replace(/\D/g, "")}`;
	if (!digits) return "";
	return `+${digits}`;
}
/**
* Check if a value looks like a WATI phone target (E.164 phone number).
*/
function looksLikeWatiTarget(raw) {
	const normalized = normalizeE164(raw);
	return /^\+\d{7,15}$/.test(normalized);
}
/**
* Normalize a WATI target. Returns null if invalid.
*/
function normalizeWatiTarget(raw) {
	const normalized = normalizeE164(raw);
	if (!normalized || !looksLikeWatiTarget(normalized)) return null;
	return normalized;
}
/**
* Check if a phone number is in the allowlist.
*/
function isPhoneAllowed(phone, allowFrom) {
	if (allowFrom.includes("*")) return true;
	const normalizedPhone = normalizeE164(phone);
	if (!normalizedPhone) return false;
	return allowFrom.some((entry) => {
		const entryStr = String(entry).trim();
		if (entryStr === "*") return true;
		return normalizeE164(entryStr) === normalizedPhone;
	});
}
var init_targets = __esmMin((() => {}));

//#endregion
//#region extensions/wati/src/api.ts
var api_exports = /* @__PURE__ */ __exportAll({
	probeWati: () => probeWati,
	sendWatiMedia: () => sendWatiMedia,
	sendWatiMessage: () => sendWatiMessage
});
async function sendWatiMessage(params) {
	const { account, to, text } = params;
	const apiUrl = account.apiUrl;
	const apiToken = account.apiToken;
	if (!apiUrl || !apiToken) throw new Error("WATI API URL or token not configured");
	const phone = normalizeE164(to).replace(/^\+/, "");
	if (!phone) throw new Error(`Invalid WATI target: ${to}`);
	const url = `${apiUrl.replace(/\/$/, "")}/api/v1/sendSessionMessage/${phone}`;
	const response = await fetch(url, {
		method: "POST",
		headers: {
			Authorization: `Bearer ${apiToken}`,
			"Content-Type": "application/json"
		},
		body: JSON.stringify({ messageText: text })
	});
	if (!response.ok) {
		const body = await response.text().catch(() => "");
		throw new Error(`WATI API error ${response.status}: ${body}`);
	}
	const result = await response.json();
	return {
		ok: result.result !== false,
		info: result.info
	};
}
async function sendWatiMedia(params) {
	const { account, to, mediaUrl, caption, fileName } = params;
	const apiUrl = account.apiUrl;
	const apiToken = account.apiToken;
	if (!apiUrl || !apiToken) throw new Error("WATI API URL or token not configured");
	const phone = normalizeE164(to).replace(/^\+/, "");
	if (!phone) throw new Error(`Invalid WATI target: ${to}`);
	const url = `${apiUrl.replace(/\/$/, "")}/api/v1/sendSessionFile/${phone}`;
	const body = new FormData();
	const mediaResponse = await fetch(mediaUrl);
	if (!mediaResponse.ok) throw new Error(`Failed to fetch media: ${mediaResponse.status}`);
	const blob = await mediaResponse.blob();
	body.append("file", blob, fileName ?? "attachment");
	if (caption) body.append("caption", caption);
	const response = await fetch(url, {
		method: "POST",
		headers: { Authorization: `Bearer ${apiToken}` },
		body
	});
	if (!response.ok) {
		const responseBody = await response.text().catch(() => "");
		throw new Error(`WATI API error ${response.status}: ${responseBody}`);
	}
	const result = await response.json();
	return {
		ok: result.result !== false,
		info: result.info
	};
}
async function probeWati(account) {
	const apiUrl = account.apiUrl;
	const apiToken = account.apiToken;
	if (!apiUrl || !apiToken) return {
		ok: false,
		error: "API URL or token not configured"
	};
	try {
		const url = `${apiUrl.replace(/\/$/, "")}/api/v1/getContacts`;
		const response = await fetch(url, {
			method: "GET",
			headers: { Authorization: `Bearer ${apiToken}` },
			signal: AbortSignal.timeout(1e4)
		});
		if (!response.ok) return {
			ok: false,
			error: `HTTP ${response.status}`
		};
		return { ok: true };
	} catch (err) {
		return {
			ok: false,
			error: String(err)
		};
	}
}
var init_api = __esmMin((() => {
	init_targets();
}));

//#endregion
//#region extensions/wati/src/runtime.ts
init_api();
let runtime = null;
function setWatiRuntime(next) {
	runtime = next;
}
function getWatiRuntime() {
	if (!runtime) throw new Error("WATI runtime not initialized");
	return runtime;
}

//#endregion
//#region extensions/wati/src/monitor.ts
init_targets();
const webhookTargets = /* @__PURE__ */ new Map();
function logVerbose(core, runtime, message) {
	if (core.logging.shouldLogVerbose()) runtime.log?.(`[wati] ${message}`);
}
function registerWatiWebhookTarget(target) {
	return registerWebhookTarget(webhookTargets, target).unregister;
}
/**
* Verify WATI webhook request using optional secret.
* WATI supports a webhook secret that is sent as a query parameter or header.
*/
function verifyWatiWebhook(req, account) {
	const secret = account.config.webhookSecret?.trim();
	if (!secret) return true;
	if (req.headers["x-wati-secret"] === secret) return true;
	return new URL(req.url ?? "/", "http://localhost").searchParams.get("secret") === secret;
}
async function handleWatiWebhookRequest(req, res) {
	const resolved = resolveWebhookTargets(req, webhookTargets);
	if (!resolved) return false;
	const { targets } = resolved;
	if (rejectNonPostWebhookRequest(req, res)) return true;
	const body = await readJsonBodyWithLimit(req, {
		maxBytes: 1024 * 1024,
		timeoutMs: 3e4,
		emptyObjectOnEmpty: false
	});
	if (!body.ok) {
		res.statusCode = body.code === "PAYLOAD_TOO_LARGE" ? 413 : body.code === "REQUEST_BODY_TIMEOUT" ? 408 : 400;
		res.end(body.code === "REQUEST_BODY_TIMEOUT" ? requestBodyErrorToText("REQUEST_BODY_TIMEOUT") : body.error);
		return true;
	}
	const raw = body.value;
	if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
		res.statusCode = 400;
		res.end("invalid payload");
		return true;
	}
	const event = raw;
	if (event.owner === true) {
		res.statusCode = 200;
		res.end("{}");
		return true;
	}
	const matchedTargets = [];
	for (const target of targets) if (verifyWatiWebhook(req, target.account)) {
		matchedTargets.push(target);
		if (matchedTargets.length > 1) break;
	}
	if (matchedTargets.length === 0) {
		res.statusCode = 401;
		res.end("unauthorized");
		return true;
	}
	if (matchedTargets.length > 1) {
		res.statusCode = 401;
		res.end("ambiguous webhook target");
		return true;
	}
	const selected = matchedTargets[0];
	selected.statusSink?.({ lastInboundAt: Date.now() });
	processWatiEvent(event, selected).catch((err) => {
		selected.runtime.error?.(`[${selected.account.accountId}] WATI webhook failed: ${String(err)}`);
	});
	res.statusCode = 200;
	res.setHeader("Content-Type", "application/json");
	res.end("{}");
	return true;
}
async function processWatiEvent(event, target) {
	const { account, config, runtime, core, statusSink } = target;
	const eventType = event.eventType ?? "";
	if (eventType && eventType !== "message") {
		logVerbose(core, runtime, `skip non-message event: ${eventType}`);
		return;
	}
	const waId = event.waId?.trim();
	if (!waId) {
		logVerbose(core, runtime, "skip event: missing waId");
		return;
	}
	const senderId = normalizeE164(waId);
	if (!senderId) {
		logVerbose(core, runtime, `skip event: invalid waId "${waId}"`);
		return;
	}
	const senderName = event.senderName?.trim() ?? "";
	const dmPolicy = account.config.dm?.policy ?? "pairing";
	const configAllowFrom = (account.config.dm?.allowFrom ?? account.config.allowFrom ?? []).map((v) => String(v));
	const shouldComputeAuth = core.channel.commands.shouldComputeCommandAuthorized(event.text ?? "", config);
	const storeAllowFrom = dmPolicy !== "open" || shouldComputeAuth ? await core.channel.pairing.readAllowFromStore("wati").catch(() => []) : [];
	const effectiveAllowFrom = [...configAllowFrom, ...storeAllowFrom];
	if (dmPolicy === "disabled" || account.config.dm?.enabled === false) {
		logVerbose(core, runtime, `blocked WATI DM from ${senderId} (dmPolicy=disabled)`);
		return;
	}
	const senderAllowed = isPhoneAllowed(senderId, effectiveAllowFrom);
	if (dmPolicy !== "open" && !senderAllowed) {
		if (dmPolicy === "pairing") {
			const { code, created } = await core.channel.pairing.upsertPairingRequest({
				channel: "wati",
				id: senderId,
				meta: { name: senderName || void 0 }
			});
			if (created) {
				logVerbose(core, runtime, `wati pairing request sender=${senderId}`);
				try {
					await sendWatiMessage({
						account,
						to: senderId,
						text: core.channel.pairing.buildPairingReply({
							channel: "wati",
							idLine: `Your WhatsApp number: ${senderId}`,
							code
						})
					});
					statusSink?.({ lastOutboundAt: Date.now() });
				} catch (err) {
					logVerbose(core, runtime, `pairing reply failed for ${senderId}: ${String(err)}`);
				}
			}
		} else logVerbose(core, runtime, `blocked unauthorized WATI sender ${senderId} (dmPolicy=${dmPolicy})`);
		return;
	}
	const messageText = (event.text ?? "").trim();
	const hasMedia = event.type !== "text" && event.type !== void 0 && event.data?.url;
	const rawBody = messageText || (hasMedia ? `<media:${event.type ?? "attachment"}>` : "");
	if (!rawBody) return;
	const useAccessGroups = config.commands?.useAccessGroups !== false;
	const commandAuthorized = shouldComputeAuth ? core.channel.commands.resolveCommandAuthorizedFromAuthorizers({
		useAccessGroups,
		authorizers: [{
			configured: effectiveAllowFrom.length > 0,
			allowed: senderAllowed
		}]
	}) : void 0;
	if (core.channel.commands.isControlCommandMessage(rawBody, config) && commandAuthorized !== true) {
		logVerbose(core, runtime, `wati: drop control command from ${senderId}`);
		return;
	}
	const route = core.channel.routing.resolveAgentRoute({
		cfg: config,
		channel: "wati",
		accountId: account.accountId,
		peer: {
			kind: "direct",
			id: senderId
		}
	});
	let mediaPath;
	let mediaType;
	if (hasMedia && event.data?.url) try {
		const maxBytes = (account.config.mediaMaxMb ?? 20) * 1024 * 1024;
		const loaded = await core.channel.media.fetchRemoteMedia({
			url: event.data.url,
			maxBytes
		});
		const saved = await core.channel.media.saveMediaBuffer(loaded.buffer, loaded.contentType ?? event.data.mimeType, "inbound", maxBytes, event.data.fileName);
		mediaPath = saved.path;
		mediaType = saved.contentType;
	} catch (err) {
		runtime.error?.(`[wati] media download failed: ${String(err)}`);
	}
	const fromLabel = senderName || senderId;
	const storePath = core.channel.session.resolveStorePath(config.session?.store, { agentId: route.agentId });
	const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(config);
	const previousTimestamp = core.channel.session.readSessionUpdatedAt({
		storePath,
		sessionKey: route.sessionKey
	});
	const body = core.channel.reply.formatAgentEnvelope({
		channel: "WATI WhatsApp",
		from: fromLabel,
		timestamp: event.timestamp ? Number(event.timestamp) * 1e3 : void 0,
		previousTimestamp,
		envelope: envelopeOptions,
		body: rawBody
	});
	const ctxPayload = core.channel.reply.finalizeInboundContext({
		Body: body,
		BodyForAgent: rawBody,
		RawBody: rawBody,
		CommandBody: rawBody,
		From: `wati:${senderId}`,
		To: `wati:${account.config.channelPhoneNumber ?? account.accountId}`,
		SessionKey: route.sessionKey,
		AccountId: route.accountId,
		ChatType: "direct",
		ConversationLabel: fromLabel,
		SenderName: senderName || void 0,
		SenderId: senderId,
		CommandAuthorized: commandAuthorized,
		Provider: "wati",
		Surface: "wati",
		MessageSid: event.whatsappMessageId ?? event.id,
		MessageSidFull: event.whatsappMessageId ?? event.id,
		MediaPath: mediaPath,
		MediaType: mediaType,
		MediaUrl: mediaPath,
		OriginatingChannel: "wati",
		OriginatingTo: `wati:${account.accountId}`
	});
	core.channel.session.recordSessionMetaFromInbound({
		storePath,
		sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
		ctx: ctxPayload
	}).catch((err) => {
		runtime.error?.(`wati: failed updating session meta: ${String(err)}`);
	});
	const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
		cfg: config,
		agentId: route.agentId,
		channel: "wati",
		accountId: route.accountId
	});
	await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
		ctx: ctxPayload,
		cfg: config,
		dispatcherOptions: {
			...prefixOptions,
			deliver: async (payload) => {
				await deliverWatiReply({
					payload,
					account,
					senderId,
					runtime,
					core,
					config,
					statusSink
				});
			},
			onError: (err, info) => {
				runtime.error?.(`[${account.accountId}] WATI ${info.kind} reply failed: ${String(err)}`);
			}
		},
		replyOptions: { onModelSelected }
	});
}
async function deliverWatiReply(params) {
	const { payload, account, senderId, runtime, core, config, statusSink } = params;
	const mediaList = payload.mediaUrls?.length ? payload.mediaUrls : payload.mediaUrl ? [payload.mediaUrl] : [];
	if (mediaList.length > 0) {
		const { sendWatiMedia } = await Promise.resolve().then(() => (init_api(), api_exports));
		for (const mediaUrl of mediaList) try {
			await sendWatiMedia({
				account,
				to: senderId,
				mediaUrl,
				caption: payload.text
			});
			statusSink?.({ lastOutboundAt: Date.now() });
		} catch (err) {
			runtime.error?.(`WATI media send failed: ${String(err)}`);
		}
		return;
	}
	if (payload.text) {
		const chunkLimit = account.config.textChunkLimit ?? 4e3;
		const chunkMode = core.channel.text.resolveChunkMode(config, "wati", account.accountId);
		const chunks = core.channel.text.chunkMarkdownTextWithMode(payload.text, chunkLimit, chunkMode);
		for (const chunk of chunks) try {
			await sendWatiMessage({
				account,
				to: senderId,
				text: chunk
			});
			statusSink?.({ lastOutboundAt: Date.now() });
		} catch (err) {
			runtime.error?.(`WATI message send failed: ${String(err)}`);
		}
	}
}
function monitorWatiProvider(options) {
	const core = getWatiRuntime();
	const webhookPath = resolveWebhookPath({
		webhookPath: options.webhookPath,
		webhookUrl: options.webhookUrl,
		defaultPath: "/wati"
	});
	if (!webhookPath) {
		options.runtime.error?.(`[${options.account.accountId}] invalid webhook path`);
		return () => {};
	}
	return registerWatiWebhookTarget({
		account: options.account,
		config: options.config,
		runtime: options.runtime,
		core,
		path: webhookPath,
		statusSink: options.statusSink
	});
}
async function startWatiMonitor(params) {
	return monitorWatiProvider(params);
}
function resolveWatiWebhookPath(params) {
	return resolveWebhookPath({
		webhookPath: params.account.config.webhookPath,
		webhookUrl: params.account.config.webhookUrl,
		defaultPath: "/wati"
	}) ?? "/wati";
}

//#endregion
//#region extensions/wati/src/channel.ts
init_api();
init_targets();
const watiPlugin = {
	id: "wati",
	meta: {
		id: "wati",
		label: "WATI WhatsApp",
		selectionLabel: "WATI (WhatsApp Business API)",
		detailLabel: "WATI WhatsApp Business",
		docsPath: "/channels/wati",
		docsLabel: "wati",
		blurb: "WhatsApp Business API via WATI — webhook-based, popular in LATAM & SEA.",
		systemImage: "message",
		aliases: ["wati-whatsapp", "whatsapp-business"]
	},
	pairing: {
		idLabel: "watiSenderId",
		normalizeAllowEntry: (entry) => normalizeE164(entry),
		notifyApproval: async ({ cfg, id }) => {
			const account = resolveWatiAccount({ cfg });
			if (!account.apiUrl || !account.apiToken) return;
			await sendWatiMessage({
				account,
				to: normalizeWatiTarget(id) ?? id,
				text: "Your pairing request has been approved. You can now send messages."
			});
		}
	},
	capabilities: {
		chatTypes: ["direct"],
		media: true,
		blockStreaming: true
	},
	streaming: { blockStreamingCoalesceDefaults: {
		minChars: 1500,
		idleMs: 1e3
	} },
	reload: { configPrefixes: ["channels.wati"] },
	config: {
		listAccountIds: (cfg) => listWatiAccountIds(cfg),
		resolveAccount: (cfg, accountId) => resolveWatiAccount({
			cfg,
			accountId
		}),
		defaultAccountId: (cfg) => resolveDefaultWatiAccountId(cfg),
		setAccountEnabled: ({ cfg, accountId, enabled }) => {
			const accountKey = accountId || DEFAULT_ACCOUNT_ID;
			const wati = cfg.channels?.wati ?? {};
			const accounts = wati.accounts ?? {};
			const existing = accounts[accountKey] ?? {};
			return {
				...cfg,
				channels: {
					...cfg.channels,
					wati: {
						...wati,
						accounts: {
							...accounts,
							[accountKey]: {
								...existing,
								enabled
							}
						}
					}
				}
			};
		},
		deleteAccount: ({ cfg, accountId }) => {
			const accountKey = accountId || DEFAULT_ACCOUNT_ID;
			const wati = cfg.channels?.wati ?? {};
			const accounts = { ...wati.accounts ?? {} };
			delete accounts[accountKey];
			return {
				...cfg,
				channels: {
					...cfg.channels,
					wati: {
						...wati,
						accounts: Object.keys(accounts).length ? accounts : void 0
					}
				}
			};
		},
		isConfigured: (account) => Boolean(account.apiUrl?.trim() && account.apiToken?.trim()),
		describeAccount: (account) => ({
			accountId: account.accountId,
			name: account.name,
			enabled: account.enabled,
			configured: Boolean(account.apiUrl && account.apiToken),
			apiUrl: account.apiUrl ? "configured" : "missing"
		}),
		resolveAllowFrom: ({ cfg, accountId }) => {
			const account = resolveWatiAccount({
				cfg,
				accountId
			});
			return (account.config.dm?.allowFrom ?? account.config.allowFrom ?? []).map((v) => String(v));
		},
		formatAllowFrom: ({ allowFrom }) => allowFrom.map((entry) => String(entry).trim()).filter(Boolean).map((entry) => entry === "*" ? entry : normalizeE164(entry)).filter(Boolean)
	},
	security: {
		resolveDmPolicy: ({ cfg, accountId, account }) => {
			const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
			const watiSection = cfg.channels?.wati;
			const allowFromPath = Boolean(watiSection?.accounts?.[resolvedAccountId]) ? `channels.wati.accounts.${resolvedAccountId}.dm.` : "channels.wati.dm.";
			return {
				policy: account.config.dm?.policy ?? "pairing",
				allowFrom: account.config.dm?.allowFrom ?? account.config.allowFrom ?? [],
				allowFromPath,
				approveHint: formatPairingApproveHint("wati"),
				normalizeEntry: (raw) => normalizeE164(raw)
			};
		},
		collectWarnings: ({ account }) => {
			const warnings = [];
			if (account.config.dm?.policy === "open") warnings.push(`- WATI DMs are open to anyone. Set channels.wati.dm.policy="pairing" or "allowlist".`);
			if (!account.apiUrl || !account.apiToken) warnings.push(`- WATI API credentials not configured. Set channels.wati.apiUrl and channels.wati.apiToken.`);
			return warnings;
		}
	},
	messaging: {
		normalizeTarget: (raw) => normalizeWatiTarget(raw) ?? void 0,
		targetResolver: {
			looksLikeId: (raw) => looksLikeWatiTarget(raw),
			hint: "<E.164 phone number>"
		}
	},
	directory: {
		self: async () => null,
		listPeers: async ({ cfg, accountId, query, limit }) => {
			const account = resolveWatiAccount({
				cfg,
				accountId
			});
			const q = query?.trim().toLowerCase() || "";
			const allowFrom = account.config.dm?.allowFrom ?? account.config.allowFrom ?? [];
			return Array.from(new Set(allowFrom.map((entry) => String(entry).trim()).filter((entry) => Boolean(entry) && entry !== "*").map((entry) => normalizeE164(entry)).filter(Boolean))).filter((id) => q ? id.toLowerCase().includes(q) : true).slice(0, limit && limit > 0 ? limit : void 0).map((id) => ({
				kind: "user",
				id
			}));
		}
	},
	outbound: {
		deliveryMode: "direct",
		chunker: (text, limit) => getWatiRuntime().channel.text.chunkMarkdownText(text, limit),
		chunkerMode: "markdown",
		textChunkLimit: 4e3,
		resolveTarget: ({ to, allowFrom, mode }) => {
			const trimmed = to?.trim() ?? "";
			if (!trimmed) return {
				ok: false,
				error: missingTargetError("WATI", "<E.164 phone number>")
			};
			const normalized = normalizeWatiTarget(trimmed);
			if (!normalized) return {
				ok: false,
				error: missingTargetError("WATI", "<E.164 phone number>")
			};
			if (mode === "implicit" && allowFrom) {
				if (!isPhoneAllowed(normalized, allowFrom)) return {
					ok: false,
					error: /* @__PURE__ */ new Error(`target not allowlisted: ${normalized}`)
				};
			}
			return {
				ok: true,
				to: normalized
			};
		},
		sendText: async ({ cfg, to, text, accountId }) => {
			return {
				channel: "wati",
				messageId: "",
				chatId: to,
				ok: (await sendWatiMessage({
					account: resolveWatiAccount({
						cfg,
						accountId
					}),
					to,
					text
				})).ok
			};
		},
		sendMedia: async ({ cfg, to, text, mediaUrl, accountId }) => {
			if (!mediaUrl) throw new Error("WATI mediaUrl is required.");
			const account = resolveWatiAccount({
				cfg,
				accountId
			});
			const { sendWatiMedia } = await Promise.resolve().then(() => (init_api(), api_exports));
			return {
				channel: "wati",
				messageId: "",
				chatId: to,
				ok: (await sendWatiMedia({
					account,
					to,
					mediaUrl,
					caption: text
				})).ok
			};
		}
	},
	status: {
		defaultRuntime: {
			accountId: DEFAULT_ACCOUNT_ID,
			running: false,
			lastStartAt: null,
			lastStopAt: null,
			lastError: null
		},
		collectStatusIssues: (accounts) => accounts.flatMap((entry) => {
			const accountId = String(entry.accountId ?? DEFAULT_ACCOUNT_ID);
			const enabled = entry.enabled !== false;
			const configured = entry.configured === true;
			if (!enabled || !configured) return [];
			const issues = [];
			const snapshot = entry;
			if (!snapshot.apiUrl || snapshot.apiUrl === "missing") issues.push({
				channel: "wati",
				accountId,
				kind: "config",
				message: "WATI API URL is missing (set channels.wati.apiUrl or WATI_API_URL env).",
				fix: "Set channels.wati.apiUrl to your WATI instance URL."
			});
			return issues;
		}),
		buildChannelSummary: ({ snapshot }) => ({
			configured: snapshot.configured ?? false,
			webhookPath: snapshot.webhookPath ?? null,
			running: snapshot.running ?? false,
			lastStartAt: snapshot.lastStartAt ?? null,
			lastStopAt: snapshot.lastStopAt ?? null,
			lastError: snapshot.lastError ?? null,
			probe: snapshot.probe,
			lastProbeAt: snapshot.lastProbeAt ?? null
		}),
		probeAccount: async ({ account }) => probeWati(account),
		buildAccountSnapshot: ({ account, runtime, probe }) => ({
			accountId: account.accountId,
			name: account.name,
			enabled: account.enabled,
			configured: Boolean(account.apiUrl && account.apiToken),
			apiUrl: account.apiUrl ? "configured" : "missing",
			webhookPath: account.config.webhookPath,
			running: runtime?.running ?? false,
			lastStartAt: runtime?.lastStartAt ?? null,
			lastStopAt: runtime?.lastStopAt ?? null,
			lastError: runtime?.lastError ?? null,
			lastInboundAt: runtime?.lastInboundAt ?? null,
			lastOutboundAt: runtime?.lastOutboundAt ?? null,
			dmPolicy: account.config.dm?.policy ?? "pairing",
			probe
		})
	},
	gateway: { startAccount: async (ctx) => {
		const account = ctx.account;
		ctx.log?.info(`[${account.accountId}] starting WATI webhook listener`);
		ctx.setStatus({
			accountId: account.accountId,
			running: true,
			lastStartAt: Date.now(),
			webhookPath: resolveWatiWebhookPath({ account })
		});
		const unregister = await startWatiMonitor({
			account,
			config: ctx.cfg,
			runtime: ctx.runtime,
			abortSignal: ctx.abortSignal,
			webhookPath: account.config.webhookPath,
			webhookUrl: account.config.webhookUrl,
			statusSink: (patch) => ctx.setStatus({
				accountId: account.accountId,
				...patch
			})
		});
		return () => {
			unregister?.();
			ctx.setStatus({
				accountId: account.accountId,
				running: false,
				lastStopAt: Date.now()
			});
		};
	} }
};

//#endregion
//#region extensions/wati/index.ts
const plugin = {
	id: "wati",
	name: "WATI WhatsApp",
	description: "WATI WhatsApp Business channel plugin",
	configSchema: emptyPluginConfigSchema(),
	register(api) {
		setWatiRuntime(api.runtime);
		api.registerChannel({ plugin: watiPlugin });
		api.registerHttpHandler(handleWatiWebhookRequest);
	}
};

//#endregion
export { plugin as default };