import { ipcRenderer } from "electron";
import { action, computed, observable, toJS, reaction } from "mobx";
import { BaseStore } from "./base-store";
import { clusterStore } from "./cluster-store";
import { appEventBus } from "./event-bus";
import { broadcastMessage, handleRequest, requestMain } from "../common/ipc";
import logger from "../main/logger";
import type { ClusterId } from "./cluster-store";

export type WorkspaceId = string;

export interface WorkspaceStoreModel {
  workspaces: WorkspaceModel[];
  currentWorkspace?: WorkspaceId;
}

export interface WorkspaceModel {
  id: WorkspaceId;
  name: string;
  description?: string;
  ownerRef?: string;
  lastActiveClusterId?: ClusterId;
}

export interface WorkspaceState {
  enabled: boolean;
}

/**
 * Workspace
 *
 * @beta
 */
export class Workspace implements WorkspaceModel, WorkspaceState {
  /**
   * Unique id for workspace
   *
   * @observable
   */
  @observable id: WorkspaceId;
  /**
   * Workspace name
   *
   * @observable
   */
  @observable name: string;
  /**
   * Workspace description
   *
   * @observable
   */
  @observable description?: string;
  /**
   * Workspace owner reference
   *
   * If extension sets ownerRef then it needs to explicitly mark workspace as enabled onActivate (or when workspace is saved)
   *
   * @observable
   */
  @observable ownerRef?: string;
  /**
   * Is workspace enabled
   *
   * Workspaces that don't have ownerRef will be enabled by default. Workspaces with ownerRef need to explicitly enable a workspace.
   *
   * @observable
   */
  @observable enabled: boolean;
  /**
   * Last active cluster id
   *
   * @observable
   */
  @observable lastActiveClusterId?: ClusterId;

  constructor(data: WorkspaceModel) {
    Object.assign(this, data);

    if (!ipcRenderer) {
      reaction(() => this.getState(), () => {
        this.pushState();
      });
    }
  }

  /**
   * Is workspace managed by an extension
   */
  get isManaged(): boolean {
    return !!this.ownerRef;
  }

  /**
   * Get workspace state
   *
   */
  getState(): WorkspaceState {
    return toJS({
      enabled: this.enabled
    });
  }

  /**
   * Push state
   *
   * @interal
   * @param state workspace state
   */
  pushState(state = this.getState()) {
    logger.silly("[WORKSPACE] pushing state", {...state, id: this.id});
    broadcastMessage("workspace:state", this.id, toJS(state));
  }

  /**
   *
   * @param state workspace state
   */
  @action setState(state: WorkspaceState) {
    Object.assign(this, state);
  }

  toJSON(): WorkspaceModel {
    return toJS({
      id: this.id,
      name: this.name,
      description: this.description,
      ownerRef: this.ownerRef,
      lastActiveClusterId: this.lastActiveClusterId
    });
  }
}

