import { Signal } from "@char/aftercare";
import { ChatResponse } from "./response.tsx";

const route = new Signal<string | undefined>(window.location.hash.substring(1) || undefined);
let lastRoute: string | undefined = "";

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();
      route.set(conversation.id);
    });

    nav.append(button);
  }

  nav.append(
    <button
      type="button"
      _tap={b =>
        b.addEventListener("click", e => {
          e.preventDefault();
          route.set("new");
        })
      }
    >
      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, ChatResponse>();

  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);
      id = message.i;
    } else if ("u" in message) {
      chatlog.append(<article className="user">{message.u}</article>);
    } else if ("f" in message) {
      const response = new ChatResponse();
      response.append(message.f);
      response.finalize();
      chatlog.append(response.element);
    } else if ("s" in message) {
      const response = new ChatResponse();
      inFlightMessages.set(message.s, response);
      chatlog.append(response.element);
    } else if ("r" in message && "c" in message) {
      const article = inFlightMessages.get(message.r)!;
      article.append(message.c);
    } else if ("d" in message) {
      const response = inFlightMessages.get(message.d);
      if (response) response.finalize();
      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>
  );
}

window.addEventListener("hashchange", () => {
  route.set(window.location.hash.substring(1));
});
route.subscribeImmediate(async r => {
  if (r === lastRoute) return;

  main.innerHTML = "";

  if (r) {
    main.append(conversationUI(r));
  } else {
    main.append(await nav());
  }

  lastRoute = r;
});
