import { useEffect } from "react";
import { emitControllerToken, setExternalControllerMovement, subscribeControllerToken } from "../input/controller";
import { getFrontendSnapshot, useFrontendStore } from "../frontend/store";
import { useGameStore } from "../game/store";
import {
  createDualScreenChannel,
  currentBottomGameSnapshot,
  diffBottomGameSnapshot,
  receiveAuthoritativeMessage,
  type BottomGameSnapshot,
  type DualScreenMessage,
} from "./dualScreenSync";
import { forceBothThorDisplays, listenForDisplayDisconnect, shouldOwnNativeDisplays } from "./nativeDualScreen";
import { createRateLimitedPublisher } from "./rateLimitedPublisher";

const GAME_SYNC_INTERVAL_MS = 100;

export function useForcedThorDisplays() {
  useEffect(() => {
    if (!shouldOwnNativeDisplays()) return;
    let disposed = false;
    let listener: { remove: () => Promise<void> } | null = null;
    const force = () => forceBothThorDisplays().catch(() => {
      // Native display listener retains force mode and opens when Thor reports panel again.
    });
    force();
    listenForDisplayDisconnect(() => {
      if (!disposed) force();
    }).then((handle) => {
      if (disposed) handle.remove();
      else listener = handle;
    }).catch(() => undefined);
    return () => {
      disposed = true;
      listener?.remove();
    };
  }, []);
}

export function useAuthoritativeDualScreenSync() {
  useEffect(() => {
    const channel = createDualScreenChannel();
    if (!channel) return;
    let latest = useFrontendStore.getState();
    let companionReady = false;
    let receivingRelayedToken = false;
    let topTokenSequence = 0;
    let lastPublishedGame: BottomGameSnapshot | undefined;
    const publish = (includeFrontend: boolean) => {
      const hunter = latest.activeSlotId
        ? latest.slots.find((slot) => slot.id === latest.activeSlotId)?.local ?? null
        : null;
      const snapshot = latest.screen === "game" ? currentBottomGameSnapshot() : undefined;
      const game = snapshot ? diffBottomGameSnapshot(lastPublishedGame, snapshot) : undefined;
      lastPublishedGame = snapshot;
      channel.postMessage({
        type: "app-state",
        screen: latest.screen,
        hunterName: hunter?.hunterName ?? null,
        notice: latest.notice,
        frontend: includeFrontend ? getFrontendSnapshot() : undefined,
        game,
      } satisfies DualScreenMessage);
    };
    // Player motion and simulation can update the same store several times per
    // frame interval. The lower display receives a 10 Hz tactical projection and
    // only fields that changed semantically since the last publication.
    const gamePublisher = createRateLimitedPublisher(() => publish(false), GAME_SYNC_INTERVAL_MS);
    const unsubscribeFrontend = useFrontendStore.subscribe((state) => {
      latest = state;
      if (state.screen !== "game") lastPublishedGame = undefined;
      if (companionReady) publish(true);
    });
    const unsubscribeGame = useGameStore.subscribe(() => {
      if (companionReady && latest.screen === "game") gamePublisher.request();
    });
    const unsubscribeController = subscribeControllerToken((controllerEvent) => {
      if (!companionReady || receivingRelayedToken) return;
      topTokenSequence += 1;
      channel.postMessage({
        type: "controller-echo",
        id: `top-${topTokenSequence}`,
        event: controllerEvent,
      } satisfies DualScreenMessage);
    });
    channel.onmessage = (event: MessageEvent<DualScreenMessage>) => {
      if (event.data.type === "companion-ready") {
        companionReady = true;
        lastPublishedGame = undefined;
        publish(true);
      } else if (event.data.type === "companion-closing") {
        companionReady = false;
        lastPublishedGame = undefined;
        gamePublisher.cancel();
        setExternalControllerMovement({ moveX: 0, moveY: 0, lookX: 0, lookY: 0 });
      } else if (event.data.type === "controller-token") {
        receivingRelayedToken = true;
        emitControllerToken(event.data.event);
        receivingRelayedToken = false;
        channel.postMessage({ ...event.data, type: "controller-echo" } satisfies DualScreenMessage);
      } else receiveAuthoritativeMessage(event.data);
    };
    // Lets a companion that opened first repeat its handshake without sending a
    // full state snapshot when no second display exists.
    channel.postMessage({ type: "authoritative-ready" } satisfies DualScreenMessage);
    return () => {
      unsubscribeFrontend();
      unsubscribeGame();
      unsubscribeController();
      gamePublisher.dispose();
      channel.close();
    };
  }, []);
}
