import { IDisposable, Terminal as XtermTerminal } from "@xterm/xterm";
import "@xterm/xterm/css/xterm.css";
import React, { useEffect, useRef } from "react";
import { VscTerminal } from "react-icons/vsc";
import { useSelector } from "react-redux";
import { FitAddon } from "xterm-addon-fit";
import Socket from "../services/socket";
import { RootState } from "../store";
import ActionType from "../types/ActionType";
import ObservationType from "../types/ObservationType";

class JsonWebsocketAddon {
  _disposables: IDisposable[];

  constructor() {
    this._disposables = [];
  }

  activate(terminal: XtermTerminal) {
    this._disposables.push(
      terminal.onData((data) => {
        const payload = JSON.stringify({ action: "terminal", data });
        Socket.send(payload);
      }),
    );
    Socket.addEventListener("message", (event) => {
      const { action, args, observation, content } = JSON.parse(event.data);
      if (action === ActionType.RUN) {
        terminal.writeln(args.command);
      }
      if (observation === ObservationType.RUN) {
        content.split("\n").forEach((line: string) => {
          terminal.writeln(line);
        });
        terminal.write("\n$ ");
      }
    });
  }

  dispose() {
    this._disposables.forEach((d) => d.dispose());
    Socket.removeEventListener("message", () => {});
  }
}

/**
 * The terminal's content is set by write messages. To avoid complicated state logic,
 * we keep the terminal persistently open as a child of <App /> and hidden when not in use.
 */

function Terminal(): JSX.Element {
  const terminalRef = useRef<HTMLDivElement>(null);
  const { commands } = useSelector((state: RootState) => state.cmd);

  useEffect(() => {
    const terminal = new XtermTerminal({
      // This value is set to the appropriate value by the
      // `fitAddon.fit()` call below.
      // If not set here, the terminal does not respect the width
      // of its parent element. This causes a bug where the terminal
      // is too large and switching tabs causes a layout shift.
      cols: 0,
      fontFamily: "Menlo, Monaco, 'Courier New', monospace",
      fontSize: 14,
      theme: {
        background: "#262626",
      },
    });
    terminal.write("$ ");

    const fitAddon = new FitAddon();
    terminal.loadAddon(fitAddon);

    terminal.open(terminalRef.current as HTMLDivElement);

    // Without this timeout, `fitAddon.fit()` throws the error
    // "this._renderer.value is undefined"
    setTimeout(() => {
      fitAddon.fit();
    }, 1);

    const jsonWebsocketAddon = new JsonWebsocketAddon();
    terminal.loadAddon(jsonWebsocketAddon);

    // FIXME, temporary solution to display the terminal,
    // but it will rerender the terminal every time the commands change
    commands.forEach((command) => {
      if (command.type === "input") {
        terminal.writeln(command.content);
      } else {
        command.content.split("\n").forEach((line: string) => {
          terminal.writeln(line);
        });
        terminal.write("\n$ ");
      }
    });
    return () => {
      terminal.dispose();
    };
  }, [commands]);

  return (
    <div className="flex flex-col h-full">
      <div className="flex items-center gap-2 px-4 py-2 text-sm border-b border-neutral-600">
        <VscTerminal />
        Terminal
      </div>
      <div className="grow p-2 flex min-h-0">
        <div ref={terminalRef} className="h-full w-full" />
      </div>
    </div>
  );
}

export default Terminal;
