import type { Adapter } from "./adapter";
import type { AdapterResult } from "./schema";

export interface Clock {
  now(): number;
  setTimeout(fn: () => void, ms: number): unknown;
  clearTimeout(handle: unknown): void;
}

export const systemClock: Clock = {
  now: () => Date.now(),
  setTimeout: (fn, ms) => setTimeout(fn, ms),
  clearTimeout: (h) => clearTimeout(h as ReturnType<typeof setTimeout>),
};

export type PollOutcome =
  | { ok: true; data: AdapterResult }
  | { ok: false; error: unknown };

export type PollListener = (adapter: Adapter, outcome: PollOutcome, atTs: number, durationMs: number) => void;

const JITTER_RATIO = 0.1;

/** Schedules per-adapter polling with jitter. Adapter errors never propagate — reported via onResult. */
export class PollScheduler {
  private readonly timers = new Map<string, unknown>();
  private stopped = false;

  constructor(
    private readonly adapters: readonly Adapter[],
    private readonly onResult: PollListener,
    private readonly clock: Clock = systemClock,
    private readonly rng: () => number = Math.random,
  ) {}

  start(): void {
    this.stopped = false;
    for (const adapter of this.adapters) {
      this.scheduleNext(adapter, 0);
    }
  }

  stop(): void {
    this.stopped = true;
    for (const handle of this.timers.values()) {
      this.clock.clearTimeout(handle);
    }
    this.timers.clear();
  }

  private scheduleNext(adapter: Adapter, delayMs: number): void {
    if (this.stopped) return;
    const handle = this.clock.setTimeout(() => {
      void this.runPoll(adapter);
    }, delayMs);
    this.timers.set(adapter.id, handle);
  }

  private jitteredInterval(adapter: Adapter): number {
    const jitter = adapter.interval * JITTER_RATIO * (this.rng() * 2 - 1);
    return Math.max(0, Math.round(adapter.interval + jitter));
  }

  private async runPoll(adapter: Adapter): Promise<void> {
    if (this.stopped) return;
    const startedAt = this.clock.now();
    try {
      const data = await adapter.poll();
      const completedAt = this.clock.now();
      this.onResult(adapter, { ok: true, data }, completedAt, Math.max(0, completedAt - startedAt));
    } catch (error) {
      const completedAt = this.clock.now();
      this.onResult(adapter, { ok: false, error }, completedAt, Math.max(0, completedAt - startedAt));
    }
    this.scheduleNext(adapter, this.jitteredInterval(adapter));
  }
}
