import { inject, injectable } from 'inversify';
import { Layer } from '../layer';
import { Disposable, DisposableCollection } from '@gedit/utils';
import { domUtils } from '@gedit/utils/lib/browser';
import {
  ConflatableMessage,
  IMessageHandler,
  Message,
  MessageLoop,
} from '@phosphor/messaging';
// import * as ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import * as React from 'react';
import { PipelineEntitiesSelector } from './pipeline-entities-selector';
import { AbleManager, EntityManager } from '../../common';
import { PipelineEntitiesImpl } from './pipeline-entities';

export const FLUSH_LAYER_REQUEST = 'flush-layer-request';

let id = 0;

export class FlushLayerMessage extends ConflatableMessage {
  constructor(readonly layer: Layer) {
    super(FLUSH_LAYER_REQUEST + '_layer' + id++);
  }
}

/**
 * pipeline渲染器
 */
@injectable()
export class PipelineRenderer implements Disposable, IMessageHandler {
  protected toDispose = new DisposableCollection();
  readonly layers: Layer[] = [];
  protected forceUpdates: Set<Layer> = new Set();
  readonly layerDrawMap: Map<Layer, () => void> = new Map();
  readonly layerFlushMessages: Map<Layer, FlushLayerMessage> = new Map();
  readonly node = domUtils.createDivWithClass('gedit-playground-pipeline');

  constructor(
    @inject(PipelineEntitiesSelector)
    protected readonly selector: PipelineEntitiesSelector,
    @inject(EntityManager) entityManager: EntityManager,
    @inject(AbleManager) ableManager: AbleManager
  ) {
    /**
     * entity修改触发layer更新
     */
    this.toDispose.push(
      entityManager.onEntityChanged((entityType: string) => {
        const layers = this.selector.entityLayerMap.get(entityType);
        if (layers) layers.forEach(layer => this.updateLayer(layer));
      })
    );
    /**
     * able修改则触发layer更新
     */
    this.toDispose.push(
      ableManager.onAbleChanged((ableType: string) => {
        const layers = this.selector.ableLayerMap.get(ableType);
        if (layers) layers.forEach(layer => this.updateLayer(layer));
      })
    );
  }

  addLayer(layer: Layer): void {
    this.layers.push(layer);
    this.toDispose.push(layer);
    this.layerFlushMessages.set(layer, new FlushLayerMessage(layer));
    layer.pipelineNode = this.node;
    layer.pipelineLayers = this.layers;
    // Auto create node
    if (layer.draw && !layer.node) {
      layer.node = document.createElement('div');
    }
    // 把layer加到父亲节点上
    if (layer.node) {
      this.node.appendChild(layer.node);
      layer.node.classList.add('gedit-playground-layer');
    }
    if (layer.draw) {
      const draw = layer.draw.bind(layer);
      this.layerDrawMap.set(layer, () => {
        const drawResult = draw();
        if (!layer.rootReact) {
          layer.rootReact = createRoot(layer.node!);
        }
        if (drawResult) {
          // ReactDOM.render(<React.Fragment>{drawResult}</React.Fragment>, layer.node!);
          layer.rootReact.render(<React.Fragment>{drawResult}</React.Fragment>);
        }
      });
      // 重载layer draw
      layer.draw = () => {
        this.updateLayer(layer, true);
      };
    }
  }

  flush(): void {
    this.layers.forEach(layer => {
      this.updateLayer(layer);
    });
  }
  ready(): void {
    this.layers.forEach(layer => {
      if (layer.onReady) layer.onReady();
    });
  }

  dispose(): void {
    this.toDispose.dispose();
    this.node.remove();
  }

  processMessage(msg: Message): void {
    if (msg instanceof FlushLayerMessage) {
      this.onFlushRequest(msg.layer);
    }
  }

  protected onFlushRequest(layer: Layer): void {
    if (this.toDispose.disposed) return;
    const result = this.selector.getEntities(layer);
    // 这里更新layer的entities
    (layer.entityBindedList as PipelineEntitiesImpl).load(result.entities);
    const draw = this.layerDrawMap.get(layer);
    // 只有修改或强制刷新才会刷新
    if (draw && (result.changed || this.forceUpdates.has(layer))) {
      this.forceUpdates.delete(layer);
      try {
        draw();
      } catch (e) {
        console.error(e);
      }
    }
  }

  /**
   * 1. PostMessage: 会将消息在nextTick执行
   * 2. ConflatableMessage: 当多个消息进来会在下一个nextTick做合并
   * 3. 图层相互隔离，即时一层挂了也不受影响
   */
  updateLayer(layer: Layer, forceUpdate?: boolean): void {
    if (forceUpdate) {
      this.forceUpdates.add(layer);
    }
    MessageLoop.postMessage(this, this.layerFlushMessages.get(layer)!);
  }
}
