import { lazy, Suspense, useCallback, useEffect, useRef, useState } from "react";
import { emitControllerToken, isControllerDispatchActive, subscribeControllerMovement, subscribeControllerToken } from "../input/controller";
import { useGameStore } from "../game/store";
import { useFrontendStore } from "../frontend/store";
import type { AppScreen } from "../frontend/types";
import { FrontEnd } from "../components/FrontEnd";
import { createDualScreenChannel, type DualScreenMessage, type FrontendCommand, type GameCommand } from "./dualScreenSync";
import type { BossId } from "../game/types";
import type { DifficultySlug } from "../game/progression/loot";
import { useForcedThorDisplays } from "./useThorDualScreen";
import { createRateLimitedPublisher } from "./rateLimitedPublisher";
import { DEFAULT_CONTROLLER_GLYPHS } from "../input/controllerGlyphs";

const BottomScreen = lazy(() => import("../components/BottomScreen").then((module) => ({ default: module.BottomScreen })));
const CONTROLLER_MOTION_SYNC_INTERVAL_MS = 33;

function screenTitle(screen: AppScreen) {
  switch (screen) {
    case "login": return "Sign in or continue offline";
    case "saves": return "Choose hunter save";
    case "home": return "Choose expedition";
    case "profile": return "Hunter profile";
    case "gear": return "Gear upgrade";
    case "settings": return "Field settings";
    case "mode": return "Prepare encounter";
    case "game": return "Field console";
  }
}

function CompanionStandby({ screen, hunterName, notice }: {
  screen: AppScreen;
  hunterName: string | null;
  notice: string;
}) {
  return (
    <section className="display bottom-display companion-standby" aria-label="Thor context display">
      <header>
        <span>IH</span>
        <div><small>AYN Thor · Context display</small><strong>I Want To Heal</strong></div>
      </header>
      <main>
        <small>Current task</small>
        <h1>{screenTitle(screen)}</h1>
        <p>{hunterName ? `${hunterName}'s field console is linked to the upper display.` : "Upper display owns primary navigation. Lower display remains linked and ready."}</p>
        {notice && <em>{notice}</em>}
      </main>
      <footer>
        <span><b>＋</b> Navigate</span>
        <span><b>{DEFAULT_CONTROLLER_GLYPHS.confirm}</b> Select</span>
        <span><b>{DEFAULT_CONTROLLER_GLYPHS.back}</b> Back</span>
      </footer>
    </section>
  );
}

