import { message, user } from "@localtypes";

let passwd = "";
let token = localStorage.getItem("token") ?? "";

while (!(passwd && passwd !== "") && !token) {
  passwd = prompt("input passwd")!;
}

export let ws = new WebSocket("ws://localhost/ws");

let messages: message[] = [];
let self: user;
let users: user[] = [];
let queue: (message | null)[] = new Array(6).fill(null);
const listenersMessages = new Set<() => void>();
const listenersUsers = new Set<() => void>();
let visibleMessages: (message | null)[] = [];
let dirty = true;

function rebuildVisibleMessages() {
  if (!dirty) return;

  visibleMessages = [...messages, ...queue];

  dirty = false;
}

ws.onopen = () => {
  console.log("opened websocket!");
  if (!token) {
    ws.send(JSON.stringify({
      op: OpcodesServerbound.identify,
      data: {
        password: passwd,
      }
    }))
  } else {
    ws.send(JSON.stringify({
      op: OpcodesServerbound.identify,
      data: {
        token,
      }
    }))
  }
}

ws.onmessage = message => {
  const msg = message.data;
  parseMessages(msg);
}

const onclose = () => {
  console.log("ws disconnected. reconnecting");
  ws = new WebSocket("ws://localhost/ws");
  ws.onopen = () => {
    console.log("opened websocket!");
    if (!token) {
      ws.send(JSON.stringify({
        op: OpcodesServerbound.identify,
        data: {
          password: passwd,
        }
      }))
    } else {
      ws.send(JSON.stringify({
        op: OpcodesServerbound.identify,
        data: {
          token,
        }
      }))
    }
  }

  ws.onmessage = message => {
    const msg = message.data;
    parseMessages(msg);
  }

  ws.onclose = onclose;
}

ws.onclose = onclose;

function notifyMessages() {
  listenersMessages.forEach((l) => l());
}

function notifyUsers() {
  listenersUsers.forEach((l) => l());
}

enum OpcodesClientbound {
  authorizeLogin,
  initMessages,
  updateMessages,
  registerSuccess,
  error,
  users,
  keepAlive = 9,
}

enum OpcodesServerbound {
  identify,
  sendMessage,
  register,
  getUsers,
  keepAlive = 9,
}

function parseMessages(message: string) {
  const json: {op: number, data: any} = JSON.parse(message) as {op: number, data: any};
  console.log(json);
  switch (json.op) {
    case OpcodesClientbound.authorizeLogin:
      token = json.data.token;
      localStorage.setItem("token", token!);
      self = json.data;
      self.token = null;
      self.password = null;
      ws.send(JSON.stringify({
        op: OpcodesServerbound.getUsers,
        data: null
      }))
      break;
    case OpcodesClientbound.initMessages:
      messages.push(...json.data);
      dirty = true;
      notifyMessages();
      break;
    case OpcodesClientbound.users:
      users = json.data;
      console.log(users);
      notifyUsers();
      break;
    case OpcodesClientbound.keepAlive:
      ws.send(JSON.stringify({
        op: OpcodesServerbound.keepAlive,
        data: null,
      }))
      break;
    case OpcodesClientbound.updateMessages:
      messages.push(json.data);
      if (json.data.author.id === self.id) {
        console.log(queue);
        queue.reverse();
        queue.pop();
        queue.reverse();
        queue.push(null as never);
        console.log(queue);
      }
      dirty = true;
      notifyMessages();
      break;
    default:
      break;
  }
}

export function getMessages(): (message | null)[] {
  rebuildVisibleMessages();
  return visibleMessages;
}
export function getUsers(): user[] {
  return users;
}

export function sendChatMessage(message: message & { token?: string }) {
  if (queue.filter(m => m !== null).length < 6) {
    console.log(message);
    for (let i = 0; i < queue.length; i++) {
      if (!queue[i]) {
        queue[i] = message;
        i = 6;
      }
    }
    dirty = true;
    notifyMessages();
    ws.send(JSON.stringify({op: OpcodesServerbound.sendMessage, data: message}));
  }
}

export function subscribeMessages(listener: () => void) {
  listenersMessages.add(listener);
  return () => listenersMessages.delete(listener);
}
export function subscribeUsers(listener: () => void) {
  listenersUsers.add(listener);
  return () => listenersUsers.delete(listener);
}

export { token, self };