import { n as __exportAll } from "./chunk-BEJ448es.mjs";
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
//#region extensions/crypto/src/services/plan-scheduler.ts
/**
* Plan Scheduler — persistent job store with timer loop and condition polling.
*
* Responsibilities:
* 1. Persist plans to disk (Fly volume) so they survive restarts.
* 2. Run a tick loop that checks time-based and condition-based triggers.
* 3. When a trigger fires, emit an event — the executor (LLM) handles execution.
* 4. Track execution state per plan.
*
* The scheduler NEVER executes tools directly. It checks conditions and fires events.
* This keeps money-touching logic in the executor where the LLM can reason about it.
*
* ─── Persistence Format ─────────────────────────────────────────────────
*
* Plans are stored as individual JSON files in the state directory:
*   /workspace/.openclaw-state/plans/{planId}.json
*   /workspace/.openclaw-state/plans/executions/{planId}/{executionId}.json
*
* On startup, the scheduler reads all plan files and rebuilds its in-memory state.
*/
var plan_scheduler_exports = /* @__PURE__ */ __exportAll({
	FilePlanStore: () => FilePlanStore,
	NULL_RESOLVER: () => NULL_RESOLVER,
	PlanScheduler: () => PlanScheduler,
	getScheduler: () => getScheduler,
	matchesCron: () => matchesCron
});
/** Default no-op resolver for testing. */
const NULL_RESOLVER = {
	price: async () => 0,
	balance: async () => 0,
	gasPrice: async () => 0,
	timestamp: () => Math.floor(Date.now() / 1e3),
	blockNumber: async () => 0
};
function getPlansDir() {
	return process.env.OPENCLAWNCH_TX_DIR ? join(process.env.OPENCLAWNCH_TX_DIR, "..", "plans") : join(process.env.HOME ?? "/tmp", ".openclawnch", "plans");
}
function getExecutionsDir(planId) {
	return join(getPlansDir(), "executions", sanitizeId(planId));
}
function sanitizeId(id) {
	return id.replace(/[^a-zA-Z0-9_\-]/g, "_").slice(0, 64);
}
var FilePlanStore = class {
	dir;
	constructor(dir) {
		this.dir = dir ?? getPlansDir();
		this.ensureDir(this.dir);
	}
	save(plan) {
		this.ensureDir(this.dir);
		writeFileSync(join(this.dir, `${sanitizeId(plan.id)}.json`), JSON.stringify(plan, null, 2), "utf8");
	}
	load(planId) {
		const path = join(this.dir, `${sanitizeId(planId)}.json`);
		try {
			if (!existsSync(path)) return null;
			return JSON.parse(readFileSync(path, "utf8"));
		} catch {
			return null;
		}
	}
	loadAll(userId) {
		try {
			if (!existsSync(this.dir)) return [];
			const files = readdirSync(this.dir).filter((f) => f.endsWith(".json") && !f.startsWith("_"));
			const plans = [];
			for (const f of files) try {
				const plan = JSON.parse(readFileSync(join(this.dir, f), "utf8"));
				if (!userId || plan.userId === userId) plans.push(plan);
			} catch {}
			return plans;
		} catch {
			return [];
		}
	}
	delete(planId) {
		const path = join(this.dir, `${sanitizeId(planId)}.json`);
		try {
			if (existsSync(path)) {
				rmSync(path);
				return true;
			}
		} catch {}
		return false;
	}
	saveExecution(exec) {
		const dir = getExecutionsDir(exec.planId);
		this.ensureDir(dir);
		writeFileSync(join(dir, `${sanitizeId(exec.executionId)}.json`), JSON.stringify(exec, null, 2), "utf8");
	}
	loadExecutions(planId) {
		const dir = getExecutionsDir(planId);
		try {
			if (!existsSync(dir)) return [];
			return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => {
				try {
					return JSON.parse(readFileSync(join(dir, f), "utf8"));
				} catch {
					return null;
				}
			}).filter((e) => e !== null);
		} catch {
			return [];
		}
	}
	/** Persist scheduler runtime state (interval counts, last check times). */
	saveState(state) {
		this.ensureDir(this.dir);
		writeFileSync(join(this.dir, "_scheduler-state.json"), JSON.stringify(state), "utf8");
	}
	/** Restore scheduler runtime state. Returns null if no state file. */
	loadState() {
		const path = join(this.dir, "_scheduler-state.json");
		try {
			if (!existsSync(path)) return null;
			return JSON.parse(readFileSync(path, "utf8"));
		} catch {
			return null;
		}
	}
	get templatesDir() {
		return join(this.dir, "templates");
	}
	saveTemplate(template) {
		this.ensureDir(this.templatesDir);
		writeFileSync(join(this.templatesDir, `${sanitizeId(template.id)}.json`), JSON.stringify(template, null, 2), "utf8");
	}
	loadTemplate(templateId) {
		const path = join(this.templatesDir, `${sanitizeId(templateId)}.json`);
		try {
			if (!existsSync(path)) return null;
			return JSON.parse(readFileSync(path, "utf8"));
		} catch {
			return null;
		}
	}
	listTemplates(userId) {
		try {
			if (!existsSync(this.templatesDir)) return [];
			return readdirSync(this.templatesDir).filter((f) => f.endsWith(".json")).map((f) => {
				try {
					return JSON.parse(readFileSync(join(this.templatesDir, f), "utf8"));
				} catch {
					return null;
				}
			}).filter((t) => t !== null).filter((t) => !userId || t.createdBy === userId);
		} catch {
			return [];
		}
	}
	deleteTemplate(templateId) {
		const path = join(this.templatesDir, `${sanitizeId(templateId)}.json`);
		try {
			if (existsSync(path)) {
				rmSync(path);
				return true;
			}
		} catch {}
		return false;
	}
	get deadLetterDir() {
		return join(this.dir, "dead-letter");
	}
	saveDeadLetter(entry) {
		this.ensureDir(this.deadLetterDir);
		const filename = `${sanitizeId(entry.planId)}_${sanitizeId(entry.nodeId)}_${entry.timestamp}.json`;
		writeFileSync(join(this.deadLetterDir, filename), JSON.stringify(entry, null, 2), "utf8");
	}
	loadDeadLetters(planId) {
		try {
			if (!existsSync(this.deadLetterDir)) return [];
			return readdirSync(this.deadLetterDir).filter((f) => f.endsWith(".json")).map((f) => {
				try {
					return JSON.parse(readFileSync(join(this.deadLetterDir, f), "utf8"));
				} catch {
					return null;
				}
			}).filter((e) => e !== null).filter((e) => !planId || e.planId === planId).sort((a, b) => b.timestamp - a.timestamp);
		} catch {
			return [];
		}
	}
	clearDeadLetters(planId) {
		try {
			if (!existsSync(this.deadLetterDir)) return 0;
			const files = readdirSync(this.deadLetterDir).filter((f) => f.endsWith(".json"));
			let removed = 0;
			for (const f of files) {
				if (planId) try {
					if (JSON.parse(readFileSync(join(this.deadLetterDir, f), "utf8")).planId !== planId) continue;
				} catch {
					continue;
				}
				try {
					rmSync(join(this.deadLetterDir, f));
					removed++;
				} catch {}
			}
			return removed;
		} catch {
			return 0;
		}
	}
	get checkpointsDir() {
		return join(this.dir, "checkpoints");
	}
	saveCheckpoint(cp) {
		this.ensureDir(this.checkpointsDir);
		writeFileSync(join(this.checkpointsDir, `${sanitizeId(cp.executionId)}.json`), JSON.stringify(cp, null, 2), "utf8");
	}
	loadCheckpoint(executionId) {
		const path = join(this.checkpointsDir, `${sanitizeId(executionId)}.json`);
		try {
			if (!existsSync(path)) return null;
			return JSON.parse(readFileSync(path, "utf8"));
		} catch {
			return null;
		}
	}
	loadAllCheckpoints() {
		try {
			if (!existsSync(this.checkpointsDir)) return [];
			return readdirSync(this.checkpointsDir).filter((f) => f.endsWith(".json")).map((f) => {
				try {
					return JSON.parse(readFileSync(join(this.checkpointsDir, f), "utf8"));
				} catch {
					return null;
				}
			}).filter((cp) => cp !== null);
		} catch {
			return [];
		}
	}
	deleteCheckpoint(executionId) {
		const path = join(this.checkpointsDir, `${sanitizeId(executionId)}.json`);
		try {
			if (existsSync(path)) {
				rmSync(path);
				return true;
			}
		} catch {}
		return false;
	}
	ensureDir(dir) {
		if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
	}
};
var PlanScheduler = class {
	store;
	resolver;
	handlers = [];
	tickInterval = null;
	plans = /* @__PURE__ */ new Map();
	lastConditionCheck = /* @__PURE__ */ new Map();
	intervalRunCounts = /* @__PURE__ */ new Map();
	cronRunCounts = /* @__PURE__ */ new Map();
	lastCronFire = /* @__PURE__ */ new Map();
	tickMs;
	running = false;
	stateDirty = false;
	stateFlushTimer = null;
	constructor(opts) {
		this.store = opts?.store ?? new FilePlanStore();
		this.resolver = opts?.resolver ?? NULL_RESOLVER;
		this.tickMs = opts?.tickMs ?? 15e3;
	}
	/** Register an event handler. Returns unsubscribe function. */
	on(handler) {
		this.handlers.push(handler);
		return () => {
			this.handlers = this.handlers.filter((h) => h !== handler);
		};
	}
	/** Load persisted plans and start the tick loop. */
	start() {
		if (this.running) return;
		this.running = true;
		const persisted = this.store.loadAll();
		for (const plan of persisted) if (plan.status === "scheduled" || plan.status === "running" || plan.status === "paused") this.plans.set(plan.id, plan);
		this.restoreState();
		this.tickInterval = setInterval(() => this.tick(), this.tickMs);
		this.tick();
	}
	/** Stop the tick loop. Flush pending state. Plans remain persisted. */
	stop() {
		this.running = false;
		if (this.tickInterval) {
			clearInterval(this.tickInterval);
			this.tickInterval = null;
		}
		this.flushState();
		if (this.stateFlushTimer) {
			clearTimeout(this.stateFlushTimer);
			this.stateFlushTimer = null;
		}
	}
	/** Add or update a plan. Persists immediately. */
	addPlan(plan) {
		this.store.save(plan);
		if (plan.status === "scheduled" || plan.status === "running") this.plans.set(plan.id, plan);
		this.emit({
			type: "plan_added",
			plan
		}).catch(() => {});
	}
	/** Cancel a plan. */
	cancelPlan(planId) {
		const plan = this.plans.get(planId) ?? this.store.load(planId);
		if (!plan) return false;
		plan.status = "cancelled";
		this.store.save(plan);
		this.plans.delete(planId);
		this.lastConditionCheck.delete(planId);
		this.intervalRunCounts.delete(planId);
		this.cronRunCounts.delete(planId);
		this.lastCronFire.delete(planId);
		this.persistState();
		this.emit({
			type: "plan_cancelled",
			planId
		}).catch(() => {});
		return true;
	}
	/** Pause a plan (stops trigger checking, can be resumed). */
	pausePlan(planId) {
		const plan = this.plans.get(planId);
		if (!plan || plan.status !== "scheduled") return false;
		plan.status = "paused";
		this.store.save(plan);
		return true;
	}
	/** Resume a paused plan. */
	resumePlan(planId) {
		const plan = this.plans.get(planId) ?? this.store.load(planId);
		if (!plan || plan.status !== "paused") return false;
		plan.status = "scheduled";
		this.store.save(plan);
		this.plans.set(plan.id, plan);
		return true;
	}
	/** Get a plan by ID. */
	getPlan(planId) {
		return this.plans.get(planId) ?? this.store.load(planId);
	}
	/** List all plans for a user. */
	listPlans(userId) {
		return this.store.loadAll(userId);
	}
	/** Get execution history for a plan. */
	getExecutions(planId) {
		return this.store.loadExecutions(planId);
	}
	saveTemplate(template) {
		if (typeof this.store.saveTemplate === "function") this.store.saveTemplate(template);
	}
	loadTemplate(templateId) {
		if (typeof this.store.loadTemplate === "function") return this.store.loadTemplate(templateId);
		return null;
	}
	listTemplates(userId) {
		if (typeof this.store.listTemplates === "function") return this.store.listTemplates(userId);
		return [];
	}
	deleteTemplate(templateId) {
		if (typeof this.store.deleteTemplate === "function") return this.store.deleteTemplate(templateId);
		return false;
	}
	saveDeadLetter(entry) {
		if (typeof this.store.saveDeadLetter === "function") this.store.saveDeadLetter(entry);
	}
	loadDeadLetters(planId) {
		if (typeof this.store.loadDeadLetters === "function") return this.store.loadDeadLetters(planId);
		return [];
	}
	clearDeadLetters(planId) {
		if (typeof this.store.clearDeadLetters === "function") return this.store.clearDeadLetters(planId);
		return 0;
	}
	saveCheckpoint(cp) {
		if (typeof this.store.saveCheckpoint === "function") this.store.saveCheckpoint(cp);
	}
	loadCheckpoint(executionId) {
		if (typeof this.store.loadCheckpoint === "function") return this.store.loadCheckpoint(executionId);
		return null;
	}
	loadAllCheckpoints() {
		if (typeof this.store.loadAllCheckpoints === "function") return this.store.loadAllCheckpoints();
		return [];
	}
	deleteCheckpoint(executionId) {
		if (typeof this.store.deleteCheckpoint === "function") return this.store.deleteCheckpoint(executionId);
		return false;
	}
	/** Mark a plan execution as complete (called by executor). */
	markCompleted(planId, execution) {
		const plan = this.plans.get(planId);
		if (!plan) return;
		this.store.saveExecution(execution);
		const trigger = plan.trigger;
		if (!trigger || trigger.type === "immediate" || trigger.type === "time") {
			plan.status = "completed";
			this.store.save(plan);
			this.plans.delete(planId);
		} else if (trigger.type === "condition" && !trigger.recurring) {
			plan.status = "completed";
			this.store.save(plan);
			this.plans.delete(planId);
		} else if (trigger.type === "price" && !trigger.recurring) {
			plan.status = "completed";
			this.store.save(plan);
			this.plans.delete(planId);
		}
	}
	/** Mark a plan execution as failed (called by executor). */
	markFailed(planId, execution) {
		const plan = this.plans.get(planId);
		if (!plan) return;
		this.store.saveExecution(execution);
		const trigger = plan.trigger;
		if (!trigger || trigger.type === "immediate" || trigger.type === "time") {
			plan.status = "failed";
			this.store.save(plan);
			this.plans.delete(planId);
		} else if (trigger.type === "price" && !trigger.recurring) {
			plan.status = "failed";
			this.store.save(plan);
			this.plans.delete(planId);
		}
	}
	/** Restore intervalRunCounts and lastConditionCheck from disk. */
	restoreState() {
		if (typeof this.store.loadState !== "function") return;
		const state = this.store.loadState();
		if (!state) return;
		if (state.intervalRunCounts) {
			for (const [k, v] of Object.entries(state.intervalRunCounts)) if (this.plans.has(k)) this.intervalRunCounts.set(k, v);
		}
		if (state.lastConditionCheck) {
			for (const [k, v] of Object.entries(state.lastConditionCheck)) if (this.plans.has(k)) this.lastConditionCheck.set(k, v);
		}
		if (state.cronRunCounts) {
			for (const [k, v] of Object.entries(state.cronRunCounts)) if (this.plans.has(k)) this.cronRunCounts.set(k, v);
		}
		if (state.lastCronFire) {
			for (const [k, v] of Object.entries(state.lastCronFire)) if (this.plans.has(k)) this.lastCronFire.set(k, v);
		}
	}
	/** Mark state as dirty; it will be flushed within 5s or at next tick boundary. */
	persistState() {
		this.stateDirty = true;
		if (!this.stateFlushTimer) this.stateFlushTimer = setTimeout(() => {
			this.stateFlushTimer = null;
			this.flushState();
		}, 5e3);
	}
	/** Write state to disk immediately if dirty. */
	flushState() {
		if (!this.stateDirty) return;
		if (typeof this.store.saveState !== "function") return;
		this.stateDirty = false;
		const state = {
			intervalRunCounts: Object.fromEntries(this.intervalRunCounts),
			lastConditionCheck: Object.fromEntries(this.lastConditionCheck),
			cronRunCounts: Object.fromEntries(this.cronRunCounts),
			lastCronFire: Object.fromEntries(this.lastCronFire)
		};
		this.store.saveState(state);
	}
	/** How many plans are actively being watched. */
	get activeCount() {
		return this.plans.size;
	}
	/** Get all active in-memory plans. */
	getActivePlans() {
		return Array.from(this.plans.values());
	}
	/** Is the scheduler running. */
	get isRunning() {
		return this.running;
	}
	ticking = false;
	async tick() {
		if (this.ticking) return;
		this.ticking = true;
		try {
			const now = Date.now();
			for (const [planId, plan] of this.plans) {
				if (plan.status === "paused" || plan.status === "running") continue;
				try {
					if (await this.shouldTriggerFire(plan, now)) await this.fireTrigger(plan);
				} catch (err) {
					await this.emit({
						type: "condition_check_error",
						planId,
						error: err.message ?? String(err)
					});
				}
			}
		} finally {
			this.ticking = false;
		}
	}
	async shouldTriggerFire(plan, now) {
		const trigger = plan.trigger;
		if (!trigger || trigger.type === "immediate") return true;
		switch (trigger.type) {
			case "time": {
				const at = new Date(trigger.at).getTime();
				return !isNaN(at) && now >= at;
			}
			case "interval": {
				const startAt = trigger.startAt ? new Date(trigger.startAt).getTime() : plan.createdAt;
				if (now < startAt) return false;
				if (now > (trigger.endAt ? new Date(trigger.endAt).getTime() : Infinity)) {
					await this.emit({
						type: "plan_expired",
						plan,
						reason: "Past end time"
					});
					plan.status = "completed";
					this.store.save(plan);
					this.plans.delete(plan.id);
					return false;
				}
				const runCount = this.intervalRunCounts.get(plan.id) ?? 0;
				if (trigger.maxRuns && runCount >= trigger.maxRuns) {
					await this.emit({
						type: "plan_expired",
						plan,
						reason: `Reached ${trigger.maxRuns} executions`
					});
					plan.status = "completed";
					this.store.save(plan);
					this.plans.delete(plan.id);
					return false;
				}
				const elapsed = now - startAt;
				return Math.floor(elapsed / trigger.everyMs) > runCount;
			}
			case "condition": {
				const lastCheck = this.lastConditionCheck.get(plan.id) ?? 0;
				const pollMs = trigger.pollIntervalMs ?? 6e4;
				if (now - lastCheck < pollMs) return false;
				this.lastConditionCheck.set(plan.id, now);
				this.persistState();
				if (trigger.expiresAfterMs && now - plan.createdAt > trigger.expiresAfterMs) {
					await this.emit({
						type: "plan_expired",
						plan,
						reason: "Condition watch expired"
					});
					plan.status = "completed";
					this.store.save(plan);
					this.plans.delete(plan.id);
					return false;
				}
				return await this.evaluateCondition(trigger.when);
			}
			case "cron": return this.evaluateCronTrigger(plan, trigger, now);
			case "price": return false;
			case "onchain_event": return false;
			case "balance": return false;
		}
		return false;
	}
	async fireTrigger(plan) {
		const executionId = `exec_${plan.id}_${Date.now()}`;
		const trigger = plan.trigger;
		if (trigger?.type === "interval") {
			const count = (this.intervalRunCounts.get(plan.id) ?? 0) + 1;
			this.intervalRunCounts.set(plan.id, count);
			this.persistState();
		}
		if (!trigger || trigger.type === "immediate" || trigger.type === "time") {
			plan.status = "running";
			this.store.save(plan);
		}
		if (trigger?.type === "condition" && !trigger.recurring) {
			plan.status = "running";
			this.store.save(plan);
		}
		await this.emit({
			type: "trigger_fired",
			plan,
			executionId
		});
	}
	evaluateCronTrigger(plan, trigger, now) {
		const runCount = this.cronRunCounts.get(plan.id) ?? 0;
		if (trigger.maxRuns && runCount >= trigger.maxRuns) {
			plan.status = "completed";
			this.store.save(plan);
			this.plans.delete(plan.id);
			return false;
		}
		const minuteFloor = Math.floor(now / 6e4) * 6e4;
		if (minuteFloor <= (this.lastCronFire.get(plan.id) ?? 0)) return false;
		const date = trigger.timezone ? dateInTimezone(now, trigger.timezone) : new Date(now);
		if (!matchesCron(trigger.expression, date)) return false;
		this.cronRunCounts.set(plan.id, runCount + 1);
		this.lastCronFire.set(plan.id, minuteFloor);
		this.persistState();
		return true;
	}
	async firePriceTrigger(planId) {
		const plan = this.plans.get(planId);
		if (!plan) return;
		if (plan.status === "paused" || plan.status === "running") return;
		const trigger = plan.trigger;
		if (!trigger || trigger.type !== "price") return;
		if (!trigger.recurring) {
			plan.status = "running";
			this.store.save(plan);
		}
		const executionId = `exec_${plan.id}_${Date.now()}`;
		await this.emit({
			type: "trigger_fired",
			plan,
			executionId
		});
	}
	async fireOnChainTrigger(planId) {
		const plan = this.plans.get(planId);
		if (!plan) return;
		if (plan.status === "paused" || plan.status === "running") return;
		const trigger = plan.trigger;
		if (!trigger || trigger.type !== "onchain_event") return;
		if (!trigger.recurring) {
			plan.status = "running";
			this.store.save(plan);
		}
		const executionId = `exec_${plan.id}_${Date.now()}`;
		await this.emit({
			type: "trigger_fired",
			plan,
			executionId
		});
	}
	async fireBalanceTrigger(planId) {
		const plan = this.plans.get(planId);
		if (!plan) return;
		if (plan.status === "paused" || plan.status === "running") return;
		const trigger = plan.trigger;
		if (!trigger || trigger.type !== "balance") return;
		if (!trigger.recurring) {
			plan.status = "running";
			this.store.save(plan);
		}
		const executionId = `exec_${plan.id}_${Date.now()}`;
		await this.emit({
			type: "trigger_fired",
			plan,
			executionId
		});
	}
	async evaluateCondition(cond) {
		if (cond.type === "compare") {
			const left = await this.resolveValue(cond.left);
			const right = await this.resolveValue(cond.right);
			return this.compare(left, cond.op, right);
		} else if (cond.type === "logic") switch (cond.op) {
			case "and":
				for (const sub of cond.conditions) if (!await this.evaluateCondition(sub)) return false;
				return true;
			case "or":
				for (const sub of cond.conditions) if (await this.evaluateCondition(sub)) return true;
				return false;
			case "not": return !await this.evaluateCondition(cond.conditions[0]);
		}
		return false;
	}
	async resolveValue(ref) {
		switch (ref.type) {
			case "literal": return typeof ref.value === "number" ? ref.value : parseFloat(String(ref.value)) || 0;
			case "runtime": return this.resolveRuntimeFn(ref.fn, ref.args);
			case "env": {
				const v = process.env[ref.key];
				return v ? parseFloat(v) || 0 : 0;
			}
			case "step_output": return 0;
		}
	}
	async resolveRuntimeFn(fn, args) {
		const resolvedArgs = await Promise.all(args.map((a) => this.resolveValue(a)));
		switch (fn) {
			case "price":
				const token = args[0]?.type === "literal" ? String(args[0].value) : "ETH";
				return this.resolver.price(token);
			case "balance":
				const balToken = args[0]?.type === "literal" ? String(args[0].value) : "ETH";
				const balChain = resolvedArgs[1] ?? void 0;
				return this.resolver.balance(balToken, balChain);
			case "gas_price": return this.resolver.gasPrice(resolvedArgs[0] ?? void 0);
			case "timestamp": return this.resolver.timestamp();
			case "block_number": return this.resolver.blockNumber(resolvedArgs[0] ?? void 0);
		}
	}
	compare(left, op, right) {
		switch (op) {
			case "gt": return left > right;
			case "gte": return left >= right;
			case "lt": return left < right;
			case "lte": return left <= right;
			case "eq": return Math.abs(left - right) < Number.EPSILON;
			case "neq": return Math.abs(left - right) >= Number.EPSILON;
			default: return false;
		}
	}
	async emit(event) {
		for (const handler of this.handlers) try {
			await handler(event);
		} catch (err) {
			console.error(`[plan-scheduler] handler error on ${event.type}:`, err);
		}
	}
};
/** Convert a Date to the equivalent time in a different timezone. */
function dateInTimezone(epochMs, tz) {
	try {
		const parts = new Intl.DateTimeFormat("en-US", {
			timeZone: tz,
			year: "numeric",
			month: "2-digit",
			day: "2-digit",
			hour: "2-digit",
			minute: "2-digit",
			second: "2-digit",
			hour12: false
		}).formatToParts(new Date(epochMs));
		const get = (type) => parseInt(parts.find((p) => p.type === type)?.value ?? "0", 10);
		return new Date(get("year"), get("month") - 1, get("day"), get("hour"), get("minute"), get("second"));
	} catch {
		return new Date(epochMs);
	}
}
/** Check if a date matches a 5-field cron expression. */
function matchesCron(expression, date) {
	const fields = expression.trim().split(/\s+/);
	if (fields.length !== 5) return false;
	const minute = date.getMinutes();
	const hour = date.getHours();
	const dayOfMonth = date.getDate();
	const month = date.getMonth() + 1;
	const dayOfWeek = date.getDay();
	return matchesField(fields[0], minute, 0, 59) && matchesField(fields[1], hour, 0, 23) && matchesField(fields[2], dayOfMonth, 1, 31) && matchesField(fields[3], month, 1, 12) && matchesField(fields[4], dayOfWeek, 0, 7);
}
/** Check if a single cron field matches a value. */
function matchesField(field, value, min, max) {
	if (max === 7 && value === 7) value = 0;
	for (const part of field.split(",")) if (matchesPart(part.trim(), value, min, max)) return true;
	return false;
}
function matchesPart(part, value, min, _max) {
	const slashIdx = part.indexOf("/");
	const rangePart = slashIdx >= 0 ? part.slice(0, slashIdx) : part;
	const step = slashIdx >= 0 ? parseInt(part.slice(slashIdx + 1), 10) : 1;
	if (isNaN(step) || step < 1) return false;
	if (rangePart === "*") return (value - min) % step === 0;
	const dashIdx = rangePart.indexOf("-");
	if (dashIdx >= 0) {
		const start = parseInt(rangePart.slice(0, dashIdx), 10);
		const end = parseInt(rangePart.slice(dashIdx + 1), 10);
		if (isNaN(start) || isNaN(end)) return false;
		if (value < start || value > end) return false;
		return (value - start) % step === 0;
	}
	const num = parseInt(rangePart, 10);
	if (isNaN(num)) return false;
	return value === num;
}
let _scheduler = null;
function getScheduler(opts) {
	if (!_scheduler) _scheduler = new PlanScheduler(opts);
	else if (opts) console.warn("[plan-scheduler] getScheduler() called with config but scheduler already exists. Config ignored. Call resetScheduler() first to reconfigure.");
	return _scheduler;
}
//#endregion
export { plan_scheduler_exports as n, getScheduler as t };

//# sourceMappingURL=plan-scheduler-DLnFAyk5.mjs.map