//#region src/client/portable-text.ts
/**
* Convert Portable Text blocks to Markdown.
* Unknown block types are serialized as opaque fences.
*/
function portableTextToMarkdown(blocks) {
	const lines = [];
	let prevWasList = false;
	for (let i = 0; i < blocks.length; i++) {
		const block = blocks[i];
		if (block._type === "block") {
			const isList = !!block.listItem;
			if (i > 0 && (!isList || !prevWasList)) lines.push("");
			lines.push(renderStandardBlock(block));
			prevWasList = isList;
		} else if (block._type === "code") {
			if (i > 0) lines.push("");
			const lang = block.language || "";
			const code = block.code || "";
			lines.push("```" + lang);
			lines.push(code);
			lines.push("```");
			prevWasList = false;
		} else if (block._type === "image") {
			if (i > 0) lines.push("");
			const alt = block.alt || "";
			const url = block.asset?.url || "";
			lines.push(`![${alt}](${url})`);
			prevWasList = false;
		} else {
			if (i > 0) lines.push("");
			lines.push(`<!--ec:block ${JSON.stringify(block)} -->`);
			prevWasList = false;
		}
	}
	return lines.join("\n") + "\n";
}
function renderStandardBlock(block) {
	const text = renderSpans(block.children ?? [], block.markDefs ?? []);
	if (block.listItem) return `${"  ".repeat(Math.max(0, (block.level ?? 1) - 1))}${block.listItem === "number" ? "1." : "-"} ${text}`;
	if (block.style && block.style.startsWith("h")) {
		const level = parseInt(block.style.substring(1), 10);
		if (level >= 1 && level <= 6) return `${"#".repeat(level)} ${text}`;
	}
	if (block.style === "blockquote") return `> ${text}`;
	return text;
}
function renderSpans(spans, markDefs) {
	let result = "";
	for (const span of spans) {
		if (span._type !== "span") continue;
		let text = span.text ?? "";
		const marks = span.marks ?? [];
		for (const mark of marks) {
			const def = markDefs.find((d) => d._key === mark);
			if (def) {
				if (def._type === "link") text = `[${text}](${def.href ?? ""})`;
			} else switch (mark) {
				case "strong":
					text = `**${text}**`;
					break;
				case "em":
					text = `_${text}_`;
					break;
				case "code":
					text = `\`${text}\``;
					break;
				case "strike-through":
				case "strikethrough":
					text = `~~${text}~~`;
					break;
			}
		}
		result += text;
	}
	return result;
}
const OPAQUE_FENCE_PATTERN = /^<!--ec:block (.+) -->$/;
const HEADING_PATTERN = /^(#{1,6})\s+(.+)$/;
const UNORDERED_LIST_PATTERN = /^(\s*)[-*+]\s+(.+)$/;
const ORDERED_LIST_PATTERN = /^(\s*)\d+\.\s+(.+)$/;
const IMAGE_PATTERN = /^!\[([^\]]*)\]\(([^)]+)\)$/;
const INLINE_MARKDOWN_PATTERN = /(\*\*(.+?)\*\*)|(_(.+?)_)|(`(.+?)`)|(\[(.+?)\]\((.+?)\))|(~~(.+?)~~)/g;
/**
* Convert Markdown to Portable Text blocks.
* Opaque fences (<!--ec:block ... -->) are deserialized and spliced back in.
*/
function markdownToPortableText(markdown) {
	const blocks = [];
	const lines = markdown.split("\n");
	let i = 0;
	while (i < lines.length) {
		const line = lines[i];
		const opaqueMatch = line.match(OPAQUE_FENCE_PATTERN);
		if (opaqueMatch) {
			try {
				blocks.push(JSON.parse(opaqueMatch[1]));
			} catch {
				blocks.push(makeBlock(line));
			}
			i++;
			continue;
		}
		if (line.startsWith("```")) {
			const lang = line.slice(3).trim();
			const codeLines = [];
			i++;
			while (i < lines.length && !lines[i].startsWith("```")) {
				codeLines.push(lines[i]);
				i++;
			}
			blocks.push({
				_type: "code",
				_key: generateKey(),
				language: lang || void 0,
				code: codeLines.join("\n")
			});
			i++;
			continue;
		}
		if (line.trim() === "") {
			i++;
			continue;
		}
		const headingMatch = line.match(HEADING_PATTERN);
		if (headingMatch) {
			blocks.push(makeBlock(headingMatch[2], `h${headingMatch[1].length}`));
			i++;
			continue;
		}
		if (line.startsWith("> ")) {
			blocks.push(makeBlock(line.slice(2), "blockquote"));
			i++;
			continue;
		}
		const ulMatch = line.match(UNORDERED_LIST_PATTERN);
		if (ulMatch) {
			const level = Math.floor(ulMatch[1].length / 2) + 1;
			blocks.push(makeListBlock(ulMatch[2], "bullet", level));
			i++;
			continue;
		}
		const olMatch = line.match(ORDERED_LIST_PATTERN);
		if (olMatch) {
			const level = Math.floor(olMatch[1].length / 2) + 1;
			blocks.push(makeListBlock(olMatch[2], "number", level));
			i++;
			continue;
		}
		const imgMatch = line.match(IMAGE_PATTERN);
		if (imgMatch) {
			blocks.push({
				_type: "image",
				_key: generateKey(),
				alt: imgMatch[1],
				asset: { url: imgMatch[2] }
			});
			i++;
			continue;
		}
		blocks.push(makeBlock(line));
		i++;
	}
	return blocks;
}
function makeBlock(text, style = "normal") {
	const { spans, markDefs } = parseInline(text);
	return {
		_type: "block",
		_key: generateKey(),
		style,
		markDefs,
		children: spans
	};
}
function makeListBlock(text, listItem, level) {
	const { spans, markDefs } = parseInline(text);
	return {
		_type: "block",
		_key: generateKey(),
		style: "normal",
		listItem,
		level,
		markDefs,
		children: spans
	};
}
/**
* Parse inline markdown (bold, italic, code, links, strikethrough) into PT spans + markDefs.
*/
function parseInline(text) {
	const spans = [];
	const markDefs = [];
	const regex = INLINE_MARKDOWN_PATTERN;
	let lastIndex = 0;
	let match;
	while ((match = regex.exec(text)) !== null) {
		if (match.index > lastIndex) spans.push({
			_type: "span",
			_key: generateKey(),
			text: text.slice(lastIndex, match.index),
			marks: []
		});
		if (match[2] != null) spans.push({
			_type: "span",
			_key: generateKey(),
			text: match[2],
			marks: ["strong"]
		});
		else if (match[4] != null) spans.push({
			_type: "span",
			_key: generateKey(),
			text: match[4],
			marks: ["em"]
		});
		else if (match[6] != null) spans.push({
			_type: "span",
			_key: generateKey(),
			text: match[6],
			marks: ["code"]
		});
		else if (match[8] != null && match[9] != null) {
			const key = generateKey();
			markDefs.push({
				_key: key,
				_type: "link",
				href: match[9]
			});
			spans.push({
				_type: "span",
				_key: generateKey(),
				text: match[8],
				marks: [key]
			});
		} else if (match[11] != null) spans.push({
			_type: "span",
			_key: generateKey(),
			text: match[11],
			marks: ["strike-through"]
		});
		lastIndex = match.index + match[0].length;
	}
	if (lastIndex < text.length) spans.push({
		_type: "span",
		_key: generateKey(),
		text: text.slice(lastIndex),
		marks: []
	});
	if (spans.length === 0) spans.push({
		_type: "span",
		_key: generateKey(),
		text,
		marks: []
	});
	return {
		spans,
		markDefs
	};
}
let keyCounter = 0;
function generateKey() {
	return `k${(keyCounter++).toString(36)}`;
}
/**
* Convert content data for reading: PT fields -> markdown strings.
* Only converts fields with type "portableText" that contain arrays.
*/
function convertDataForRead(data, fields, raw = false) {
	if (raw) return data;
	const result = { ...data };
	for (const field of fields) if (field.type === "portableText" && Array.isArray(result[field.slug])) result[field.slug] = portableTextToMarkdown(result[field.slug]);
	return result;
}
/**
* Convert content data for writing: markdown strings -> PT arrays.
* Only converts fields with type "portableText" that contain strings.
*/
function convertDataForWrite(data, fields) {
	const result = { ...data };
	for (const field of fields) if (field.type === "portableText" && typeof result[field.slug] === "string") result[field.slug] = markdownToPortableText(result[field.slug]);
	return result;
}

