import '@operato/process/ox-process-modeller.js'
import './things-scene-components.import'

import gql from 'graphql-tag'
import { css, html, PropertyValues } from 'lit'
import { customElement, property, query } from 'lit/decorators.js'
import { connect } from 'pwa-helpers/connect-mixin.js'

import { Component, Model, Scene } from '@hatiolab/things-scene'
import { client, gqlContext } from '@operato/graphql'
import { OxPropertyEditor } from '@operato/property-editor'
import { PageView, store } from '@operato/shell'
import { ComponentTemplate } from '@operato/process/dist/src/types'
import { OxProcessModeller } from '@operato/process/ox-process-modeller.js'

import { provider } from '../process-provider'
import components from './things-scene-components-with-tools.import'

const NOOP = () => {}

@customElement('process-modeller-page')
export class ProcessModellerPage extends connect(store)(PageView) {
  constructor() {
    super()

    components.forEach((component: { templates: ComponentTemplate[] }) => {
      OxProcessModeller.registerTemplate(component.templates)
    })

    /* 컴포넌트에서 정의된 에디터들을 MODELLER_EDITORS에 등록 */
    var addedEditors = {} as { [type: string]: any }
    for (let component in components) {
      let { editors } = components[component]

      editors &&
        editors.forEach((editor: any) => {
          let { type, element } = editor

          addedEditors[type] = element
        })
    }

    OxPropertyEditor.register(addedEditors)
  }

  @property({ type: String }) processId: string = ''
  @property({ type: String }) processName: string = ''
  @property({ type: Object }) model: Model = {
    width: 1920,
    height: 1080
  }
  @property({ type: String }) baseUrl: string = ''
  @property({ type: Array }) selected: Component[] = []
  @property({ type: Number }) mode: number = 1
  @property({ type: Boolean }) hideProperty: boolean = false
  @property({ type: String }) overlay: string = ''
  @property({ type: Object }) scene: Scene | null = null
  @property({ type: Array }) componentGroupList: object = OxProcessModeller.groups
  @property({ type: Array }) fonts: any[] = []
  @property({ type: Boolean }) _showSpinner: boolean = false

  @query('process-modeller') modeller?: OxProcessModeller

  private process: Model | null = null

  static styles = [
    css`
      :host {
        display: flex;
        flex-direction: column;

        overflow: hidden;
        position: relative;
      }

      process-modeller {
        flex: 1;
      }

      oops-spinner {
        display: none;
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      }

      oops-spinner[show] {
        display: block;
      }

      oops-note {
        display: block;
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
      }
    `
  ]

  get context() {
    return {
      title: this.process ? this.processName : this._showSpinner ? 'Fetching process...' : 'Process Not Found',
      help: 'process-modeller/modeller',
      widebleed: true
    }
  }

  get oopsNote() {
    return {
      icon: 'color_lens',
      title: 'EMPTY PROCESS',
      description: 'There are no process to be designed',
      click: NOOP
    }
  }

  async refresh() {
    if (!this.processId) {
      this.process = null
      this.model = {
        width: 1920,
        height: 1080
      }

      return
    }
    try {
      this._showSpinner = true
      this.updateContext()

      var response = await client.query({
        query: gql`
          query FetchProcessById($id: String!) {
            process(id: $id) {
              id
              name
              model
            }
          }
        `,
        variables: { id: this.processId },
        context: gqlContext()
      })

      var process = response.data.process

      if (!process) {
        this.process = null
        throw 'process not found'
      }

      this.process = {
        ...process,
        model: JSON.parse(process.model)
      }

      this.processName = this.process?.name
      this.model = {
        ...this.process?.model
      }
    } catch (ex) {
      document.dispatchEvent(
        new CustomEvent('notify', {
          detail: {
            level: 'error',
            message: ex,
            ex
          }
        })
      )
    } finally {
      this._showSpinner = false
      this.updateContext()
    }
  }

  updated(changes: PropertyValues<this>) {
    if (changes.has('processId')) {
      this.refresh()
    }
  }

  pageUpdated(changes: any, lifecycle: any) {
    if (this.active) {
      this.processId = lifecycle.resourceId
    } else {
      this.processId = ''
      this.model = {
        width: 1280,
        height: 1020,
        components: []
      }
      this.modeller?.close()
    }
  }

  stateChanged(state: any) {
    this.baseUrl = state.route.rootPath
    this.fonts = state.font
  }

  render() {
    var oops = (!this._showSpinner && !this.model && this.oopsNote) as
      | {
          icon: string
          title: string
          description: string
          click: () => any
        }
      | false

    return oops
      ? html`
          <oops-note
            icon=${oops.icon}
            title=${oops.title}
            description=${oops.description}
            @click=${oops.click || NOOP}
          ></oops-note>
        `
      : html`
          <ox-process-modeller
            .mode=${this.mode}
            @mode-changed=${(e: CustomEvent) => {
              this.mode = e.detail.value
            }}
            .model=${this.model}
            @model-changed=${(e: CustomEvent) => {
              this.model = e.detail.value
            }}
            .scene=${this.scene}
            @scene-changed=${(e: CustomEvent) => {
              this.scene = e.detail.value
            }}
            .selected=${this.selected}
            @selected-changed=${(e: CustomEvent) => {
              this.selected = e.detail.value
            }}
            .baseUrl=${this.baseUrl}
            .provider=${provider}
            @save-model=${(e: CustomEvent) => this.saveProcess()}
            .componentGroupList=${this.componentGroupList}
            .fonts=${this.fonts}
            .hideProperty=${this.hideProperty}
          >
          </ox-process-modeller>
          <oops-spinner ?show=${this._showSpinner}></oops-spinner>
        `

    return html``
  }

  async updateProcess() {
    try {
      this._showSpinner = true

      var { id, name, description, groupId } = this.process || {}
      var model = JSON.stringify(this.scene?.model)

      await client.mutate({
        mutation: gql`
          mutation UpdateProcess($id: String!, $patch: ProcessPatch!) {
            updateProcess(id: $id, patch: $patch) {
              id
            }
          }
        `,
        variables: {
          id,
          patch: { name, description, model, groupId }
        },
        context: gqlContext()
      })

      document.dispatchEvent(
        new CustomEvent('notify', {
          detail: {
            level: 'info',
            message: 'saved'
          }
        })
      )
    } catch (ex) {
      document.dispatchEvent(
        new CustomEvent('notify', {
          detail: {
            level: 'error',
            message: ex,
            ex: ex
          }
        })
      )
    } finally {
      this._showSpinner = false
    }

    this.updateContext()
  }

  async saveProcess() {
    await this.updateProcess()
  }
}
