//#region src/compliance.ts
var ComplianceError = class extends Error {
	code;
	name = "ComplianceError";
	constructor(message, code) {
		super(message);
		this.code = code;
	}
};
const IL_PROMOTIONAL_LABEL = "פרסומת";
const OPT_IN_REGIONS = /* @__PURE__ */ new Set(["EU", "IL"]);
function normalizeEmail(email) {
	return email.trim().toLowerCase();
}
function requiresDoubleOptIn(region) {
	if (!region) return true;
	return OPT_IN_REGIONS.has(region);
}
/** Default-on IL/EU — records confirmed double-opt-in consent. */
async function confirmDoubleOptIn(store, email, region, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
	const normalized = normalizeEmail(email);
	const existing = await store.getConsent(normalized) ?? {
		email: normalized,
		doubleOptInConfirmed: false,
		trackingConsent: false
	};
	const record = {
		...existing,
		email: normalized,
		doubleOptInConfirmed: true,
		confirmedAt: existing.confirmedAt ?? now(),
		region: region ?? existing.region
	};
	await store.setConsent(record);
	return record;
}
async function recordTrackingConsent(store, email, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
	const normalized = normalizeEmail(email);
	const existing = await store.getConsent(normalized) ?? {
		email: normalized,
		doubleOptInConfirmed: false,
		trackingConsent: false
	};
	const record = {
		...existing,
		email: normalized,
		trackingConsent: true,
		trackingConsentAt: existing.trackingConsentAt ?? now()
	};
	await store.setConsent(record);
	return record;
}
/** Pre-send legal gate — blocks campaigns missing required sender declarations. */
function validatePreSendLegalGate(ctx) {
	const { legal, region } = ctx;
	if (!legal.spf || !legal.dkim || !legal.dmarc) throw new ComplianceError("campaign blocked: SPF, DKIM, and DMARC must all be declared on the sending domain", "missing-domain-auth");
	if (!legal.physicalPostalAddress?.trim()) throw new ComplianceError("campaign blocked: physical postal address is required", "missing-postal-address");
	if (!legal.advertisingLabel?.trim()) throw new ComplianceError("campaign blocked: advertising label is required", "missing-advertising-label");
	if (region === "IL" && !legal.advertisingLabel.includes(IL_PROMOTIONAL_LABEL)) throw new ComplianceError(`campaign blocked: IL promotional mail requires the "${IL_PROMOTIONAL_LABEL}" advertising label`, "missing-il-promotional-label");
}
/** RFC 8058 one-click unsubscribe headers — injected on every compliant send. */
function buildRfc8058Headers(oneClickUnsubscribeUrl) {
	const url = oneClickUnsubscribeUrl.trim();
	if (!url) throw new ComplianceError("one-click unsubscribe URL is required", "missing-unsubscribe-url");
	return {
		"List-Unsubscribe": `<${url}>`,
		"List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
	};
}
/** Tracking pixel + click redirect emitted ONLY when per-contact tracking consent is recorded. */
function applyTracking(html, opts) {
	if (!opts.hasTrackingConsent) return stripTrackingArtifacts(html);
	const pixel = `<img src="${`${opts.trackingBaseUrl.replace(/\/$/, "")}/o/${encodeURIComponent(opts.campaignId)}/${encodeURIComponent(opts.email)}`}" width="1" height="1" alt="" />`;
	return (html.includes("</body>") ? html.replace("</body>", `${pixel}</body>`) : `${html}${pixel}`).replace(/href="(https?:\/\/[^"]+)"/gi, (_match, url) => `href="${opts.trackingBaseUrl.replace(/\/$/, "")}/c/${encodeURIComponent(opts.campaignId)}/${encodeURIComponent(opts.email)}?u=${encodeURIComponent(url)}"`);
}
function stripTrackingArtifacts(html) {
	return html.replace(/<img[^>]+src="[^"]*\/o\/[^"]*"[^>]*>/gi, "").replace(/href="[^"]*\/c\/[^"]*\?u=([^"]+)"/gi, "href=\"$1\"");
}
function assertRecipientEligible(email, consent, suppressed, region) {
	const normalized = normalizeEmail(email);
	if (suppressed) return {
		email: normalized,
		code: "suppressed",
		reason: "recipient is on the suppression list"
	};
	if (requiresDoubleOptIn(region) && !consent?.doubleOptInConfirmed) return {
		email: normalized,
		code: "unconfirmed-consent",
		reason: "double opt-in is not confirmed for this recipient"
	};
	return null;
}
/**
* Compliance-wrapped per-recipient send — suppression + consent gate, legal pre-check,
* tracking-off-by-default, RFC 8058 header injection.
*/
async function executeCompliantSend(store, input, sendOne) {
	validatePreSendLegalGate(input.legal);
	const sent = [];
	const refused = [];
	const rfc8058 = buildRfc8058Headers(input.legal.oneClickUnsubscribeUrl);
	for (const rawEmail of input.recipients) {
		const email = normalizeEmail(rawEmail);
		const suppressed = await store.isSuppressed(email);
		const consent = await store.getConsent(email);
		const refusal = assertRecipientEligible(email, consent, suppressed, input.legal.region ?? input.campaign.region);
		if (refusal) {
			refused.push(refusal);
			continue;
		}
		const html = applyTracking(input.campaign.html, {
			campaignId: input.campaignId,
			email,
			trackingBaseUrl: input.trackingBaseUrl,
			hasTrackingConsent: consent?.trackingConsent === true
		});
		await sendOne({
			email,
			subject: input.campaign.subject,
			html,
			text: input.campaign.text,
			headers: { ...rfc8058 }
		});
		sent.push(email);
	}
	return {
		sent,
		refused
	};
}
/** In-memory ComplianceStore for tests and self-managed hosts without a wired DB yet. */
function createMemoryComplianceStore(initial) {
	const suppressed = [...initial?.suppressed ?? []];
	const consents = /* @__PURE__ */ new Map();
	for (const record of initial?.consents ?? []) consents.set(normalizeEmail(record.email), {
		...record,
		email: normalizeEmail(record.email)
	});
	return {
		suppressed,
		consents,
		async isSuppressed(email) {
			const normalized = normalizeEmail(email);
			return suppressed.some((entry) => normalizeEmail(entry.email) === normalized);
		},
		async listSuppressed() {
			return [...suppressed];
		},
		async getConsent(email) {
			return consents.get(normalizeEmail(email)) ?? null;
		},
		async setConsent(record) {
			consents.set(normalizeEmail(record.email), {
				...record,
				email: normalizeEmail(record.email)
			});
		},
		async addSuppression(entry) {
			const normalized = normalizeEmail(entry.email);
			if (!suppressed.some((e) => normalizeEmail(e.email) === normalized)) suppressed.push({
				...entry,
				email: normalized
			});
		}
	};
}
//#endregion
//#region src/index.ts
/**
* `@platform-modules/marketing` — newsletter/campaign seam (LCD adapter axis + compliance core).
*/
var MarketingError = class extends Error {};
var UnsupportedOperation = class extends MarketingError {
	capability;
	name = "UnsupportedOperation";
	constructor(capability, message) {
		super(message ?? `adapter does not support capability: ${capability}`);
		this.capability = capability;
	}
};
var MarketingProviderError = class extends MarketingError {
	provider;
	retryable;
	cause;
	name = "MarketingProviderError";
	constructor(message, provider, retryable = false, cause) {
		super(message);
		this.provider = provider;
		this.retryable = retryable;
		this.cause = cause;
	}
};
const adapterFactories = {};
function registerMarketingAdapter(name, factory) {
	adapterFactories[name] = factory;
}
function getAdapter(name, creds, deps) {
	const factory = adapterFactories[name];
	if (!factory) throw new Error(`No marketing adapter factory registered for "${name}". Import @platform-modules/marketing/${name} to register it.`);
	return factory(creds, deps);
}
function requireCapability(adapter, capability) {
	if (!adapter.supports[capability]) throw new UnsupportedOperation(capability);
}
/** Shared compliant send orchestration — adapters call this; never bypass. */
async function sendCampaignThroughCompliance(store, input, sendOne) {
	return executeCompliantSend(store, input, sendOne);
}
//#endregion
export { registerMarketingAdapter as a, ComplianceError as c, buildRfc8058Headers as d, confirmDoubleOptIn as f, validatePreSendLegalGate as g, recordTrackingConsent as h, getAdapter as i, applyTracking as l, executeCompliantSend as m, MarketingProviderError as n, requireCapability as o, createMemoryComplianceStore as p, UnsupportedOperation as r, sendCampaignThroughCompliance as s, MarketingError as t, assertRecipientEligible as u };
