import { createSignal, onCleanup, onMount, Show } from "solid-js";
import { Game, type DebugSnapshot, type HudSnapshot } from "../engine/Game.js";
import { ApiClient } from "../net/ApiClient.js";
import { NetSession } from "../net/NetSession.js";
import { START_ROOM, START_SPAWN } from "../world/rooms/index.js";
import { DebugPanel } from "./DebugPanel.jsx";
import { InventoryPanel } from "./InventoryPanel.jsx";
import { LobbyScreen } from "./LobbyScreen.jsx";
import { Prompt } from "./Prompt.jsx";
import "./ui.css";

/** Derives the signalling endpoint from the API origin. */
function signalingUrlFrom(apiBaseUrl: string): string {
  return `${apiBaseUrl.replace(/^http/, "ws")}/ws`;
}

export default function App() {
  let canvas!: HTMLCanvasElement;
  let game: Game | null = null;

  const [ready, setReady] = createSignal(false);
  const [error, setError] = createSignal<string | null>(null);
  const [hud, setHud] = createSignal<HudSnapshot | null>(null);
  const [debug, setDebug] = createSignal<DebugSnapshot | null>(null);
  const [inventoryOpen, setInventoryOpen] = createSignal(false);
  const [debugOpen, setDebugOpen] = createSignal(false);
  const [lobbyOpen, setLobbyOpen] = createSignal(false);
  const [netNotice, setNetNotice] = createSignal<string | null>(null);

  const api = new ApiClient();

  /**
   * Builds the session and hands it to the running Game. The world is already
   * simulating, so joining co-op is additive — remote players just appear.
   */
  const connectSession = async (lobbyCode: string): Promise<NetSession> => {
    const instance = game;
    if (!instance) throw new Error("game is not running yet");

    const net = new NetSession(signalingUrlFrom(api.baseUrl));
    net.onClientMessage = (message, from) => instance.handleClientMessage(message, from);
    net.onHostMessage = (message) => instance.handleHostMessage(message);
    net.onFatal = (message) => setNetNotice(message);

    const token = api.sessionToken;
    if (!token) throw new Error("sign in first");

    await net.connect(token, lobbyCode);
    instance.attachNet(net);
    return net;
  };

  onMount(async () => {
    const instance = new Game(canvas);
    game = instance;
    instance.onHud = setHud;
    instance.onDebug = setDebug;

    try {
      await instance.start(START_ROOM, START_SPAWN);
      setReady(true);
    } catch (cause) {
      // Most likely a WebGL2 or wasm failure. Surface it rather than leaving a
      // black canvas with no explanation.
      setError(cause instanceof Error ? cause.message : String(cause));
    }
  });

  const onKeyDown = (event: KeyboardEvent) => {
    switch (event.code) {
      case "Tab":
        event.preventDefault();
        setInventoryOpen((open) => !open);
        break;
      case "Backquote":
        setDebugOpen((open) => !open);
        break;
      case "Escape":
        setInventoryOpen(false);
        setLobbyOpen(false);
        break;
      case "KeyM":
        setLobbyOpen((open) => !open);
        break;
      case "F5":
        if (event.shiftKey) {
          event.preventDefault();
          game?.saveToLocalStorage();
        }
        break;
    }
  };

  window.addEventListener("keydown", onKeyDown);

  onCleanup(() => {
    window.removeEventListener("keydown", onKeyDown);
    game?.dispose();
    game = null;
  });

  return (
    <div class="viewport">
      <canvas ref={canvas} class="game-canvas" />

      <Show when={!ready() && !error()}>
        <div class="overlay overlay--center">
          <p class="loading">Loading physics core…</p>
        </div>
      </Show>

      <Show when={error()}>
        {(message) => (
          <div class="overlay overlay--center">
            <div class="panel panel--error">
              <h2>Failed to start</h2>
              <p>{message()}</p>
            </div>
          </div>
        )}
      </Show>

      <Show when={ready()}>
        <div class="hud">
          <Prompt prompt={hud()?.prompt ?? null} message={hud()?.message ?? null} />

          <div class="hud__corner">
            <span class="hud__scheme">
              {hud()?.controlScheme === "tank" ? "TANK" : "CAMERA-RELATIVE"}
            </span>
            <span class="hud__hint">
              C switch · TAB inventory · M co-op · ` debug · SHIFT+F5 save
            </span>
          </div>

          <Show when={netNotice()}>
            {(notice) => <div class="hud__net-notice">{notice()}</div>}
          </Show>

          <Show when={lobbyOpen()}>
            <LobbyScreen
              api={api}
              onConnect={connectSession}
              onClose={() => setLobbyOpen(false)}
            />
          </Show>

          <Show when={inventoryOpen()}>
            <InventoryPanel
              items={hud()?.items ?? []}
              capacity={hud()?.capacity ?? 0}
              usedSlots={hud()?.usedSlots ?? 0}
              onCombine={(a, b) => game?.combineItems(a, b) ?? "Nothing happens."}
              onClose={() => setInventoryOpen(false)}
            />
          </Show>

          <Show when={debugOpen()}>
            <DebugPanel debug={debug()} />
          </Show>
        </div>
      </Show>
    </div>
  );
}
