'use strict';

var React = require('react');

function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }

var React__default = /*#__PURE__*/_interopDefault(React);

// src/screenReceiver/useScreenReceiver.ts
var DEFAULT_STUN_SERVER = { urls: "stun:stun.l.google.com:19302" };
function parseMessage(payload) {
  try {
    if (typeof payload === "string") return JSON.parse(payload);
    if (payload instanceof ArrayBuffer) return JSON.parse(new TextDecoder().decode(payload));
    return JSON.parse(String(payload));
  } catch {
    return null;
  }
}
function useScreenReceiver(options) {
  const { wsUrl, roomId, iceServers = [DEFAULT_STUN_SERVER], maxLogs = 80 } = options;
  const [logs, setLogs] = React.useState([]);
  const [isConnected, setIsConnected] = React.useState(false);
  const [connectionState, setConnectionState] = React.useState("idle");
  const [iceConnectionState, setIceConnectionState] = React.useState("idle");
  const [stream, setStream] = React.useState(null);
  const logIdRef = React.useRef(0);
  const peerRef = React.useRef({ pendingCandidates: [] });
  const videoRef = React.useRef(null);
  const appendLog = React.useCallback(
    (text) => {
      logIdRef.current += 1;
      setLogs((prev) => [...prev, { id: logIdRef.current, text }].slice(-maxLogs));
    },
    [maxLogs]
  );
  const disconnect = React.useCallback(() => {
    peerRef.current.ws?.close();
    peerRef.current.pc?.close();
    peerRef.current = { pendingCandidates: [] };
    setIsConnected(false);
    setConnectionState("idle");
    setIceConnectionState("idle");
    setStream(null);
  }, []);
  const connect = React.useCallback(async () => {
    disconnect();
    const normalizedWsUrl = wsUrl.trim();
    const normalizedRoomId = roomId.trim();
    if (!normalizedWsUrl) {
      appendLog("ws url is empty");
      return;
    }
    if (!normalizedRoomId) {
      appendLog("room id is empty");
      return;
    }
    appendLog(`connecting: ${normalizedWsUrl}`);
    let ws;
    try {
      ws = new WebSocket(normalizedWsUrl);
    } catch (error) {
      appendLog(`ws create error: ${String(error)}`);
      return;
    }
    const pc = new RTCPeerConnection({ iceServers });
    pc.addTransceiver("video", { direction: "recvonly" });
    peerRef.current.ws = ws;
    peerRef.current.pc = pc;
    peerRef.current.pendingCandidates = [];
    ws.onopen = () => {
      appendLog("ws connected");
      setIsConnected(true);
      ws.send(JSON.stringify({ type: "join", roomId: normalizedRoomId, role: "viewer" }));
    };
    ws.onclose = () => {
      appendLog("ws closed");
      setIsConnected(false);
    };
    ws.onerror = (event) => {
      appendLog(`ws error: ${event.type}`);
    };
    ws.onmessage = async (event) => {
      const message = parseMessage(event.data);
      if (!message) {
        appendLog("ws message parse error");
        return;
      }
      if (message.type === "joined") {
        const joined = message;
        peerRef.current.selfPeerId = joined.peerId;
        appendLog(`joined room ${joined.roomId} as ${joined.peerId}`);
        return;
      }
      if (message.type === "room_state") {
        const roomState = message;
        const broadcaster = roomState.peers.find((peer) => peer.role === "broadcaster");
        if (broadcaster) {
          peerRef.current.publisherPeerId = broadcaster.peerId;
          appendLog(`broadcaster online: ${broadcaster.peerId}`);
        }
        return;
      }
      if (message.type === "offer" && typeof message.sdp === "string") {
        appendLog("offer received");
        peerRef.current.publisherPeerId = typeof message.fromPeerId === "string" ? message.fromPeerId : void 0;
        await pc.setRemoteDescription({ type: "offer", sdp: message.sdp });
        for (const candidate of peerRef.current.pendingCandidates) {
          try {
            await pc.addIceCandidate(candidate);
          } catch (error) {
            appendLog(`ice err: ${String(error)}`);
          }
        }
        peerRef.current.pendingCandidates = [];
        const answer = await pc.createAnswer();
        await pc.setLocalDescription(answer);
        ws.send(
          JSON.stringify({
            type: "answer",
            sdp: answer.sdp,
            targetPeerId: peerRef.current.publisherPeerId
          })
        );
        appendLog("answer sent");
        return;
      }
      if (message.type === "ice" && message.candidate) {
        if (!pc.remoteDescription) {
          peerRef.current.pendingCandidates.push(message.candidate);
          return;
        }
        try {
          await pc.addIceCandidate(message.candidate);
        } catch (error) {
          appendLog(`ice err: ${String(error)}`);
        }
        return;
      }
      if (message.type === "peer_left") {
        const peerLeft = message;
        if (peerLeft.peerId === peerRef.current.publisherPeerId) {
          peerRef.current.publisherPeerId = void 0;
          appendLog(`publisher left: ${peerLeft.peerId}`);
        }
        return;
      }
      if (message.type === "error") {
        appendLog(`server error: ${String(message.reason ?? "unknown")}`);
      }
    };
    pc.ontrack = (event) => {
      const receivedStream = event.streams[0];
      if (receivedStream) {
        setStream(receivedStream);
        appendLog("track received");
      }
    };
    pc.onicecandidate = (event) => {
      if (!event.candidate) return;
      ws.send(
        JSON.stringify({
          type: "ice",
          candidate: event.candidate,
          targetPeerId: peerRef.current.publisherPeerId
        })
      );
    };
    pc.onconnectionstatechange = () => {
      const state = pc.connectionState || "new";
      setConnectionState(state);
      appendLog(`pc state: ${state}`);
    };
    pc.oniceconnectionstatechange = () => {
      const state = pc.iceConnectionState || "new";
      setIceConnectionState(state);
      appendLog(`ice state: ${state}`);
    };
  }, [appendLog, disconnect, iceServers, roomId, wsUrl]);
  React.useEffect(() => () => disconnect(), [disconnect]);
  React.useEffect(() => {
    if (!videoRef.current) return;
    videoRef.current.srcObject = stream;
  }, [stream]);
  const clearLogs = React.useCallback(() => setLogs([]), []);
  return React.useMemo(
    () => ({
      connect,
      disconnect,
      clearLogs,
      logs,
      isConnected,
      connectionState,
      iceConnectionState,
      videoRef,
      stream
    }),
    [clearLogs, connect, connectionState, disconnect, iceConnectionState, isConnected, logs, stream]
  );
}

