"use client";

import { useEffect, useSyncExternalStore } from "react";

import type { StreamEvent } from "@/lib/api/types";

type StreamSnapshot = {
  connected: boolean;
  ready: boolean;
  lastEvent: StreamEvent | null;
};

type Listener = () => void;

const INITIAL_SNAPSHOT: StreamSnapshot = {
  connected: false,
  ready: false,
  lastEvent: null,
};

const RETRY_DELAY_MS = 2_000;

class StreamClient {
  private snapshot: StreamSnapshot = INITIAL_SNAPSHOT;
  private listeners = new Set<Listener>();
  private source: EventSource | null = null;
  private retryHandle: number | null = null;

  subscribe = (listener: Listener) => {
    this.listeners.add(listener);
    this.ensureConnected();
    return () => {
      this.listeners.delete(listener);
      if (this.listeners.size === 0) {
        this.teardown();
      }
    };
  };

  getSnapshot = () => this.snapshot;

  getServerSnapshot = () => INITIAL_SNAPSHOT;

  private emit() {
    for (const listener of this.listeners) {
      listener();
    }
  }

  private update(next: Partial<StreamSnapshot>) {
    this.snapshot = { ...this.snapshot, ...next };
    this.emit();
  }

  private scheduleReconnect() {
    if (this.retryHandle !== null || this.listeners.size === 0) {
      return;
    }
    this.retryHandle = window.setTimeout(() => {
      this.retryHandle = null;
      this.ensureConnected();
    }, RETRY_DELAY_MS);
  }

  private teardown() {
    if (this.retryHandle !== null) {
      window.clearTimeout(this.retryHandle);
      this.retryHandle = null;
    }
    this.source?.close();
    this.source = null;
    this.snapshot = INITIAL_SNAPSHOT;
  }

  private ensureConnected() {
    if (typeof window === "undefined" || this.source || this.listeners.size === 0) {
      return;
    }

    const source = new EventSource("/api/stream");
    this.source = source;

    source.onopen = () => {
      this.update({ ready: true });
    };

    source.onmessage = (message) => {
      try {
        const payload = JSON.parse(message.data) as StreamEvent;
        this.update({ connected: true, ready: true, lastEvent: payload });
      } catch {
        // Ignore malformed payloads to keep the shell interactive.
      }
    };

    source.addEventListener("upstream-open", () => {
      this.update({ connected: true, ready: true });
    });

    source.addEventListener("upstream-closed", () => {
      this.update({ connected: false, ready: true });
    });

    source.onerror = () => {
      source.close();
      if (this.source === source) {
        this.source = null;
      }
      this.update({ connected: false, ready: true });
      this.scheduleReconnect();
    };
  }
}

const streamClient = new StreamClient();

export function useStream(onEvent?: (event: StreamEvent) => void) {
  const snapshot = useSyncExternalStore(
    streamClient.subscribe,
    streamClient.getSnapshot,
    streamClient.getServerSnapshot,
  );

  useEffect(() => {
    if (snapshot.lastEvent) {
      onEvent?.(snapshot.lastEvent);
    }
  }, [snapshot.lastEvent, onEvent]);

  return snapshot;
}