export function BottomDisplayApp() {
  useForcedThorDisplays();
  const channelRef = useRef<BroadcastChannel | null>(null);
  const [surface, setSurface] = useState<{ screen: AppScreen; hunterName: string | null; notice: string }>({
    screen: "login",
    hunterName: null,
    notice: "Linking upper display…",
  });

  const postFrontendCommand = useCallback((command: FrontendCommand) => {
    if (isControllerDispatchActive()) return;
    channelRef.current?.postMessage({ type: "frontend-command", command } satisfies DualScreenMessage);
  }, []);

  const launchGame = useCallback((bossIds: readonly BossId[], difficultySlug?: DifficultySlug) => {
    postFrontendCommand({ name: "launchGame", bossIds, difficultySlug });
  }, [postFrontendCommand]);

  useEffect(() => {
    const channel = createDualScreenChannel();
    if (!channel) return;
    channelRef.current = channel;
    const sentControllerIds = new Set<string>();
    let controllerSequence = 0;
    let receivingControllerEcho = false;
    let latestMovement = { moveX: 0, moveY: 0, lookX: 0, lookY: 0 };
    const movementPublisher = createRateLimitedPublisher(() => {
      channel.postMessage({
        type: "controller-motion",
        movement: { ...latestMovement },
      } satisfies DualScreenMessage);
    }, CONTROLLER_MOTION_SYNC_INTERVAL_MS);
    const announceReady = () => channel.postMessage({ type: "companion-ready" } satisfies DualScreenMessage);
    const announceClosing = () => {
      movementPublisher.cancel();
      channel.postMessage({ type: "companion-closing" } satisfies DualScreenMessage);
    };
    const postCommand = (command: GameCommand) => channel.postMessage({ type: "game-command", command } satisfies DualScreenMessage);
    const postFrontend = (command: FrontendCommand) => {
      if (!isControllerDispatchActive()) channel.postMessage({ type: "frontend-command", command } satisfies DualScreenMessage);
    };
    useFrontendStore.setState({
      restoreSession: () => Promise.resolve(false),
      signIn: (username, password) => {
        postFrontend({ name: "signIn", username, password });
        return Promise.resolve(false);
      },
      createAccount: (username, password) => {
        postFrontend({ name: "createAccount", username, password });
        return Promise.resolve(false);
      },
      continueOffline: () => postFrontend({ name: "continueOffline" }),
      signOut: () => postFrontend({ name: "signOut" }),
      navigate: (screen) => postFrontend({ name: "navigate", screen }),
      selectSlot: (slotId) => postFrontend({ name: "selectSlot", slotId }),
      createSlot: (slotId, hunterName) => {
        postFrontend({ name: "createSlot", slotId, hunterName });
        return false;
      },
      playSlot: (slotId) => postFrontend({ name: "playSlot", slotId }),
      deleteSlot: (slotId) => postFrontend({ name: "deleteSlot", slotId }),
      copySlot: (sourceId, targetId) => postFrontend({ name: "copySlot", sourceId, targetId }),
      uploadSlot: (slotId) => {
        postFrontend({ name: "uploadSlot", slotId });
        return Promise.resolve();
      },
      downloadSlot: (slotId) => {
        postFrontend({ name: "downloadSlot", slotId });
        return Promise.resolve();
      },
      selectMode: (mode) => postFrontend({ name: "selectMode", mode }),
      selectBoss: (bossId) => postFrontend({ name: "selectBoss", bossId }),
      selectDifficulty: (difficultySlug) => postFrontend({ name: "selectDifficulty", difficultySlug }),
      selectGearOwner: (ownerId) => postFrontend({ name: "selectGearOwner", ownerId }),
      selectGearSlot: (slotId) => postFrontend({ name: "selectGearSlot", slotId }),
      selectGearWorkshopMode: (mode) => postFrontend({ name: "selectGearWorkshopMode", mode }),
      selectInfusion: (infusionId) => postFrontend({ name: "selectInfusion", infusionId }),
      selectPassiveAbility: (abilityId) => postFrontend({ name: "selectPassiveAbility", abilityId }),
      selectPassiveInfusion: (passiveId) => postFrontend({ name: "selectPassiveInfusion", passiveId }),
      upgradeSelectedGear: () => {
        postFrontend({ name: "upgradeSelectedGear" });
        return false;
      },
      equipSelectedInfusion: () => {
        postFrontend({ name: "equipSelectedInfusion" });
        return false;
      },
      equipPassiveInfusion: (passiveId) => {
        postFrontend({ name: "equipPassiveInfusion", passiveId });
        return false;
      },
      selectHealerClass: (classId) => postFrontend({ name: "selectHealerClass", classId }),
      updateSetting: (key, value) => postFrontend({ name: "updateSetting", key, value }),
    });
    useGameStore.setState({
      startEncounter: () => postCommand({ name: "startEncounter" }),
      restart: () => postCommand({ name: "restart" }),
      castAbility: (abilityId) => {
        postCommand({ name: "castAbility", abilityId });
        return false;
      },
      selectMember: (memberId) => postCommand({ name: "selectMember", memberId }),
      cycleMember: (direction) => postCommand({ name: "cycleMember", direction }),
      setActiveTab: (tab) => postCommand({ name: "setActiveTab", tab }),
      selectItem: (itemId) => postCommand({ name: "selectItem", itemId }),
      setPaused: (paused) => postCommand({ name: "setPaused", paused }),
      setPauseSelection: (selection) => postCommand({ name: "setPauseSelection", selection }),
      setSelectedRunBuff: (buffId) => postCommand({ name: "setSelectedRunBuff", buffId }),
      chooseRunBuff: (buffId) => {
        postCommand({ name: "chooseRunBuff", buffId });
        return false;
      },
      continueRoguelikeRound: () => {
        postCommand({ name: "continueRoguelikeRound" });
        return false;
      },
      startRogueTrialsEndless: () => {
        postCommand({ name: "startRogueTrialsEndless" });
        return false;
      },
      setEndlessChoiceSelection: (selection) => postCommand({ name: "setEndlessChoiceSelection", selection }),
    });
    channel.onmessage = (event: MessageEvent<DualScreenMessage>) => {
      if (event.data.type === "authoritative-ready") {
        channel.postMessage({ type: "companion-ready" } satisfies DualScreenMessage);
        return;
      }
      if (event.data.type === "controller-echo") {
        if (sentControllerIds.delete(event.data.id)) return;
        receivingControllerEcho = true;
        emitControllerToken(event.data.event);
        receivingControllerEcho = false;
        return;
      }
      if (event.data.type === "app-state") {
        const { screen, hunterName, notice, frontend, game } = event.data;
        setSurface((current) => current.screen === screen
          && current.hunterName === hunterName
          && current.notice === notice
          ? current
          : { screen, hunterName, notice });
        if (frontend) useFrontendStore.setState(frontend);
        if (game) useGameStore.setState(game);
      }
    };
    const unsubscribeToken = subscribeControllerToken((event) => {
      if (receivingControllerEcho) return;
      controllerSequence += 1;
      const id = `bottom-${controllerSequence}`;
      sentControllerIds.add(id);
      channel.postMessage({ type: "controller-token", id, event } satisfies DualScreenMessage);
    });
    const unsubscribeMovement = subscribeControllerMovement((movement) => {
      latestMovement = movement;
      movementPublisher.request();
    });
    window.addEventListener("pagehide", announceClosing);
    window.addEventListener("pageshow", announceReady);
    announceReady();
    return () => {
      unsubscribeToken();
      unsubscribeMovement();
      window.removeEventListener("pagehide", announceClosing);
      window.removeEventListener("pageshow", announceReady);
      movementPublisher.dispose();
      announceClosing();
      channelRef.current = null;
      channel.close();
    };
  }, []);

  return (
    <main className="bottom-display-root">
      {surface.screen === "game"
        ? <Suspense fallback={<CompanionStandby screen="game" hunterName={surface.hunterName} notice="Loading field controls…" />}><BottomScreen onExit={() => postFrontendCommand({ name: "exitGame" })} /></Suspense>
        : surface.notice === "Linking upper display…"
          ? <CompanionStandby screen={surface.screen} hunterName={surface.hunterName} notice={surface.notice} />
          : <FrontEnd onLaunch={launchGame} />}
    </main>
  );
}
