import { getEventBus } from "./event-bus-CMN2leCa.mjs";
//#region extensions/crypto/src/services/price-watcher.ts
/**
* Price Watcher — polls token prices and fires events when thresholds are crossed.
*
* This service is the bridge between the event bus and price triggers defined
* on plans. It maintains a set of "watches" (token + condition + threshold)
* and periodically checks prices, emitting `price_crossed` events when thresholds
* are breached.
*
* Design decisions:
* - Uses the simple price-service (DexScreener) for fast, cached lookups.
* - Deduplicates watches per token — if 3 plans watch ETH at different thresholds,
*   we fetch ETH once per tick and check all 3 thresholds.
* - Supports hysteresis (price must move N% past threshold before re-triggering)
*   and cooldown (minimum time between triggers for same watch).
* - Does NOT execute plans directly — it emits events that the scheduler consumes.
*/
var PriceWatcher = class {
	watches = /* @__PURE__ */ new Map();
	state = /* @__PURE__ */ new Map();
	tickInterval = null;
	running = false;
	priceFetcher;
	tickMs;
	constructor(opts) {
		this.priceFetcher = opts?.priceFetcher ?? defaultPriceFetcher;
		this.tickMs = opts?.tickMs ?? 3e4;
	}
	/** Add a price watch. */
	addWatch(watch) {
		this.watches.set(watch.id, watch);
		if (!this.state.has(watch.id)) this.state.set(watch.id, {
			lastPrice: null,
			wasTriggered: false,
			lastFiredAt: 0
		});
	}
	/** Create a watch from a PriceTrigger (from a plan). */
	addFromTrigger(planId, trigger) {
		this.addWatch({
			id: planId,
			token: trigger.token,
			condition: trigger.condition,
			threshold: trigger.threshold,
			hysteresisPercent: trigger.hysteresisPercent ?? 1,
			cooldownMs: trigger.cooldownMs ?? 3e5,
			recurring: trigger.recurring ?? false
		});
	}
	/** Remove a watch. */
	removeWatch(id) {
		this.state.delete(id);
		return this.watches.delete(id);
	}
	/** Get all active watches. */
	getWatches() {
		return Array.from(this.watches.values());
	}
	/** Get the number of active watches. */
	get watchCount() {
		return this.watches.size;
	}
	/** Start the polling loop. */
	start() {
		if (this.running) return;
		this.running = true;
		this.tickInterval = setInterval(() => {
			this.tick().catch(() => {});
		}, this.tickMs);
	}
	/** Stop the polling loop. */
	stop() {
		this.running = false;
		if (this.tickInterval) {
			clearInterval(this.tickInterval);
			this.tickInterval = null;
		}
	}
	/** Check if the watcher is running. */
	get isRunning() {
		return this.running;
	}
	/** Run one price check cycle. Public for testing. */
	async tick() {
		if (this.watches.size === 0) return;
		const tokenWatches = /* @__PURE__ */ new Map();
		for (const watch of this.watches.values()) {
			const key = watch.token.toUpperCase();
			if (!tokenWatches.has(key)) tokenWatches.set(key, []);
			tokenWatches.get(key).push(watch);
		}
		const pricePromises = Array.from(tokenWatches.keys()).map(async (token) => {
			try {
				return {
					token,
					price: await this.priceFetcher(token)
				};
			} catch {
				return {
					token,
					price: null
				};
			}
		});
		const results = await Promise.all(pricePromises);
		const prices = /* @__PURE__ */ new Map();
		for (const { token, price } of results) if (price !== null && !isNaN(price)) prices.set(token, price);
		const now = Date.now();
		const toRemove = [];
		const bus = getEventBus();
		for (const watch of this.watches.values()) {
			const tokenKey = watch.token.toUpperCase();
			const currentPrice = prices.get(tokenKey);
			if (currentPrice === void 0) continue;
			const st = this.state.get(watch.id);
			const previousPrice = st.lastPrice;
			st.lastPrice = currentPrice;
			if (now - st.lastFiredAt < watch.cooldownMs) continue;
			if (this.evaluateCondition(watch, currentPrice, previousPrice, st.wasTriggered)) {
				st.wasTriggered = true;
				st.lastFiredAt = now;
				bus.emit("price_crossed", {
					type: "price_crossed",
					token: watch.token,
					condition: watch.condition,
					threshold: watch.threshold,
					currentPrice,
					previousPrice: previousPrice ?? currentPrice,
					timestamp: now
				});
				if (!watch.recurring) toRemove.push(watch.id);
			} else if (st.wasTriggered) {
				const hysteresisMargin = watch.threshold * (watch.hysteresisPercent / 100);
				if (watch.condition === "above" ? currentPrice < watch.threshold - hysteresisMargin : watch.condition === "below" ? currentPrice > watch.threshold + hysteresisMargin : Math.abs(currentPrice - watch.threshold) > hysteresisMargin) st.wasTriggered = false;
			}
		}
		for (const id of toRemove) {
			this.watches.delete(id);
			this.state.delete(id);
		}
	}
	evaluateCondition(watch, currentPrice, previousPrice, wasTriggered) {
		if (wasTriggered) return false;
		switch (watch.condition) {
			case "above": return currentPrice >= watch.threshold;
			case "below": return currentPrice <= watch.threshold;
			case "crosses":
				if (previousPrice === null) return false;
				return previousPrice < watch.threshold && currentPrice >= watch.threshold || previousPrice > watch.threshold && currentPrice <= watch.threshold;
			default: return false;
		}
	}
	/** Clear all watches and state. */
	clear() {
		this.watches.clear();
		this.state.clear();
	}
};
const defaultPriceFetcher = async (token) => {
	const { getPrice } = await import("./price-service-3xN2ZZh7.mjs").then((n) => n.r);
	return (await getPrice(token))?.priceUsd ?? null;
};
let instance = null;
function getPriceWatcher(opts) {
	if (!instance) instance = new PriceWatcher(opts);
	return instance;
}
//#endregion
export { getPriceWatcher };

//# sourceMappingURL=price-watcher-BfdX3aMJ.mjs.map