import { Sockpuppet } from "puppet/client";
import { acceptEULA, checkEULA } from "../util/EULA.ts";
import { Loader } from "../types/mcgrizzconf.ts";
import { getConfFile, updateConfFile } from "../util/confFile.ts";
import { IS_BROWSER } from "$fresh/runtime.ts";
import { getServerStartCommands } from "../serverConfigs/start.ts";

type MCServerEvent = "message";
type MCServerEventCallback = (msg: string) => void;

type status = "running" | "stopped";

class ServerState {
  private _status: status = "stopped";
  public get status() {
    return this._status;
  }
  private set status(s: status) {
    this._status = s;

    const statusEvent = new CustomEvent("serverstatuschange", {
      detail: this._status,
    });
    globalThis.dispatchEvent(statusEvent);
  }

  private command!: Deno.Command;
  private process!: Deno.ChildProcess;

  private _eulaAccepted = false;

  private sockpuppet!: Sockpuppet;
  private _channelId;
  public get channelId() {
    return this._channelId;
  }
  public get eulaAccepted() {
    return this._eulaAccepted;
  }
  private stdin!: WritableStreamDefaultWriter;

  private _serverType: Loader = "unset";
  public get serverType(): Loader {
    return this._serverType;
  }
  public set serverType(loader: Loader) {
    updateConfFile({ loader });
    this._serverType = loader;
  }

  private _serverVersion: string;
  public get serverVersion(): string {
    return this._serverVersion;
  }
  public set serverVersion(version: string) {
    updateConfFile({ version });
    this._serverVersion = version;
  }

  constructor() {
    this._channelId = crypto.randomUUID ? crypto.randomUUID() : "";

    const conf = getConfFile();
    this._serverType = conf.loader;
    this._serverVersion = conf.version;

    this._eulaAccepted = checkEULA();

    this.sockpuppet = new Sockpuppet(
      "ws://sockpuppet.cyborggrizzly.com",
      () => {
        this.sockpuppet.createChannel(this._channelId);
        this.sockpuppet.joinChannel(this.channelId, async (msg) => {
          if (msg === "log" && !IS_BROWSER) {
            try {
              const log = await Deno.readTextFile("./server/logs/latest.log");
              this.channel?.send(log);
            } catch {
              1;
            }
          } else {
            console.log(msg);
            this.sendStdIn(msg);
          }
        });
      },
    );
  }

  public get channel() {
    return this.sockpuppet.getChannel(this.channelId);
  }

  public async sendStdIn(message: string) {
    if (IS_BROWSER || !this.stdin) return;

    const msg = new TextEncoder().encode(message + "\n");

    await this.stdin.write(msg);
  }

  // "instance" should be moved to a private member once multi-instance support is implemented
  public async startMCServer(instance = "server") {
    this.command = getServerStartCommands(this._serverType, instance);

    this.channel?.send("clear");

    this.process = this.command.spawn();

    const { readable, writable } = new TransformStream();
    readable.pipeTo(this.process.stdin);
    this.stdin = writable.getWriter();

    this.status = "running";

    this.startStream();
    await this.process.status;
    this.status = "stopped";
  }

  private async startStream() {
    // const stream = this.process.stdout.getReader();
    // const decoder = new TextDecoder();

    // while (true) {
    //   const { done, value } = await stream.read();
    //   if (value) {
    //     const line = decoder.decode(value);
    //     this.channel?.send(line);
    //     const stdoutMsg = new CustomEvent("stdoutmsg", {
    //       detail: line,
    //     });
    //     globalThis.dispatchEvent(stdoutMsg);
    //   }
    //   if (done) break;
    // }

    await this.process.stdout.pipeThrough(new TextDecoderStream()).pipeTo(
      new WritableStream({
        write: (chunk) => {
          this.channel?.send(chunk);
          const stdoutMsg = new CustomEvent("stdoutmsg", {
            detail: chunk,
          });
          globalThis.dispatchEvent(stdoutMsg);
        },
      }),
    );
  }

  public gracefullyStopMCServer() {
    this.status = "stopped";
    this.sendStdIn("stop");
  }

  public forceStopMCServer() {
    this.status = "stopped";
    this.process.kill();
  }

  public async restartMCServer() {
    if (this.status === "running") {
      await this.sendStdIn("stop");
      const statusEvent = new CustomEvent("serverstatuschange", {
        detail: "restarting",
      });

      globalThis.dispatchEvent(statusEvent);

      // while (true) {
      await this.process.status;
      // }
    }

    this.startMCServer();
  }

  public acceptEULA() {
    this._eulaAccepted = true;
    acceptEULA();
  }
}

export const SERVER_STATE = new ServerState();