export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
  static readonly defaultId: WorkspaceId = "default";
  private static stateRequestChannel = "workspace:states";

  private constructor() {
    super({
      configName: "lens-workspace-store",
    });
  }

  async load() {
    await super.load();
    type workspaceStateSync = {
      id: string;
      state: WorkspaceState;
    };

    if (ipcRenderer) {
      logger.info("[WORKSPACE-STORE] requesting initial state sync");
      const workspaceStates: workspaceStateSync[] = await requestMain(WorkspaceStore.stateRequestChannel);

      workspaceStates.forEach((workspaceState) => {
        const workspace = this.getById(workspaceState.id);

        if (workspace) {
          workspace.setState(workspaceState.state);
        }
      });
    } else {
      handleRequest(WorkspaceStore.stateRequestChannel, (): workspaceStateSync[] => {
        const states: workspaceStateSync[] = [];

        this.workspacesList.forEach((workspace) => {
          states.push({
            state: workspace.getState(),
            id: workspace.id
          });
        });

        return states;
      });
    }
  }

  registerIpcListener() {
    logger.info("[WORKSPACE-STORE] starting to listen state events");
    ipcRenderer.on("workspace:state", (event, workspaceId: string, state: WorkspaceState) => {
      this.getById(workspaceId)?.setState(state);
    });
  }

  unregisterIpcListener() {
    super.unregisterIpcListener();
    ipcRenderer.removeAllListeners("workspace:state");
  }

  @observable currentWorkspaceId = WorkspaceStore.defaultId;

  @observable workspaces = observable.map<WorkspaceId, Workspace>({
    [WorkspaceStore.defaultId]: new Workspace({
      id: WorkspaceStore.defaultId,
      name: "default"
    })
  });

  @computed get currentWorkspace(): Workspace {
    return this.getById(this.currentWorkspaceId);
  }

  @computed get workspacesList() {
    return Array.from(this.workspaces.values());
  }

  @computed get enabledWorkspacesList() {
    return this.workspacesList.filter((w) => w.enabled);
  }

  pushState() {
    this.workspaces.forEach((w) => {
      w.pushState();
    });
  }

  isDefault(id: WorkspaceId) {
    return id === WorkspaceStore.defaultId;
  }

  getById(id: WorkspaceId): Workspace {
    return this.workspaces.get(id);
  }

  getByName(name: string): Workspace {
    return this.workspacesList.find(workspace => workspace.name === name);
  }

  @action
  setActive(id = WorkspaceStore.defaultId) {
    if (id === this.currentWorkspaceId) return;

    if (!this.getById(id)) {
      throw new Error(`workspace ${id} doesn't exist`);
    }
    this.currentWorkspaceId = id;
  }

  @action
  addWorkspace(workspace: Workspace) {
    const { id, name } = workspace;

    if (!name.trim() || this.getByName(name.trim())) {
      return;
    }
    this.workspaces.set(id, workspace);

    if (!workspace.isManaged) {
      workspace.enabled = true;
    }

    appEventBus.emit({name: "workspace", action: "add"});

    return workspace;
  }

  @action
  updateWorkspace(workspace: Workspace) {
    this.workspaces.set(workspace.id, workspace);
    appEventBus.emit({name: "workspace", action: "update"});
  }

  @action
  removeWorkspace(workspace: Workspace) {
    this.removeWorkspaceById(workspace.id);
  }

  @action
  removeWorkspaceById(id: WorkspaceId) {
    const workspace = this.getById(id);

    if (!workspace) return;

    if (this.isDefault(id)) {
      throw new Error("Cannot remove default workspace");
    }

    if (this.currentWorkspaceId === id) {
      this.currentWorkspaceId = WorkspaceStore.defaultId; // reset to default
    }
    this.workspaces.delete(id);
    appEventBus.emit({name: "workspace", action: "remove"});
    clusterStore.removeByWorkspaceId(id);
  }

  @action
  setLastActiveClusterId(clusterId?: ClusterId, workspaceId = this.currentWorkspaceId) {
    this.getById(workspaceId).lastActiveClusterId = clusterId;
  }

  @action
  protected fromStore({ currentWorkspace, workspaces = [] }: WorkspaceStoreModel) {
    if (currentWorkspace) {
      this.currentWorkspaceId = currentWorkspace;
    }

    if (workspaces.length) {
      this.workspaces.clear();
      workspaces.forEach(ws => {
        const workspace = new Workspace(ws);

        if (!workspace.isManaged) {
          workspace.enabled = true;
        }
        this.workspaces.set(workspace.id, workspace);
      });
    }
  }

  toJSON(): WorkspaceStoreModel {
    return toJS({
      currentWorkspace: this.currentWorkspaceId,
      workspaces: this.workspacesList.map((w) => w.toJSON()),
    }, {
      recurseEverything: true
    });
  }
}

export const workspaceStore = WorkspaceStore.getInstance<WorkspaceStore>();
