import './things-scene-components.import'
import '@operato/board/ox-board-viewer.js'
import '@operato/oops'

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

import { buildLabelPrintCommand } from '@operato/barcode'
import { BoardViewer } from '@operato/board'
import { store, PageView } from '@operato/shell'
import { client, gqlContext, subscribe } from '@operato/graphql'
import { clientSettingStore } from '@operato/shell/object-store.js'

import { provider } from '../board-provider'

const NOOP = () => {}

function parseQuery(query: any) {
  for (const key in query) {
    if (query.hasOwnProperty(key)) {
      try {
        query[key] = JSON.parse(query[key])
      } catch (error) {
        // do nothing
      }
    }
  }

  return query
}

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

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

        overflow: hidden;
        position: relative;
      }

      ox-board-viewer {
        flex: 1;
      }

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

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

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

  @property({ type: Object }) _board: any
  @property({ type: String }) _boardId?: string
  @property({ type: String }) _baseUrl?: string
  @property({ type: String }) _help?: string
  @property({ type: Boolean }) _interactive?: boolean
  @property({ type: Boolean }) _showSpinner?: boolean

  @state() data: any

  subscriptionForAutoRefresh

  @query('ox-board-viewer') boardViewer!: BoardViewer

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

  get context() {
    return {
      /* can set the page title with the 'title' parameter. */
      title:
        this.lifecycle.params['title'] ||
        (this._board ? this._board.name : this._showSpinner ? 'Fetching board...' : 'Board Not Found'),
      help: this._help
    }
  }

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

    return oops
      ? html` <ox-oops-note icon=${oops.icon} title=${oops.title} description=${oops.description}></ox-oops-note> `
      : html`
          <ox-board-viewer
            .board=${this._board}
            .provider=${provider}
            ?hide-fullscreen=${this._interactive}
            ?hide-navigation=${this._interactive}
            .data=${this.data}
            history
          ></ox-board-viewer>
          <ox-oops-spinner ?show=${this._showSpinner}></ox-oops-spinner>
        `
  }

  updated(changes) {
    if (changes.has('_boardId')) {
      this.refresh()
    }
  }

  pageUpdated(changes, lifecycle) {
    if (this.active) {
      this._boardId = lifecycle.resourceId
      this._interactive = lifecycle.params['interactive'] === 'true'
      this._help = lifecycle.params['help']
      this.data = parseQuery({ ...lifecycle.params })
    } else {
      /*
       * 비활성화된 페이지에서 render update가 발생하지 않으므로, 강제로 scene을 close 한다.
       * 화면이 inactive 될 때, 굳이 scene을 close하는 이유는,
       * 새로운 board가 선택되어 뷰어가 열릴 때, 기존 보드 잔상이 보이지 않도록 하기위해서이다.
       */
      this.stopSubscribing()

      if (this._boardId) {
        let boardViewer = this.boardViewer
        boardViewer && boardViewer.closeScene()
      }

      this._boardId = ''
    }
  }

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

  async fetch(id: string) {
    if (!id) {
      return
    }

    return await client.query({
      query: gql`
        query ($id: String!) {
          response: board(id: $id) {
            id
            name
            model
          }
        }
      `,
      variables: { id },
      context: gqlContext()
    })
  }

  async refresh() {
    if (!this._boardId) {
      this._board = null
      return
    }

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

      var { data, errors } = (await this.fetch(this._boardId)) || {}

      var board = data?.response

      if (!board) {
        this._board = null
        const message = errors?.map(error => error.message).join('\n')
        throw message || 'board not found'
      }

      this._board = {
        ...board,
        model: JSON.parse(board.model)
      }
    } catch (ex) {
      document.dispatchEvent(
        new CustomEvent('notify', {
          detail: {
            level: 'error',
            message: ex,
            ex
          }
        })
      )
    } finally {
      this._showSpinner = false
      this.updateContext()

      const { autoRefresh = true } = (await clientSettingStore.get('board-view'))?.value || {}
      autoRefresh && this.startSubscribingForAutoRefresh()
    }
  }

  async startSubscribingForAutoRefresh() {
    if (!this._board) {
      return
    }

    await this.stopSubscribing()

    this.subscriptionForAutoRefresh = await subscribe(
      {
        query: gql`
          subscription ($id: String!) {
            board(id: $id) {
              id
            }
          }
        `,
        variables: {
          id: this._board.id
        }
      },
      {
        next: async ({ data }) => {
          await this.stopSubscribing()

          if (data) {
            this.refresh()
          }
        }
      }
    )
  }

  async stopSubscribing() {
    await this.subscriptionForAutoRefresh?.unsubscribe()
    delete this.subscriptionForAutoRefresh
  }

  async getGrf() {
    var { labelRotation } = this._board.model

    var { width, height, data } = (await this.boardViewer.getSceneImageData()) || {}
    if (!width || !data) {
      throw 'Cannot get SceneImageData...'
    }

    return buildLabelPrintCommand(data as Uint8ClampedArray, width, height, labelRotation, false, false)
  }

  async printTrick(image) {
    await this.boardViewer.printTrick(image)
  }
}
