import { INode } from './node';
import { DocumentModel } from '../document-model';
import { IPublicModelModalNodesManager } from '@felce/lowcode-types';
import { createModuleEventBus, IEventBus } from '@felce/lowcode-editor-core';

export function getModalNodes(node: INode) {
  if (!node) return [];
  let nodes: any = [];
  if (node.componentMeta.isModal) {
    nodes.push(node);
  }
  const { children } = node;
  if (children) {
    children.forEach((child) => {
      nodes = nodes.concat(getModalNodes(child));
    });
  }
  return nodes;
}

export interface IModalNodesManager extends IPublicModelModalNodesManager<INode> {}

export class ModalNodesManager implements IModalNodesManager {
  willDestroy: any;

  private page: DocumentModel;

  private modalNodes: INode[];

  private nodeRemoveEvents: any;

  private emitter: IEventBus;

  constructor(page: DocumentModel) {
    this.page = page;
    this.emitter = createModuleEventBus('ModalNodesManager');
    this.nodeRemoveEvents = {};
    this.setNodes();
    this.hideModalNodes();
    this.willDestroy = [
      page.onNodeCreate((node) => this.addNode(node)),
      page.onNodeDestroy((node) => this.removeNode(node)),
    ];
  }

  getModalNodes(): INode[] {
    return this.modalNodes;
  }

  getVisibleModalNode(): INode | null {
    const visibleNode = this.getModalNodes().find((node: INode) => node.getVisible());
    return visibleNode || null;
  }

  hideModalNodes() {
    this.modalNodes.forEach((node: INode) => {
      node.setVisible(false);
    });
  }

  setVisible(node: INode) {
    this.hideModalNodes();
    node.setVisible(true);
  }

  setInvisible(node: INode) {
    node.setVisible(false);
  }

  onVisibleChange(func: () => any) {
    this.emitter.on('visibleChange', func);
    return () => {
      this.emitter.removeListener('visibleChange', func);
    };
  }

  onModalNodesChange(func: () => any) {
    this.emitter.on('modalNodesChange', func);
    return () => {
      this.emitter.removeListener('modalNodesChange', func);
    };
  }

  private addNode(node: INode) {
    if (node?.componentMeta.isModal) {
      this.hideModalNodes();
      this.modalNodes.push(node);
      this.addNodeEvent(node);
      this.emitter.emit('modalNodesChange');
      this.emitter.emit('visibleChange');
    }
  }

  private removeNode(node: INode) {
    if (node.componentMeta.isModal) {
      const index = this.modalNodes.indexOf(node);
      if (index >= 0) {
        this.modalNodes.splice(index, 1);
      }
      this.removeNodeEvent(node);
      this.emitter.emit('modalNodesChange');
      if (node.getVisible()) {
        this.emitter.emit('visibleChange');
      }
    }
  }

  private addNodeEvent(node: INode) {
    this.nodeRemoveEvents[node.id] = node.onVisibleChange(() => {
      this.emitter.emit('visibleChange');
    });
  }

  private removeNodeEvent(node: INode) {
    if (this.nodeRemoveEvents[node.id]) {
      this.nodeRemoveEvents[node.id]();
      delete this.nodeRemoveEvents[node.id];
    }
  }

  setNodes() {
    const nodes = getModalNodes(this.page.rootNode!);
    this.modalNodes = nodes;
    this.modalNodes.forEach((node: INode) => {
      this.addNodeEvent(node);
    });

    this.emitter.emit('modalNodesChange');
  }
}
