import { defineComponent } from 'vue';
import { logger } from '../../utils';
import { createSchemaRenderer } from './factory';
import { IFileSchema, NodeSchema } from '../types';
import { Env } from '../Simulator';
import { IHandlersConfig } from '../config';
import {
  emitter,
  EVENT_RENDERER_UPDATE,
  EVENT_RENDERER_READY,
  EVENT_NODE_CHANGE,
  EVENT_RENDERER_COMPLETE
} from '../emitter';
import { Node, Project } from '../models';

function updateSchema(root: IFileSchema, schema: NodeSchema) {
  const children = root.children || [];
  if (Array.isArray(children)) {
    for (let i = 0; i < children.length; i++) {
      const child = children[i];
      if (child.id === schema.id) {
        children.splice(i, 1, schema);
        return;
      } else {
        updateSchema(child as IFileSchema, schema);
      }
    }
  }
}

function createApp(
  Vue: any,
  components: Record<string, any> = {},
  Renderer: any,
  handlers: IHandlersConfig,
  project?: Project
) {
  return defineComponent({
    data() {
      return {
        rendererKey: Symbol('key'),
        schema: <any>null,
        components: Vue.markRaw(components)
      };
    },
    render() {
      const { schema, components, rendererKey } = this;
      const model = project ? project.current : undefined;
      if (schema) {
        return Vue.h(Renderer, {
          key: rendererKey,
          schema: schema,
          components: components,
          model,
          handlers
        });
      } else {
        return null;
      }
    },
    methods: {
      sendComplete() {
        Vue.nextTick(() => {
          emitter.emit(EVENT_RENDERER_COMPLETE);
        });
      },
      onChange(schema: IFileSchema) {
        this.schema = schema;
        this.rendererKey = Symbol('key');
        this.sendComplete();
      },
      onNodeChange(node: Node) {
        const schema = node.toSchema();
        const root = this.schema;
        if (!root) return;
        if (root.id === schema.id) {
          this.onChange(schema as IFileSchema);
        } else {
          updateSchema(root, schema);
          this.sendComplete();
        }
      }
    },
    mounted() {
      emitter.on(EVENT_RENDERER_UPDATE, (this as any).onChange);
      emitter.on(EVENT_NODE_CHANGE, (this as any).onNodeChange);
      emitter.emit(EVENT_RENDERER_READY);
      emitter.emit(EVENT_RENDERER_COMPLETE);
    },
    unmounted() {
      emitter.off(EVENT_RENDERER_UPDATE, (this as any).onChange);
      emitter.off(EVENT_NODE_CHANGE, (this as any).onNodeChange);
    }
  });
}

export class Renderer {
  private app = null;
  private isReady: boolean = false;
  private __callback__: IFileSchema | null = null;
  private handlers: IHandlersConfig = {};
  constructor(handlers?: IHandlersConfig) {
    this.handlers = handlers || {};
  }
  setGlobals(app: any, props: Record<string, any>) {
    if (!app) return;
    Object.entries(props).forEach(([name, value]) => {
      app.config.globalProperties[name] = value;
    });
  }

  run(env: Env, project?: Project) {
    const {
      Vue,
      components = {},
      container = document.body,
      global
    } = env || {};
    if (!Vue) {
      logger.error('Vue is not defined');
      return;
    }
    emitter.on(EVENT_RENDERER_READY, () => {
      this.isReady = true;
      if (this.__callback__) {
        emitter.emit(EVENT_RENDERER_UPDATE, this.__callback__);
        this.__callback__ = null;
      }
    });
    const handlers = this.handlers;
    const App = createApp(
      Vue,
      components,
      createSchemaRenderer(Vue, global),
      handlers,
      project
    );
    const app = Vue.createApp(App);
    this.setGlobals(app, handlers.globalProperties || {});
    app.mount(container);
    this.app = app;
  }

  draw(schema: IFileSchema) {
    if (this.isReady) {
      emitter.emit(EVENT_RENDERER_UPDATE, schema);
    } else {
      this.__callback__ = schema;
    }
  }

  stop() {
    if (this.app) {
      emitter.off(EVENT_RENDERER_READY);
      emitter.off(EVENT_RENDERER_UPDATE);
      (this as any).app.unmount();
      this.app = null;
    }
  }
}
