import { Signal } from "@char/aftercare";

const main = document.querySelector("main")!;

async function nav() {
  const nav = <nav />;

  const conversations = await fetch("/api/conversation").then(r => r.json());
  for (const conversation of conversations) {
    const button = <button type="button">{conversation.name}</button>;
    button.addEventListener("click", e => {
      e.preventDefault();

      main.append(conversationUI(conversation.id, conversation.name));
      nav.remove();
    });

    nav.append(button);
  }

  nav.append(
    <button
      type="button"
      _tap={b =>
        b.addEventListener("click", e => {
          e.preventDefault();
          main.append(conversationUI("new", "New conversation"));
          nav.remove();
        })
      }
    >
      new
    </button>,
  );

  return nav;
}

function conversationUI(id: string) {
  window.location.hash = `#${id}`;

  let socket: WebSocket;
  let connected = false;
  const connect = () => {
    socket = new WebSocket(
      `/api/conversation/${id}/connect` + (connected ? "?continue=1" : ""),
    );
    socket.addEventListener("open", () => (connected = true));
    socket.addEventListener("close", () => (socket = connect()));
    socket.addEventListener("error", ev => {
      console.warn(ev);
      // TODO: handle errors
    });
    return socket;
  };
  socket = connect();

  const header = (
    <header>
      <h1 />
      <button tabIndex={-1} type="button" className="danger">
        delete
      </button>
    </header>
  );
  const name = new Signal("");
  name.subscribeImmediate(it => (header.querySelector("h1")!.textContent = it));
  header.querySelector("button")!.addEventListener("click", async e => {
    e.preventDefault();
    await fetch(`/api/conversation/${id}`, { method: "DELETE" });
    window.location.hash = "";
  });

  const chatlog = <section className="chatlog" />;
  const inFlightMessages = new Map<string, Element>();

  socket.addEventListener("message", event => {
    if (typeof event.data !== "string") return;
    const message = JSON.parse(event.data);

    const scrolledToBottom =
      chatlog.scrollTop + 16 >= chatlog.scrollHeight - chatlog.clientHeight;

    if ("i" in message) {
      window.history.replaceState(null, "", "#" + message.i);
    } else if ("u" in message) {
      chatlog.append(<article className="user">{message.u}</article>);
    } else if ("f" in message) {
      chatlog.append(<article className="assistant">{message.f}</article>);
    } else if ("s" in message) {
      const article = <article className="assistant" />;
      inFlightMessages.set(message.s, article);
      chatlog.append(article);
    } else if ("r" in message && "c" in message) {
      const article = inFlightMessages.get(message.r)!;
      article.append(message.c);
    } else if ("d" in message) {
      inFlightMessages.delete(message.d);
    } else if ("n" in message) {
      name.set(message.n);
    }

    if (scrolledToBottom) chatlog.scrollTop = chatlog.scrollHeight - chatlog.clientHeight;
  });

  const form = (
    <form>
      <input type="text" placeholder="Enter a prompt…" required />
    </form>
  );
  const input = form.querySelector("input")!;
  form.addEventListener("submit", e => {
    e.preventDefault();
    socket.send(input.value);
    input.value = "";
  });

  return (
    <section className="conversation">
      {header}
      {chatlog}
      {form}
    </section>
  );
}

const showUI = async () => {
  main.innerHTML = "";

  if (window.location.hash) {
    main.append(conversationUI(window.location.hash.substring(1)));
  } else {
    main.append(await nav());
  }
};

await showUI();
window.addEventListener("hashchange", async () => {
  await showUI();
});
