import { registerMarketplaceAdapter } from "../../registry.js";
//#region src/agent/skills/marketplace/adapters/clawhub/adapter.ts
const DEFAULT_CLAWHUB_BASE = "https://clawhub.ai";
const MAX_CLAWHUB_FILE_BYTES = 512 * 1024;
const MAX_SKILL_ZIP_BYTES = 15 * 1024 * 1024;
const SKILL_ID_RE = /^[a-zA-Z0-9]([a-zA-Z0-9._-]{0,62})$/;
const LIST_BATCH_SIZE = 200;
const DEFAULT_CACHE_MS = 300 * 1e3;
const MAX_LIST_CACHE_KEYS = 24;
const MAX_DETAIL_CACHE_KEYS = 48;
function isValidSkillId(id) {
	return SKILL_ID_RE.test(id);
}
function resolveClawHubBaseUrl() {
	const env = process.env.CLAWHUB_REGISTRY?.trim();
	if (env) try {
		return new URL(env).toString().replace(/\/$/, "");
	} catch {}
	return DEFAULT_CLAWHUB_BASE;
}
async function clawHubFetch(path, init) {
	const url = `${resolveClawHubBaseUrl()}${path}`;
	return await fetch(url, {
		headers: { Accept: "application/json" },
		signal: init?.signal
	});
}
function assertOk(res, context) {
	if (!res.ok) throw new Error(`ClawHub ${context} failed (${res.status})`);
}
async function listClawHubSkills(params) {
	const sp = new URLSearchParams();
	if (params?.limit) sp.set("limit", String(params.limit));
	if (params?.cursor) sp.set("cursor", params.cursor);
	if (params?.sort) sp.set("sort", params.sort);
	sp.set("nonSuspiciousOnly", "true");
	const qs = sp.toString();
	const res = await clawHubFetch(`/api/v1/skills${qs ? `?${qs}` : ""}`);
	assertOk(res, "list skills");
	return await res.json();
}
async function searchClawHubSkills(query, limit) {
	const sp = new URLSearchParams({
		q: query,
		nonSuspiciousOnly: "true"
	});
	if (limit) sp.set("limit", String(limit));
	const res = await clawHubFetch(`/api/v1/search?${sp.toString()}`);
	assertOk(res, "search");
	return await res.json();
}
async function getClawHubSkillDetail(slug) {
	const res = await clawHubFetch(`/api/v1/skills/${encodeURIComponent(slug.trim())}`);
	assertOk(res, `skill detail [${slug}]`);
	return await res.json();
}
async function getClawHubVersionDetail(slug, version) {
	const res = await clawHubFetch(`/api/v1/skills/${encodeURIComponent(slug.trim())}/versions/${encodeURIComponent(version.trim())}`);
	assertOk(res, `version detail [${slug}@${version}]`);
	return await res.json();
}
async function getClawHubSkillFileText(slug, filePath, version) {
	const enc = encodeURIComponent(slug.trim());
	const sp = new URLSearchParams({ path: filePath });
	if (version?.trim()) sp.set("version", version.trim());
	const url = `${resolveClawHubBaseUrl()}/api/v1/skills/${enc}/file?${sp.toString()}`;
	const res = await fetch(url, { headers: { Accept: "text/markdown,text/plain,*/*" } });
	if (!res.ok) throw new Error(`ClawHub file fetch [${slug}/${filePath}] failed (${res.status})`);
	const text = await res.text();
	return text.length > MAX_CLAWHUB_FILE_BYTES ? text.slice(0, MAX_CLAWHUB_FILE_BYTES) : text;
}
async function downloadClawHubSkillZip(slug, version) {
	const enc = encodeURIComponent(slug.trim());
	const sp = version?.trim() ? `?version=${encodeURIComponent(version.trim())}` : "";
	const url = `${resolveClawHubBaseUrl()}/api/v1/packages/${enc}/download${sp}`;
	const res = await fetch(url, { redirect: "follow" });
	if (!res.ok) {
		const body = await res.text().catch(() => "");
		throw new Error(`ClawHub download [${slug}] failed (${res.status}): ${body.slice(0, 200)}`);
	}
	const contentType = res.headers.get("content-type") ?? "";
	if (!contentType.includes("zip") && !contentType.includes("octet-stream")) throw new Error(`ClawHub download [${slug}] unexpected content-type: ${contentType}`);
	const arrayBuf = await res.arrayBuffer();
	if (arrayBuf.byteLength > MAX_SKILL_ZIP_BYTES) throw new Error(`ClawHub download [${slug}] exceeds size limit (${arrayBuf.byteLength} > ${MAX_SKILL_ZIP_BYTES})`);
	return {
		buffer: Buffer.from(arrayBuf),
		version: version?.trim() || "latest"
	};
}
function pickClawHubDocFilePath(files) {
	const rows = files.map((f) => ({
		path: f.path.replace(/\\/g, "/"),
		base: f.path.replace(/\\/g, "/").split("/").pop()?.toLowerCase() ?? ""
	}));
	const find = (name) => rows.find((r) => r.base === name.toLowerCase());
	const skillMd = find("SKILL.md") ?? find("skill.md");
	if (skillMd) return skillMd.path;
	const readme = find("README.md") ?? find("readme.md");
	if (readme) return readme.path;
	return null;
}
function cacheTtlMs() {
	const raw = process.env.XOPC_CLAWHUB_CACHE_MS?.trim();
	if (raw === "0" || raw === "false") return 0;
	const parsed = Number(raw);
	if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_CACHE_MS;
	return parsed;
}
function getFresh(entry) {
	if (!entry || entry.expiresAt <= Date.now()) return void 0;
	return entry.value;
}
function evictOldest(map) {
	const first = map.keys().next().value;
	if (first !== void 0) map.delete(first);
}
const listCache = /* @__PURE__ */ new Map();
const searchCache = /* @__PURE__ */ new Map();
const detailCache = /* @__PURE__ */ new Map();
async function cachedListClawHubSkills(params) {
	const ttl = cacheTtlMs();
	const key = JSON.stringify(params ?? {});
	if (ttl > 0) {
		const hit = getFresh(listCache.get(key));
		if (hit) return hit;
	}
	const value = await listClawHubSkills(params);
	if (ttl > 0) {
		while (listCache.size >= MAX_LIST_CACHE_KEYS) evictOldest(listCache);
		listCache.set(key, {
			value,
			expiresAt: Date.now() + ttl
		});
	}
	return value;
}
async function cachedSearchClawHubSkills(query, limit) {
	const ttl = cacheTtlMs();
	const key = `${query}::${limit ?? ""}`;
	if (ttl > 0) {
		const hit = getFresh(searchCache.get(key));
		if (hit) return hit;
	}
	const value = await searchClawHubSkills(query, limit);
	if (ttl > 0) {
		while (searchCache.size >= MAX_LIST_CACHE_KEYS) evictOldest(searchCache);
		searchCache.set(key, {
			value,
			expiresAt: Date.now() + ttl
		});
	}
	return value;
}
async function cachedGetClawHubSkillDetail(slug) {
	const ttl = cacheTtlMs();
	const key = slug.trim();
	if (ttl > 0) {
		const hit = getFresh(detailCache.get(key));
		if (hit) return hit;
	}
	const value = await getClawHubSkillDetail(slug);
	if (ttl > 0) {
		while (detailCache.size >= MAX_DETAIL_CACHE_KEYS) evictOldest(detailCache);
		detailCache.set(key, {
			value,
			expiresAt: Date.now() + ttl
		});
	}
	return value;
}
function convertListItemToPackageItem(item) {
	return {
		id: item.slug,
		name: item.displayName || item.slug,
		type: "skill",
		description: item.summary || "",
		downloads: item.stats.downloads,
		author: {
			username: "clawhub",
			avatarUrl: null
		},
		latestVersion: item.latestVersion?.version ?? item.tags.latest ?? "1.0.0",
		updatedAt: String(item.updatedAt),
		categories: item.metadata?.os ?? [],
		stars: item.stats.stars,
		sourceLabel: "ClawHub"
	};
}
function convertSearchResultToPackageItem(item, enrichment) {
	return {
		id: item.slug,
		name: item.displayName || item.slug,
		type: "skill",
		description: item.summary || "",
		downloads: enrichment?.stats.downloads ?? 0,
		author: {
			username: item.owner?.handle ?? item.ownerHandle ?? "clawhub",
			avatarUrl: item.owner?.image ?? null
		},
		latestVersion: item.version ?? enrichment?.latestVersion?.version ?? "1.0.0",
		updatedAt: String(item.updatedAt),
		categories: enrichment?.metadata?.os ?? [],
		stars: enrichment?.stats.stars ?? 0,
		sourceLabel: "ClawHub"
	};
}
function mapSortParam(sort) {
	if (sort === "downloads") return "downloads";
	if (sort === "newest") return "newest";
}
function fallbackReadmeMarkdown(detail) {
	const title = detail.skill.displayName || detail.skill.slug;
	const body = detail.skill.summary || "_No description._";
	return `## ${title}\n\n**${detail.skill.slug}** · v${detail.latestVersion.version}\n\n${body}`;
}
const clawHubMarketplaceAdapter = {
	id: "clawhub",
	async listCategories() {
		return [];
	},
	async listPackages(_config, params) {
		const pageSize = params.pageSize ?? 20;
		const page = params.page ?? 1;
		if (params.q?.trim()) {
			const [searchResponse, listSettled] = await Promise.allSettled([cachedSearchClawHubSkills(params.q.trim(), LIST_BATCH_SIZE), cachedListClawHubSkills({ limit: LIST_BATCH_SIZE })]);
			if (searchResponse.status === "rejected") throw searchResponse.reason;
			const enrichmentBySlug = /* @__PURE__ */ new Map();
			if (listSettled.status === "fulfilled") for (const it of listSettled.value.items) enrichmentBySlug.set(it.slug, it);
			let rows = searchResponse.value.results.map((r) => convertSearchResultToPackageItem(r, enrichmentBySlug.get(r.slug)));
			if (params.sort === "downloads") rows = [...rows].sort((a, b) => b.downloads - a.downloads);
			else if (params.sort === "newest") rows = [...rows].sort((a, b) => Number(b.updatedAt) - Number(a.updatedAt));
			const total = rows.length;
			const start = (page - 1) * pageSize;
			return {
				items: rows.slice(start, start + pageSize),
				meta: {
					page,
					pageSize,
					total,
					totalPages: Math.max(1, Math.ceil(total / pageSize))
				},
				provider: "clawhub"
			};
		}
		const rows = (await cachedListClawHubSkills({
			limit: LIST_BATCH_SIZE,
			sort: mapSortParam(params.sort)
		})).items.map(convertListItemToPackageItem);
		const total = rows.length;
		const start = (page - 1) * pageSize;
		return {
			items: rows.slice(start, start + pageSize),
			meta: {
				page,
				pageSize,
				total,
				totalPages: Math.max(1, Math.ceil(total / pageSize))
			},
			provider: "clawhub"
		};
	},
	async getPackageDetail(_config, packageName) {
		const detail = await cachedGetClawHubSkillDetail(packageName);
		const slug = detail.skill.slug;
		const version = detail.latestVersion.version;
		let readme = null;
		try {
			const docPath = pickClawHubDocFilePath((await getClawHubVersionDetail(slug, version)).version.files);
			if (docPath) {
				const trimmed = (await getClawHubSkillFileText(slug, docPath, version)).trim();
				if (trimmed) readme = trimmed;
			}
		} catch {}
		if (!readme) readme = fallbackReadmeMarkdown(detail);
		const changelog = detail.latestVersion.changelog?.trim();
		if (changelog) readme = `${readme}\n\n---\n\n## Changelog\n\n${changelog}`;
		return {
			id: slug,
			name: slug,
			type: "skill",
			description: detail.skill.summary || "",
			readme,
			downloads: detail.skill.stats.downloads,
			author: {
				username: detail.owner.handle,
				avatarUrl: detail.owner.image
			},
			latestVersion: {
				version: detail.latestVersion.version,
				changelog: detail.latestVersion.changelog,
				publishedAt: String(detail.latestVersion.createdAt)
			},
			provider: "clawhub"
		};
	},
	async downloadPackage(_config, packageName, version) {
		const slug = packageName.trim();
		const result = await downloadClawHubSkillZip(slug, version);
		return {
			buffer: result.buffer,
			skillId: isValidSkillId(slug) ? slug : "unknown",
			version: result.version
		};
	}
};
registerMarketplaceAdapter({
	adapter: clawHubMarketplaceAdapter,
	displayName: "ClawHub"
});
//#endregion
export { clawHubMarketplaceAdapter };

//# sourceMappingURL=adapter.js.map