import CBOR from "cbor";
import { useEffect, useState } from "react";
import useWebSocket, { ReadyState } from "react-use-websocket";
import * as Automerge from "@automerge/automerge";
import * as uuid from "uuid";
import {} from "libsodium";
import Upload from "./Upload";

const connectionStatusMap = {
  [ReadyState.CONNECTING]: "Connecting",
  [ReadyState.OPEN]: "Open",
  [ReadyState.CLOSING]: "Closing",
  [ReadyState.CLOSED]: "Closed",
  [ReadyState.UNINSTANTIATED]: "Uninstantiated",
};

export default function Room({ roomId }) {
  const [readyState, setReadyState] = useState(ReadyState.CLOSED);
  const [connectedClients, setConnectedClients] = useState([]);
  const [clientId, setClientId] = useState<string | null>(null);
  const [chats, setChats] = useState([]);

  const [doc, setDoc] = useState(Automerge.init());
  const [syncState, setSyncState] = useState(Automerge.initSyncState());

  const [message, setMessage] = useState("");
  const [addItemName, setAddItemName] = useState("");

  function updateDoc(newDoc) {
    setDoc(newDoc);
  }

  const {
    sendMessage,
    lastJsonMessage,
    readyState: newReadyState,
  } = useWebSocket("ws://localhost:3100/ws", {
    share: true,
    onOpen: ({}) => {
      console.log("Shiet, connected.");
    },
    onMessage: async (event) => {
      const data = CBOR.decode(await event.data.arrayBuffer());
      console.log("received", data);

      if (data.type === "ServerHello") {
        setClientId(uuid.stringify(data.client_id));
      }

      if (data.type === "RoomClientList") {
        setConnectedClients(data.clients.map((x) => uuid.stringify(x)));
      }

      if (data.type === "ChatMessage") {
        setChats([...chats, data]);
      }

      if (data.type === "Automerge") {
        const [nextDoc, nextSyncState, patch] = Automerge.receiveSyncMessage(
          doc,
          syncState,
          data.message[1]
        );
        setDoc(nextDoc);
        setSyncState(nextSyncState);
        console.log("patch", patch);
      }
    },
  });

  function sendWtfMessage(data) {
    let cbor = CBOR.encode(data);
    console.log(
      "cbor-encoded",
      [...new Uint8Array(cbor)]
        .map((x) => x.toString(16).padStart(2, "0"))
        .join(" ")
    );
    sendMessage(cbor);
  }

  useEffect(() => {
    console.log("hellosu", readyState, newReadyState);
    if (
      readyState === ReadyState.CONNECTING &&
      newReadyState === ReadyState.OPEN
    ) {
      // On Open
      sendWtfMessage({ type: "JoinRoom", room_id: roomId });
      console.log("Sent connection message");
    }
    setReadyState(newReadyState);
  }, [newReadyState]);

  function onSubmit(e) {
    e.preventDefault();
    sendWtfMessage({
      type: "ChatMessage",
      timestamp: new Date().toISOString(),
      message_id: uuid.v4(),
      room_id: roomId,
      author: clientId,
      content: message,
    });
    setMessage("");
  }

  function addItem(e) {
    e.preventDefault();
    const newDoc = Automerge.change(doc, (doc) => {
      if (!doc.items) doc.items = [];
      doc.items.push({
        id: uuid.v4(),
        content: addItemName,
      });
    });
    updateDoc(newDoc);
    const [syncMessage, binary] = Automerge.generateSyncMessage(doc, syncState);
    if (syncMessage)
      sendWtfMessage({
        type: "Automerge",
        client_id: clientId,
        room_id: roomId,
        message: binary,
      });
    setAddItemName("");
  }

  const items = doc.items || [];
  const connectionStatus = connectionStatusMap[readyState];

  if (newReadyState !== ReadyState.OPEN) return <>Connecting...</>;

  return (
    <>
      <Upload roomId={roomId} />
      <hr />
      <p>Room Id: {roomId}</p>
      <p>Connection status: {connectionStatus}</p>
      Connected:
      <ul>
        {connectedClients.map((x) => (
          <li key={x}>{x}</li>
        ))}
      </ul>
      Messages:
      <ul>
        {chats.map((x) => (
          <li key={x.message_id}>
            [{x.timestamp}] {uuid.stringify(x.author)}: {x.content}
          </li>
        ))}
      </ul>
      <form onSubmit={onSubmit}>
        <input
          type="text"
          value={message}
          onChange={(e) => setMessage(e.target.value)}
          placeholder="Type a message..."
        />
      </form>
      Grubs:
      <ul>
        {items.map((x) => (
          <li key={x.id}>{x.content}</li>
        ))}
      </ul>
      <form onSubmit={addItem}>
        <input
          type="text"
          value={addItemName}
          onChange={(e) => setAddItemName(e.target.value)}
          placeholder="Type a message..."
        />
        <button type="submit">add item</button>
      </form>
    </>
  );
}