// src/screenReceiver/signalUrl.ts
var DEFAULT_PORT = 8787;
var DEFAULT_PATH = "/ws";
function resolveScreenReceiverSignalUrl(options = {}) {
  const { signalUrl, path = DEFAULT_PATH, port = DEFAULT_PORT } = options;
  if (signalUrl && signalUrl.trim()) return signalUrl.trim();
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
  if (typeof window !== "undefined") {
    const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
    return `${protocol}//${window.location.host}${normalizedPath}`;
  }
  return `ws://127.0.0.1:${port}${normalizedPath}`;
}
var DEFAULT_ROOM_ID = "screen-room-1";
function ScreenReceiverPanel(props) {
  const { defaultSignalUrl, defaultRoomId = DEFAULT_ROOM_ID, className } = props;
  const initialSignalUrl = React.useMemo(
    () => resolveScreenReceiverSignalUrl({ signalUrl: defaultSignalUrl }),
    [defaultSignalUrl]
  );
  const [wsUrl, setWsUrl] = React.useState(initialSignalUrl);
  const [roomId, setRoomId] = React.useState(defaultRoomId);
  const receiver = useScreenReceiver({ wsUrl, roomId });
  const logs = React.useMemo(() => receiver.logs.map((entry) => entry.text).join("\n"), [receiver.logs]);
  return /* @__PURE__ */ React__default.default.createElement("div", { className }, /* @__PURE__ */ React__default.default.createElement("div", { className: "flex flex-col gap-4" }, /* @__PURE__ */ React__default.default.createElement("div", { className: "grid gap-3 md:grid-cols-[2fr,1fr,auto]" }, /* @__PURE__ */ React__default.default.createElement("label", { className: "flex flex-col gap-1 text-sm" }, /* @__PURE__ */ React__default.default.createElement("span", null, "Signaling WS"), /* @__PURE__ */ React__default.default.createElement(
    "input",
    {
      className: "rounded-md border px-3 py-2",
      value: wsUrl,
      onChange: (event) => setWsUrl(event.target.value)
    }
  )), /* @__PURE__ */ React__default.default.createElement("label", { className: "flex flex-col gap-1 text-sm" }, /* @__PURE__ */ React__default.default.createElement("span", null, "Room"), /* @__PURE__ */ React__default.default.createElement(
    "input",
    {
      className: "rounded-md border px-3 py-2",
      value: roomId,
      onChange: (event) => setRoomId(event.target.value)
    }
  )), /* @__PURE__ */ React__default.default.createElement("div", { className: "flex items-end gap-2" }, /* @__PURE__ */ React__default.default.createElement(
    "button",
    {
      onClick: () => void receiver.connect(),
      className: "rounded-md border bg-black px-4 py-2 text-sm text-white"
    },
    receiver.isConnected ? "Reconnect" : "Connect"
  ), /* @__PURE__ */ React__default.default.createElement(
    "button",
    {
      onClick: receiver.disconnect,
      className: "rounded-md border px-4 py-2 text-sm"
    },
    "Disconnect"
  ))), /* @__PURE__ */ React__default.default.createElement("div", { className: "grid gap-3 md:grid-cols-3" }, /* @__PURE__ */ React__default.default.createElement(StatusItem, { label: "WebSocket", value: receiver.isConnected ? "connected" : "idle" }), /* @__PURE__ */ React__default.default.createElement(StatusItem, { label: "PeerConnection", value: receiver.connectionState }), /* @__PURE__ */ React__default.default.createElement(StatusItem, { label: "ICE State", value: receiver.iceConnectionState })), /* @__PURE__ */ React__default.default.createElement("div", { className: "grid gap-4 lg:grid-cols-[3fr,2fr]" }, /* @__PURE__ */ React__default.default.createElement("div", { className: "rounded-xl border bg-black p-3" }, /* @__PURE__ */ React__default.default.createElement(
    "video",
    {
      ref: receiver.videoRef,
      autoPlay: true,
      playsInline: true,
      controls: true,
      muted: true,
      className: "h-[360px] w-full rounded-lg bg-black"
    }
  )), /* @__PURE__ */ React__default.default.createElement("div", { className: "rounded-xl border p-3" }, /* @__PURE__ */ React__default.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React__default.default.createElement("p", { className: "text-sm font-semibold" }, "Session Log"), /* @__PURE__ */ React__default.default.createElement(
    "button",
    {
      onClick: receiver.clearLogs,
      className: "text-xs underline-offset-2 hover:underline"
    },
    "Clear"
  )), /* @__PURE__ */ React__default.default.createElement("pre", { className: "mt-3 h-[320px] overflow-auto rounded-lg border bg-slate-50 p-3 text-xs" }, logs || "No logs yet.")))));
}
function StatusItem({ label, value }) {
  return /* @__PURE__ */ React__default.default.createElement("div", { className: "rounded-md border px-3 py-2" }, /* @__PURE__ */ React__default.default.createElement("p", { className: "text-xs text-slate-500" }, label), /* @__PURE__ */ React__default.default.createElement("p", { className: "text-sm font-medium" }, value));
}

exports.ScreenReceiverPanel = ScreenReceiverPanel;
exports.resolveScreenReceiverSignalUrl = resolveScreenReceiverSignalUrl;
exports.useScreenReceiver = useScreenReceiver;
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map