//#endregion
//#region src/client/transport.ts
/**
* Transport layer for the EmDash client.
*
* Implements a composable interceptor pipeline that modifies requests
* and responses. The client calls `transport.fetch(request)` — everything
* else (auth, CSRF, retry) is handled by interceptors.
*/
const COOKIE_NAME_VALUE_PATTERN = /^([^;]+)/;
function baseFetch(request) {
	return globalThis.fetch(request);
}
/**
* Creates a fetch function that runs requests through an interceptor pipeline.
*/
function createTransport(options = {}) {
	const interceptors = options.interceptors ?? [];
	let chain = baseFetch;
	for (let i = interceptors.length - 1; i >= 0; i--) {
		const interceptor = interceptors[i];
		const next = chain;
		chain = (req) => interceptor(req, next);
	}
	return { fetch: chain };
}
/**
* Adds X-EmDash-Request: 1 and Origin headers to mutation requests
* (POST, PUT, DELETE). The custom header satisfies EmDash's CSRF check;
* the Origin header satisfies Astro's built-in origin verification which
* rejects server-side POST requests that lack a matching Origin.
*/
function csrfInterceptor() {
	const MUTATION_METHODS = new Set([
		"POST",
		"PUT",
		"DELETE",
		"PATCH"
	]);
	return (request, next) => {
		if (MUTATION_METHODS.has(request.method)) {
			const headers = new Headers(request.headers);
			headers.set("X-EmDash-Request", "1");
			if (!headers.has("Origin")) {
				const url = new URL(request.url);
				headers.set("Origin", url.origin);
			}
			return next(new Request(request, { headers }));
		}
		return next(request);
	};
}
/**
* Adds Authorization: Bearer header from a static token.
*/
function tokenInterceptor(token) {
	return (request, next) => {
		const headers = new Headers(request.headers);
		headers.set("Authorization", `Bearer ${token}`);
		return next(new Request(request, { headers }));
	};
}
/**
* Dev bypass interceptor. Calls the dev-bypass endpoint on first request
* to establish a session, then forwards the session cookie on subsequent
* requests.
*/
function devBypassInterceptor(baseUrl) {
	let sessionCookie = null;
	let initializing = null;
	async function init() {
		const bypassUrl = new URL("/_emdash/api/auth/dev-bypass", baseUrl);
		const res = await globalThis.fetch(bypassUrl, { redirect: "manual" });
		const setCookie = res.headers.get("set-cookie");
		if (setCookie) {
			const match = setCookie.match(COOKIE_NAME_VALUE_PATTERN);
			if (match) sessionCookie = match[1];
		}
		if (res.body) await res.text().catch(() => {});
	}
	return async (request, next) => {
		if (!sessionCookie) {
			if (!initializing) initializing = init();
			await initializing;
		}
		if (sessionCookie) {
			const headers = new Headers(request.headers);
			const existing = headers.get("cookie");
			headers.set("cookie", existing ? `${existing}; ${sessionCookie}` : sessionCookie);
			return next(new Request(request, { headers }));
		}
		return next(request);
	};
}
/**
* Auto-refreshes expired OAuth tokens on 401 responses.
* Requires a refresh token and the token endpoint URL.
*/
function refreshInterceptor(options) {
	let refreshing = null;
	async function refresh() {
		const res = await globalThis.fetch(options.tokenEndpoint, {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify({
				grant_type: "refresh_token",
				refresh_token: options.refreshToken
			})
		});
		if (!res.ok) return null;
		const data = await res.json();
		const expiresAt = data.expires_in ? new Date(Date.now() + data.expires_in * 1e3).toISOString() : new Date(Date.now() + 36e5).toISOString();
		if (options.onTokenRefreshed) options.onTokenRefreshed(data.access_token, data.refresh_token ?? options.refreshToken, expiresAt);
		return data.access_token;
	}
	return async (request, next) => {
		const response = await next(request);
		if (response.status === 401) {
			if (!refreshing) refreshing = refresh().finally(() => {
				refreshing = null;
			});
			const newToken = await refreshing;
			if (newToken) {
				const headers = new Headers(request.headers);
				headers.set("Authorization", `Bearer ${newToken}`);
				return next(new Request(request, { headers }));
			}
		}
		return response;
	};
}

//#endregion
export { tokenInterceptor as a, markdownToPortableText as c, refreshInterceptor as i, portableTextToMarkdown as l, csrfInterceptor as n, convertDataForRead as o, devBypassInterceptor as r, convertDataForWrite as s, createTransport as t };
//# sourceMappingURL=transport-BtcQ-Z7T.mjs.map