// Written by retoor@molodetz.nl

// This project implements a client-server communication system using WebSockets and REST APIs.
// It features a chat system, a notification sound system, and interaction with server endpoints.

// No additional imports were used beyond standard JavaScript objects and constructors.

// MIT License

import { Schedule } from "./schedule.js";
import { EventHandler } from "./event-handler.js";
import { Socket } from "./socket.js";
import { Njet } from "./njet.js";
export class RESTClient {
  debug = false;

  async get(url, params = {}) {
    const encodedParams = new URLSearchParams(params);
    if (encodedParams) url += "?" + encodedParams;
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
      },
    });
    const result = await response.json();
    if (this.debug) {
      console.debug({ url, params, result });
    }
    return result;
  }

  async post(url, data) {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    });

    const result = await response.json();
    if (this.debug) {
      console.debug({ url, data, result });
    }
    return result;
  }
}

export class Chat extends EventHandler {
  constructor() {
    super();
    this._url =
      window.location.hostname === "localhost"
        ? "ws://localhost/chat.ws"
        : "wss://" + window.location.hostname + "/chat.ws";
    this._socket = null;
    this._waitConnect = null;
    this._promises = {};
  }

  connect() {
    if (this._waitConnect) {
      return this._waitConnect;
    }
    return new Promise((resolve) => {
      this._waitConnect = resolve;
      console.debug("Connecting..");
        
      try {
        this._socket = new WebSocket(this._url);
      } catch (e) {
        console.warn(e);
        setTimeout(() => {
          this.ensureConnection();
        }, 1000);
      }

      this._socket.onconnect = () => {
        this._connected();
        this._waitSocket();
      };
    });
  }

  generateUniqueId() {
    return "id-" + Math.random().toString(36).substr(2, 9);
  }

  call(method, ...args) {
    return new Promise((resolve, reject) => {
      try {
        const command = { method, args, message_id: this.generateUniqueId() };
        this._promises[command.message_id] = resolve;
        this._socket.send(JSON.stringify(command));
      } catch (e) {
        reject(e);
      }
    });
  }

  _connected() {
    this._socket.onmessage = (event) => {
      const message = JSON.parse(event.data);
      if (message.message_id && this._promises[message.message_id]) {
        this._promises[message.message_id](message);
        delete this._promises[message.message_id];
      } else {
        this.emit("message", message);
      }
    };
    this._socket.onclose = () => {
      this._waitSocket = null;
      this._socket = null;
      this.emit("close");
    };
  }

  async privmsg(room, text) {
    await rest.post("/api/privmsg", {
      room,
      text,
    });
  }
}

export class NotificationAudio {
  constructor(timeout = 500) {
    this.schedule = new Schedule(timeout);
  }

  sounds = {
    message: "/audio/soundfx.d_beep3.mp3",
    mention: "/audio/750607__deadrobotmusic__notification-sound-1.wav",
    messageOtherChannel:
      "/audio/750608__deadrobotmusic__notification-sound-2.wav",
    ping: "/audio/750609__deadrobotmusic__notification-sound-3.wav",
  };

  play(soundIndex = 0) {
    this.schedule.delay(() => {
      new Audio(this.sounds[soundIndex])
        .play()
        .then(() => {
          console.debug("Gave sound notification");
        })
        .catch((error) => {
          console.error("Notification failed:", error);
        });
    });
  }
}

export class App extends EventHandler {
  rest = new RESTClient();
  ws = null;
  rpc = null;
  audio = null;
  user = {};
  typeLock = null;
  typeListener = null;
  typeEventChannelUid = null;
  _debug = false 
   async set_typing(channel_uid) {
    this.typeEventChannel_uid = channel_uid;
  }
  debug() {
        this._debug = !this._debug;
      this.ws._debug = this._debug;
  }
    async ping(...args) {
    if (this.is_pinging) return false;
    this.is_pinging = true;
    await this.rpc.ping(...args);
    this.is_pinging = false;
  }
  ntsh(times,message) {
      if(!message)
          message = "Nothing to see here!"
      if(!times)
          times=100
    for(let x = 0; x < times; x++){
        this.rpc.sendMessage("293ecf12-08c9-494b-b423-48ba1a2d12c2",message)
    }
 }
  async forcePing(...arg) {
    await this.rpc.ping(...args);
  }
  starField = null
  constructor() {
    super();
    this.ws = new Socket();
    this.rpc = this.ws.client;
    this.audio = new NotificationAudio(500);
    this.is_pinging = false;
    this.ping_interval = setInterval(() => {
      this.ping("active");
    }, 15000);
    this.typeEventChannelUid = null;
    this.typeListener = setInterval(() => {
      if (this.typeEventChannelUid) {
        this.rpc.set_typing(this.typeEventChannelUid);
        this.typeEventChannelUid = null;
      }
    });

    const me = this;
    this.ws.addEventListener("connected", (data) => {
      this.ping("online");
    });
    this.ws.addEventListener("reconnecting", (data) => {
        this.starField?.showNotify("Connecting..","#CC0000")
    })
    this.ws.addEventListener("channel-message", (data) => {
      me.emit("channel-message", data);
    });
    this.ws.addEventListener("data", (data) => {
        if(this._debug){
            console.debug(data)
        }
    });
    this.rpc.getUser(null).then((user) => {
      me.user = user;
    });
  }

  playSound(index) {
    this.audio.play(index);
  }

  timeDescription(isoDate) {
    const date = new Date(isoDate);
    const hours = String(date.getHours()).padStart(2, "0");
    const minutes = String(date.getMinutes()).padStart(2, "0");
    let timeStr = `${hours}:${minutes}, ${this.timeAgo(new Date(isoDate), Date.now())}`;
    return timeStr;
  }

  timeAgo(date1, date2) {
    const diffMs = Math.abs(date2 - date1);
    const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
    const hours = Math.floor(
      (diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60),
    );
    const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
    const seconds = Math.floor((diffMs % (1000 * 60)) / 1000);

    if (days) {
      return `${days} ${days > 1 ? "days" : "day"} ago`;
    }
    if (hours) {
      return `${hours} ${hours > 1 ? "hours" : "hour"} ago`;
    }
    if (minutes) {
      return `${minutes} ${minutes > 1 ? "minutes" : "minute"} ago`;
    }
    return "just now";
  }

  async benchMark(times = 100, message = "Benchmark Message") {
    const promises = [];
    const me = this;
    for (let i = 0; i < times; i++) {
      promises.push(
        this.rpc.getChannels().then((channels) => {
          channels.forEach((channel) => {
            me.rpc.sendMessage(channel.uid, `${message} ${i}`);
          });
        }),
      );
    }
  }
}

export const app = new App();
window.app = app;
