import '@material/mwc-button'
import '@operato/process/ox-process-viewer.js'
import './things-scene-components.import'
import '../viewpart/process-report'
import '../viewpart/equipment-evaluator'
import '../viewpart/stream-info'

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

import { Model } from '@hatiolab/things-scene'
import { client, gqlContext } from '@operato/graphql'
import { i18next } from '@operato/i18n'
import { openPopup } from '@operato/layout'
import { OxProcessViewer } from '@operato/process/ox-process-viewer.js'
import { PageView, store } from '@operato/shell'
import { sleep } from '@operato/utils'

import { evaluateEnergyConsumption } from '../graphql/evaluate-energy-consumption'
import { provider } from '../process-provider'
import { StreamInfo } from '../viewpart/stream-info'

const NOOP = () => {}

@customElement('process-viewer-page')
export class ProcessViewerPage extends connect(store)(PageView) {
  static styles = [
    css`
      :host {
        display: flex;
        flex-direction: column;

        width: 100%; /* 전체화면보기를 위해서 필요함. */
        height: 100%;

        overflow: hidden;
        position: relative;
      }

      ::slotted(stream-info) {
        position: absolute;
        right: 10px;
        top: 10px;
        width: 200px;
        opacity: 0.9;
        box-shadow: 2px 2px 4px gray;
      }

      [commands] {
        position: absolute;
        right: 6px;
        top: 4px;
        display: flex;
        justify-content: flex-end;
      }

      ox-process-viewer {
        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%);
      }
    `
  ]

  @state() _process: Model | null = null
  @state() _processId: string = ''
  @state() _baseUrl: string = ''
  @state() _showSpinner: boolean = false

  @query('ox-process-viewer') processViewer?: OxProcessViewer

  private showStreamEventHandler = this.showStreamInfo.bind(this)

  get oopsNote() {
    return {
      icon: 'insert_chart_outlined',
      title: 'EMPTY BOARD',
      description: 'There are no process to be shown',
      click: NOOP
    }
  }

  get context() {
    return {
      /* can set the page title with the 'title' parameter. */
      title:
        this.lifecycle.params['title'] ||
        (this._process ? this._process.name : this._showSpinner ? 'Fetching process...' : 'Process Not Found')
    }
  }

  equipmentEvaluatorListener = (e: Event) => {
    const equipment = (e as CustomEvent).detail
    const { type, refid } = equipment?.state || {}

    var template = html` <equipment-evaluator .equipment=${equipment}></equipment-evaluator> `

    openPopup(template, {
      backdrop: true,
      size: 'large',
      title: `${i18next.t('title.equipment-evaluator')} - ${(type + ' ' + refid).toUpperCase()}`
    })
  }

  connectedCallback(): void {
    super.connectedCallback()

    document.addEventListener('show-equipment-evaluator', this.equipmentEvaluatorListener)
    document.addEventListener('show-stream-info', this.showStreamEventHandler)
  }

  disconnectedCallback(): void {
    super.disconnectedCallback()

    document.removeEventListener('show-equipment-evaluator', this.equipmentEvaluatorListener)
    document.removeEventListener('show-stream-info', this.showStreamEventHandler)
  }

  render() {
    var oops = !this._showSpinner && !this._process && this.oopsNote

    return oops
      ? html`
          <oops-note
            icon=${oops.icon}
            title=${oops.title}
            description=${oops.description}
            @click=${oops.click || NOOP}
          ></oops-note>
        `
      : html`
          <ox-process-viewer
            .process=${this._process}
            .provider=${provider}
            hide-fullscreen
            @run-process=${(e: CustomEvent) => this.onProcessRun()}
          ></ox-process-viewer>
          <oops-spinner ?show=${this._showSpinner}></oops-spinner>
          <div commands>
            <mwc-button
              icon="analytics"
              label=${String(i18next.t('title.show-process-report'))}
              @click=${() => this.onShowReport()}
            ></mwc-button>
          </div>

          <slot></slot>
        `
  }

  updated(changes: PropertyValues<this>) {
    if (changes.has('_processId')) {
      this.processViewer?.closeScene()
      this.hidePopup()
      this.refresh()
    }
  }

  pageUpdated(changes: any, lifecycle: any) {
    if (this.active) {
      this._processId = lifecycle.resourceId
    } else {
      this._processId = ''
      this.processViewer?.closeScene()
    }
  }

  stateChanged(state: any) {
    this._baseUrl = state.app.baseUrl
  }

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

    var process = response.data.process

    return {
      ...process,
      model: JSON.parse(process.model)
    }
  }

  async refresh() {
    if (!this._processId) {
      return
    }

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

      const process = await this.fetchProcess(this._processId)

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

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

  async onProcessRun() {
    try {
      const processId = this.processViewer?.currentProcessId
      if (!processId) {
        throw new Error('process should not be empty.')
      }

      const { stedDataMap } = await evaluateEnergyConsumption(processId)
      if (this.processViewer?._scene) {
        this.processViewer._scene.dataByRefid = stedDataMap
      }
    } catch (err) {
      console.error(err)
    }
  }

  async onShowReport() {
    try {
      const processId = this.processViewer?.currentProcessId
      if (!processId) {
        throw new Error('process should not be empty.')
      }

      const { stedNodes, stedDataMap, smartGuides } = await evaluateEnergyConsumption(processId)
      if (this.processViewer?._scene) {
        this.processViewer._scene.dataByRefid = stedDataMap
      }

      const process = await this.fetchProcess(processId)

      /*
       * 기존 설정된 이미지가 선택된 상태가 되게 하기 위해서는 selector에 value를 전달해줄 필요가 있음.
       * 주의. value는 object일 수도 있고, string일 수도 있다.
       * string인 경우에는 해당 보드의 id로 해석한다.
       */
      var template = html`
        <process-report .stedNodes=${stedNodes} .smartGuides=${smartGuides} .process=${process}></process-report>
      `

      openPopup(template, {
        backdrop: true,
        size: 'large',
        title: i18next.t('title.process-report')
      })
    } catch (err) {
      console.error(err)
    }
  }

  private popup?: StreamInfo

  showStreamInfo(e: Event) {
    const stream = (e as CustomEvent).detail

    if (!this.popup) {
      const popup = document.createElement('stream-info') as StreamInfo
      popup.addEventListener('close', e => {
        e.stopPropagation()
        this.hidePopup()
      })

      this.appendChild(popup)
      this.popup = popup
    }

    this.popup.stream = stream
  }

  hidePopup() {
    if (this.popup) {
      this.removeChild(this.popup)
      delete this.popup
    }
  }
}
