/**
 * System Clock / Heartbeat Service
 *
 * Broadcasts consistent time signals to all connected canvas clients.
 * Single server-authoritative source — clients subscribe via CanvasEventBus.
 * Clock events are ephemeral (WebSocket only, not persisted in CRDT).
 */

export interface ClockPayload {
	timestamp: number;
	isoString: string;
	hour: number;
	minute: number;
	second: number;
	dayOfWeek: number;
	tickCount: number;
}

export interface ClockConfig {
	enabled: boolean;
	/** Timezone for daily boundary (default: UTC) */
	timezone: string;
	/** Tick interval in seconds (minimum 10, default 60) */
	tickInterval: number;
	/** Which channels to emit */
	channels: ("tick" | "minute:5" | "hourly" | "daily")[];
}

const DEFAULT_CONFIG: ClockConfig = {
	enabled: true,
	timezone: "UTC",
	tickInterval: 60,
	channels: ["tick", "minute:5", "hourly", "daily"],
};

export type ClockBroadcastFn = (channel: string, payload: ClockPayload) => void;

export class SystemClock {
	#config: ClockConfig;
	#broadcast: ClockBroadcastFn;
	#timer: ReturnType<typeof setInterval> | null = null;
	#tickCount = 0;
	#lastHour = -1;
	#lastDay = -1;

	constructor(broadcast: ClockBroadcastFn, config?: Partial<ClockConfig>) {
		this.#config = { ...DEFAULT_CONFIG, ...config };
		// Enforce minimum tick interval
		if (this.#config.tickInterval < 10) this.#config.tickInterval = 10;
		this.#broadcast = broadcast;
	}

	/** Start the clock. Idempotent. */
	start(): void {
		if (this.#timer || !this.#config.enabled) return;

		console.log(
			`[SystemClock] Started (interval: ${this.#config.tickInterval}s, channels: ${this.#config.channels.join(", ")})`,
		);

		// Fire first tick immediately
		this.#tick();

		this.#timer = setInterval(
			() => this.#tick(),
			this.#config.tickInterval * 1000,
		);
	}

	/** Stop the clock. */
	stop(): void {
		if (this.#timer) {
			clearInterval(this.#timer);
			this.#timer = null;
			console.log("[SystemClock] Stopped");
		}
	}

	/** Check if the clock is running. */
	get running(): boolean {
		return this.#timer !== null;
	}

	/** Update config and restart if running. */
	updateConfig(config: Partial<ClockConfig>): void {
		const wasRunning = this.running;
		this.stop();
		this.#config = { ...this.#config, ...config };
		if (this.#config.tickInterval < 10) this.#config.tickInterval = 10;
		if (wasRunning && this.#config.enabled) this.start();
	}

	#tick(): void {
		this.#tickCount++;
		const now = new Date();
		const payload = this.#makePayload(now);

		// Primary tick
		if (this.#config.channels.includes("tick")) {
			this.#broadcast("clock:tick", payload);
		}

		// 5-minute tick
		if (
			this.#config.channels.includes("minute:5") &&
			this.#tickCount > 1 &&
			now.getMinutes() % 5 === 0 &&
			now.getSeconds() < this.#config.tickInterval
		) {
			this.#broadcast("clock:minute:5", payload);
		}

		// Hourly tick
		const currentHour = now.getUTCHours();
		if (
			this.#config.channels.includes("hourly") &&
			this.#lastHour !== -1 &&
			currentHour !== this.#lastHour
		) {
			this.#broadcast("clock:hourly", payload);
		}
		this.#lastHour = currentHour;

		// Daily tick
		const currentDay = now.getUTCDay();
		if (
			this.#config.channels.includes("daily") &&
			this.#lastDay !== -1 &&
			currentDay !== this.#lastDay
		) {
			this.#broadcast("clock:daily", payload);
		}
		this.#lastDay = currentDay;
	}

	#makePayload(now: Date): ClockPayload {
		return {
			timestamp: now.getTime(),
			isoString: now.toISOString(),
			hour: now.getUTCHours(),
			minute: now.getUTCMinutes(),
			second: now.getUTCSeconds(),
			dayOfWeek: now.getUTCDay(),
			tickCount: this.#tickCount,
		};
	}
}
