import { resolveMimeType } from "./share-store.js";
import { randomUUID } from "node:crypto";
import { readdirSync } from "node:fs";
import { copyFile, mkdir, rm, stat } from "node:fs/promises";
import { basename, join, resolve } from "node:path";
//#region src/share/share-auto.ts
/**
* Smart-share decision layer.
*
* The mobile app calls POST /api/shares/auto with a path + audience and
* lets the server pick file-share vs site-share, pick sane TTLs, and bundle
* the metadata the share-sheet needs (title, description, thumbnail URL,
* reachability).
*/
/** Convert an audience hint into TTL/maxViews defaults. */
function audienceDefaults(audience) {
	switch (audience) {
		case "public": return {
			ttlMs: 1440 * 6e4,
			maxViews: 100
		};
		case "colleague": return {
			ttlMs: 10080 * 6e4,
			maxViews: null
		};
		default: return {
			ttlMs: 4320 * 6e4,
			maxViews: null
		};
	}
}
/** Run filesystem probes needed by the routing logic. */
async function probeShareTarget(workspaceRoot, relPath) {
	const trimmed = relPath.trim().replace(/\\/g, "/").replace(/^\/+/, "");
	if (!trimmed) throw new Error("Empty path");
	if (trimmed.includes("..") || trimmed.includes("\0")) throw new Error("Invalid path");
	const abs = resolve(workspaceRoot, trimmed);
	const st = await stat(abs);
	if (st.isDirectory()) {
		let hasIndexHtml = false;
		try {
			hasIndexHtml = readdirSync(abs).includes("index.html");
		} catch {}
		return {
			absolutePath: abs,
			kind: "directory",
			size: 0,
			mimeType: "application/x-directory",
			hasIndexHtml
		};
	}
	if (!st.isFile()) throw new Error("Path is not a regular file or directory");
	const fileName = basename(abs);
	return {
		absolutePath: abs,
		kind: "file",
		size: st.size,
		mimeType: resolveMimeType(fileName),
		hasIndexHtml: false
	};
}
/** Pure decision function — exported for tests. */
function decideShareKind(probe, mode) {
	if (mode === "force-file") return forced(probe.kind === "directory" ? "zip" : "file", probe);
	if (mode === "force-site") {
		if (probe.kind !== "directory" && probe.mimeType !== "text/html") throw new Error("force-site requires a directory or a single HTML file");
		return {
			kind: "site",
			isDirectory: probe.kind === "directory",
			reason: "forced",
			hint: "已按指定方式作为站点分享"
		};
	}
	if (mode === "force-zip") {
		if (probe.kind !== "directory") throw new Error("force-zip requires a directory");
		return {
			kind: "zip",
			isDirectory: true,
			reason: "forced",
			hint: "已按指定方式打包为 ZIP 分享"
		};
	}
	if (probe.kind === "directory") {
		if (probe.hasIndexHtml) return {
			kind: "site",
			isDirectory: true,
			reason: "html-with-assets",
			hint: "目录包含 index.html，已通过站点分享渲染"
		};
		return {
			kind: "file",
			isDirectory: true,
			reason: "directory-browse",
			hint: "目录较多文件，通过分享页可浏览或下载 ZIP"
		};
	}
	if (probe.mimeType === "text/html") return {
		kind: "site",
		isDirectory: false,
		reason: "html-single-file",
		hint: "HTML 文件，分享后朋友点开即渲染"
	};
	if (probe.mimeType.startsWith("image/") && probe.size < 10 * 1024 * 1024) return {
		kind: "file",
		isDirectory: false,
		reason: "small-image",
		hint: "图片文件，朋友可直接预览或保存"
	};
	return {
		kind: "file",
		isDirectory: false,
		reason: "large-binary",
		hint: "通过分享链接由朋友点开下载"
	};
}
function forced(kind, probe) {
	return {
		kind,
		isDirectory: probe.kind === "directory",
		reason: "forced",
		hint: "按指定方式分享"
	};
}
/** Build the user-facing title (trimmed, no extension). */
function makeTitle(fileName, override) {
	if (override && override.trim()) return clip(override.trim(), 60);
	const idx = fileName.lastIndexOf(".");
	return clip((idx > 0 ? fileName.slice(0, idx) : fileName) || fileName, 60);
}
/** Build the user-facing description (audience + TTL hint baked in). */
function makeDescription(opts) {
	if (opts.override && opts.override.trim()) return clip(opts.override.trim(), 140);
	const now = Date.now();
	const exp = new Date(opts.expiresAt).getTime();
	const days = Math.max(1, Math.round((exp - now) / (1440 * 6e4)));
	return clip(`由 xopc 生成 · ${opts.audience === "public" ? "公开分享" : opts.audience === "colleague" ? "同事可见" : "好友可见"} · ${days} 天内有效`, 140);
}
function clip(text, max) {
	if (text.length <= max) return text;
	return `${text.slice(0, max - 1)}…`;
}
/** Subfolder under the workspace where single-HTML site shares are staged. */
const STAGING_DIR_NAME = ".xopc-share-staging";
/**
* Copy a single HTML file into `<workspaceRoot>/.xopc-share-staging/<uuid>/index.html`
* so it can be served as a site (SiteShareStore needs a directory root).
*
* The caller must register a cleanup that calls `cleanupStagedSite(stagingDir)`
* when the underlying site share is revoked / expires.
*/
async function stageSingleHtmlAsSite(workspaceRoot, absoluteHtmlPath) {
	const stagingRoot = resolve(workspaceRoot, STAGING_DIR_NAME);
	await mkdir(stagingRoot, { recursive: true });
	const id = randomUUID();
	const stagingDir = join(stagingRoot, id);
	await mkdir(stagingDir, { recursive: true });
	await copyFile(absoluteHtmlPath, join(stagingDir, "index.html"));
	return {
		stagingDir,
		relativePath: `${STAGING_DIR_NAME}/${id}`
	};
}
/** Best-effort cleanup; never throws. */
async function cleanupStagedSite(stagingDir) {
	if (!stagingDir.includes(".xopc-share-staging")) return;
	try {
		await rm(stagingDir, {
			recursive: true,
			force: true
		});
	} catch {}
}
const stagedSiteByRecordId = /* @__PURE__ */ new Map();
function rememberStagedSite(recordId, stagingDir) {
	stagedSiteByRecordId.set(recordId, stagingDir);
}
function forgetStagedSite(recordId) {
	const dir = stagedSiteByRecordId.get(recordId);
	stagedSiteByRecordId.delete(recordId);
	return dir;
}
/**
* Walk `<workspaceRoot>/.xopc-share-staging/` and drop directories that are
* no longer referenced by any live site-share record. Call at gateway boot.
*/
async function sweepOrphanedStagingDirs(workspaceRoot, liveStagingDirs) {
	const stagingRoot = resolve(workspaceRoot, STAGING_DIR_NAME);
	let entries;
	try {
		entries = readdirSync(stagingRoot);
	} catch {
		return;
	}
	for (const name of entries) {
		const abs = join(stagingRoot, name);
		if (liveStagingDirs.has(abs)) continue;
		try {
			await rm(abs, {
				recursive: true,
				force: true
			});
		} catch {}
	}
}
/**
* Boot-time housekeeping:
*  1. Walk active static site shares whose source lives under STAGING_DIR_NAME
*     (i.e. created via `stageSingleHtmlAsSite`).
*  2. Re-register them in the in-process registry so the SiteShareStore
*     cleanup hook still wipes the right directory on revoke/expire after a
*     restart.
*  3. Sweep each workspace's `.xopc-share-staging/` for entries that are not
*     referenced by any live record — these are leftovers from a previous
*     process death between create-staging and persist.
*
* Safe to call multiple times; idempotent. Best invoked from gateway.start().
*/
async function runStagingSweep() {
	const { getSiteShareStore } = await import("./site-share-store.js");
	const store = getSiteShareStore();
	const records = store.getActiveShares();
	const liveByWorkspace = /* @__PURE__ */ new Map();
	for (const rec of records) {
		if (rec.source.kind !== "static") continue;
		const ws = rec.source.workspaceRoot;
		const rootDir = rec.source.rootDir;
		if (!ws || !rootDir) continue;
		if (!rec.source.workspaceRelativePath.startsWith(`.xopc-share-staging/`)) continue;
		rememberStagedSite(rec.id, rootDir);
		const bucket = liveByWorkspace.get(ws) ?? /* @__PURE__ */ new Set();
		bucket.add(rootDir);
		liveByWorkspace.set(ws, bucket);
	}
	for (const rec of store.getAllShares()) {
		if (rec.source.kind !== "static") continue;
		if (!rec.source.workspaceRelativePath.startsWith(`.xopc-share-staging/`)) continue;
		if (!liveByWorkspace.has(rec.source.workspaceRoot)) liveByWorkspace.set(rec.source.workspaceRoot, /* @__PURE__ */ new Set());
	}
	for (const [workspaceRoot, live] of liveByWorkspace) await sweepOrphanedStagingDirs(workspaceRoot, live);
}
function resetStagedSiteRegistryForTests() {
	stagedSiteByRecordId.clear();
}
//#endregion
export { STAGING_DIR_NAME, audienceDefaults, cleanupStagedSite, decideShareKind, forgetStagedSite, makeDescription, makeTitle, probeShareTarget, rememberStagedSite, resetStagedSiteRegistryForTests, runStagingSweep, stageSingleHtmlAsSite, sweepOrphanedStagingDirs };

//# sourceMappingURL=share-auto.js.map