import { safeRun } from "../../common/util.ts";
import { Extension, HocuspocusProvider, Y, yCollab } from "../deps.ts";
import { SyncService } from "../sync_service.ts";

const userColors = [
  { color: "#30bced", light: "#30bced33" },
  { color: "#6eeb83", light: "#6eeb8333" },
  { color: "#ffbc42", light: "#ffbc4233" },
  { color: "#ecd444", light: "#ecd44433" },
  { color: "#ee6352", light: "#ee635233" },
  { color: "#9ac2c9", light: "#9ac2c933" },
  { color: "#8acb88", light: "#8acb8833" },
  { color: "#1be7ff", light: "#1be7ff33" },
];

export class CollabState {
  public ytext: Y.Text;
  collabProvider: HocuspocusProvider;
  private yundoManager: Y.UndoManager;
  interval?: number;

  constructor(
    serverUrl: string,
    readonly path: string,
    readonly token: string,
    username: string,
    private syncService: SyncService,
    public isLocalCollab: boolean,
  ) {
    this.collabProvider = new HocuspocusProvider({
      url: serverUrl,
      name: token,

      // Receive broadcasted messages from the server (right now only "page has been persisted" notifications)
      onStateless: (
        { payload },
      ) => {
        const message = JSON.parse(payload);
        switch (message.type) {
          case "persisted": {
            // Received remote persist notification, updating snapshot
            syncService.updateRemoteLastModified(
              message.path,
              message.lastModified,
            ).catch(console.error);
          }
        }
      },
    });

    this.collabProvider.on("status", (e: any) => {
      console.log("Collab status change", e);
    });

    this.ytext = this.collabProvider.document.getText("codemirror");
    this.yundoManager = new Y.UndoManager(this.ytext);

    const randomColor =
      userColors[Math.floor(Math.random() * userColors.length)];

    this.collabProvider.awareness.setLocalStateField("user", {
      name: username,
      color: randomColor.color,
      colorLight: randomColor.light,
    });
    if (isLocalCollab) {
      syncService.excludeFromSync(path).catch(console.error);

      this.interval = setInterval(() => {
        // Ping the store to make sure the file remains in exclusion
        syncService.excludeFromSync(path).catch(console.error);
      }, 1000);
    }
  }

  stop() {
    console.log("[COLLAB] Destroying collab provider");
    if (this.interval) {
      clearInterval(this.interval);
    }
    this.collabProvider.destroy();
    // For whatever reason, destroy() doesn't properly clean up everything so we need to help a bit
    this.collabProvider.configuration.websocketProvider.webSocket = null;
    this.collabProvider.configuration.websocketProvider.destroy();

    // When stopping collaboration, we're going back to sync mode. Make sure we got the latest and greatest remote timestamp to avoid
    // conflicts
    safeRun(async () => {
      await this.syncService.unExcludeFromSync(this.path);
      await this.syncService.fetchAndPersistRemoteLastModified(this.path);
    });
  }

  collabExtension(): Extension {
    return yCollab(this.ytext, this.collabProvider.awareness, {
      undoManager: this.yundoManager,
    });
  }
}