import API from 'api'
import type { Config } from 'config.types'
import { initializeApp } from 'domains/app/actions'
import { initializeConfig } from 'domains/config/actions'
import { setConfig } from 'domains/config/slice'
import { createStore } from 'domains/store'
import { initializeVisibility } from 'domains/visibility/actions'
import type ExternalApi from 'lib/external-api'
import Events, { Events as EventsType } from 'minivents'
import { render } from 'preact'
import ChatApp from 'ui/components/chat-app'
import SeamlyCore from 'ui/components/core/seamly-core'

export default class Engine {
  config: Omit<Config, 'parentElement'>

  namespace: string

  parentElement: HTMLElement | Element

  externalApi: ExternalApi

  functions: object

  eventBus: EventsType

  api: API

  constructor(config: Required<Config>, externalApi: ExternalApi) {
    const { namespace = '', parentElement, ...restConfig } = config

    this.config = {
      ...restConfig,
      namespace,
    }
    this.namespace = namespace

    this.parentElement = parentElement

    this.externalApi = externalApi

    this.api = new API({
      layoutMode: config.layoutMode,
      namespace: config.namespace,
      config: config.api,
      context: config.context,
    })

    // Following are ignored because the types of minivents do not match the docs.
    // @ts-ignore
    this.eventBus = new Events()
    this.functions = {}
    this.registerFunctions({
      on: this.eventBus.on,
      off: this.eventBus.off,
    })

    // @ts-ignore
    this.eventBus.on('function.register', (functionName, fn) =>
      this.registerFunction(functionName, fn),
    )
    // @ts-ignore
    this.eventBus.on('function.unregister', (functionName, fn) =>
      this.unregisterFunction(functionName, fn),
    )
  }

  async render() {
    const { view: View, ...restComponents } = this.config.customComponents || {}

    const renderConfig: Config = {
      ...this.config,
      parentElement: this.parentElement,
      customComponents: Object.keys(restComponents).length
        ? restComponents
        : undefined,
    }

    const store = createStore({
      api: this.api,
      eventBus: this.eventBus,
      config: renderConfig,
      initialState: undefined,
    })

    store.dispatch(setConfig(renderConfig))
    await store.dispatch(initializeConfig())
    await store.dispatch(initializeApp())
    store.dispatch(initializeVisibility())

    if (View) {
      render(
        <SeamlyCore eventBus={this.eventBus} store={store} api={this.api}>
          <View />
        </SeamlyCore>,
        this.parentElement,
      )
    } else {
      render(
        <ChatApp
          config={renderConfig}
          eventBus={this.eventBus}
          store={store}
          api={this.api}
        />,
        this.parentElement,
      )
    }
  }

  destroy() {
    render(null, this.parentElement)
    this.eventBus.off()
    this.api.disconnect()
    this.functions = {}
  }

  registerFunctions(functionMap) {
    Object.entries(functionMap).forEach(([functionName, fn]) => {
      this.registerFunction(functionName, fn)
    })
  }

  registerFunction(functionName, fn) {
    this.functions[functionName] = this.functions[functionName] || []
    this.functions[functionName].push(fn)
    this.externalApi.handleActions()
  }

  unregisterFunction(functionName, fn) {
    const functions = this.functions[functionName]
    if (functions && functions.length) {
      this.functions[functionName] = functions.filter((fn2) => fn2 !== fn)
    }
  }

  execFunction(functionName, ...args) {
    const functions = this.functions[functionName]
    if (!functions || !functions.length) {
      return false
    }
    functions.forEach((fn) => {
      try {
        fn(...args)
      } catch (e) {
        console.error(`Action(${functionName}) failed: `, e)
      }
    })
    return true
  }
}
