import '@material/web/icon/icon.js'
import '@material/web/button/elevated-button.js'
import '@material/web/progress/linear-progress.js'
import '@operato/property-editor/ox-properties-dynamic-view.js'
import '@operato/markdown'
import '@operato/board/ox-board-viewer.js'
import './equipment-info'

import gql from 'graphql-tag'
import { css, html, LitElement } from 'lit'
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'
import { customElement, property, query, state } from 'lit/decorators.js'
import { asyncReplace } from 'lit/directives/async-replace.js'

import { client } from '@operato/graphql'
import { i18next } from '@operato/i18n'
import { ScrollbarStyles } from '@operato/styles'
import { sleep } from '@operato/utils'
import { provider } from '@things-factory/board-ui'

import { evaluateEquipment } from '../graphql/evaluate-equipment'

const ActivityFetchResult = `\
{
  id
  name
  model {
    name
    description
    active
    tag
    inout
    type
    unit
    options
    quantifier
    spec
  }
  thumbnail
  uiType
  uiSource
  reportType
  reportSource
  createdAt
}`

@customElement('equipment-evaluator')
export class EquipmentEvaluator extends LitElement {
  static styles = [
    ScrollbarStyles,
    css`
      :host {
        display: flex;
        flex-direction: column;
        user-select: none;

        background-color: var(--main-section-background-color);
        min-width: 350px;
      }

      [tab] {
        display: flex;
        background-color: rgba(0, 0, 0, 0.08);
        opacity: 0.85;
      }

      [tab] > div {
        flex: 1;

        display: flex;
        align-items: center;
        justify-content: center;
        border-right: 1px solid rgba(0, 0, 0, 0.1);

        color: var(--property-sidebar-tab-icon-color);
        height: 40px;
      }

      [tab] md-icon {
        pointer-events: none;
      }

      [tab] [selected] {
        background-color: var(--main-section-background-color);
        border-left: 1px solid rgba(255, 255, 255, 0.5);
        border-right: 1px solid rgba(0, 0, 0, 0.2);
        opacity: 1;
        font-weight: bold;
      }

      [content] {
        flex: 1;

        position: relative;
        display: flex;
        flex-direction: column;

        overflow: auto;
      }

      md-elevated-button {
        position: absolute;
        top: 4px;
        right: 6px;
      }

      ox-properties-dynamic-view {
        display: block;
      }

      ox-markdown {
        flex: 1;
      }

      ox-board-viewer {
        flex: 1;
        width: 100%;
      }

      md-linear-progress {
        --md-theme-primary: #0ab2ff;
        --md-linear-progress-active-indicator-color: #efefef;
      }

      @media screen and (max-width: 460px) {
        :host {
          width: 100vw;
        }
      }
    `
  ]

  @property({ type: Object }) equipment?: any

  @state() activity: any
  @state() board: any
  @state() input: any
  @state() output: any
  @state() tabName: string = 'level1'
  @state() waitingProgress: boolean = false
  @state() equipmentLv2Result?: any

  @query('ox-board-viewer') oxBoardViewer: any

  render() {
    const { help } = this.equipment.nature
    const about = `/helps/${help || 'index'}.md`

    var tabName = this.tabName ? this.tabName : 'level1'

    return html`
      <div
        tab
        @click=${(e: MouseEvent) => {
          this.tabName = (e.target as HTMLElement).getAttribute('name')!
        }}
      >
        <div name="level1" ?selected=${tabName == 'level1'}>
          <md-icon>analytics</md-icon>&nbsp;${i18next.t('label.process-analysis')}
        </div>
        <div name="input" ?selected=${tabName == 'input'}>
          <md-icon>edit_note</md-icon>&nbsp;${i18next.t('label.design-input')}
        </div>
        <div name="result" ?selected=${tabName == 'result'}>
          <md-icon>legend_toggle</md-icon>&nbsp;${i18next.t('label.design-output')}
        </div>
      </div>

      <div content>
        ${{
          level1: html`<equipment-info .equipment=${this.equipment}></equipment-info>`,
          input: html`
            ${this.activityInputContent()}
            <md-elevated-button @click=${() => this.onRequestEvaluation()}>
              <md-icon slot="icon">backup</md-icon>
              ${String(i18next.t('label.request-evaluation'))}
            </md-elevated-button>
          `,
          result: html` ${this.activityResultContent()} `,
          help: html` <ox-markdown .src=${about} scrollable></ox-markdown>`
        }[this.tabName!]}
      </div>
    `
  }

  firstUpdated() {
    const { type } = this.equipment?.state
    this.fetchActivity(type)
  }

  async fetchActivity(name) {
    const response = await client.query({
      query: gql`
        query activityByName($name: String!) {
          activityByName(name: $name) ${ActivityFetchResult}
        }
      `,
      variables: {
        name
      }
    })

    this.activity = response.data.activityByName
  }

  async onRequestEvaluation() {
    const { type } = this.equipment?.state

    this.output = this.getOutputFromBoard()

    // TODO: need to confirm the in/out format of equiopment level2
    try {
      this.waitingProgress = true

      this.equipmentLv2Result = await evaluateEquipment({
        type,
        params: this.output
      })
    } catch (err) {
      console.error(err)
    } finally {
      this.waitingProgress = false
      this.tabName = 'result'
    }
  }

  activityInputContent() {
    const { uiType = 'generated' } = this.activity || {}

    switch (uiType) {
      case 'template':
        return this.templateContent()
      case 'generated':
        return this.generatedInputContent()
      case 'board':
        return this.boardInputContent()
      case 'page':
      case 'external':
      default:
        return html``
    }
  }

  activityResultContent() {
    const { reportType = 'generated' } = this.activity || {}

    switch (reportType) {
      case 'template':
        return this.templateContent()
      case 'generated':
        return this.generatedResultContent()
      case 'board':
        return this.boardResultContent()
      case 'page':
      case 'external':
      default:
        return html``
    }
  }

  templateContent() {
    const { uiSource: template } = this.activity
    return unsafeHTML(template)
  }

  getInputSpec() {
    if (this.activity) {
      const model = this.activity.model

      return (model || [])
        .filter(item => item.inout === 'in' || item.inout === 'inout')
        .map(item => {
          return {
            ...item,
            label: item.name
          }
        })
    } else {
      return this.equipment.designInputProperties
    }
  }

  getOutputSpec() {
    const model = this.activity?.model

    return (model || [])
      .filter(item => item.inout === 'inout' || item.inout === 'out')
      .map(item => {
        return {
          ...item,
          label: item.name
        }
      })
  }

  generatedInputContent() {
    var input = this.input

    const inputSpec = this.getInputSpec()

    return html`
      <ox-properties-dynamic-view data-name="input" .props=${inputSpec} .value=${input}></ox-properties-dynamic-view>
    `
  }

  generatedResultContent() {
    var input = this.input
    var output = this.output

    const inputSpec = this.getInputSpec()
    const outputSpec = this.getOutputSpec()

    if (!output) {
      output = inputSpec
        .filter(item => item.inout === 'inout')
        .reduce((inout, item) => {
          inout[item.name] = input[item.name]
          return inout
        }, {})
    }

    return html`
      <ox-properties-dynamic-view
        data-name="output"
        .props=${outputSpec}
        .value=${output}
        @property-change=${e => (this.output = e.currentTarget.value)}
      ></ox-properties-dynamic-view>
    `
  }

  boardInputContent() {
    var input = this.input
    var output = this.output
    var equipment = this.equipment

    const { uiSource: boardId } = this.activity

    if (!this.board || this.board.id !== boardId) {
      this.board = {
        id: boardId
      }
    }

    const streams = equipment.anchors.reduce((streams, anchor) => {
      const { name, inout } = anchor
      const line = inout == 'out' ? equipment.findOutletLines(name) : equipment.findInletLines(name)
      streams[name] = line[0]?.data
      return streams
    }, {} as any)

    var data = {
      equipment,
      ...streams
    }

    return html`
      ${this.waitingProgress
        ? html` <md-linear-progress .progress=${asyncReplace(this.waitProgress()) as number}></md-linear-progress>`
        : html``}

      <ox-board-viewer
        style="background-color: white;"
        .board=${this.board}
        .provider=${provider}
        .data=${data}
        .values=${{
          ...input,
          ...output
        }}
        hide-fullscreen
        hide-navigation
      ></ox-board-viewer>
    `
  }

  boardResultContent() {
    var result = this.equipmentLv2Result

    const { reportSource: boardId } = this.activity

    if (!this.board || this.board.id !== boardId) {
      this.board = {
        id: boardId
      }
    }

    const streams = this.equipment.anchors.reduce((streams, anchor) => {
      const { name, inout } = anchor
      const line = inout == 'out' ? this.equipment.findOutletLines(name) : this.equipment.findInletLines(name)
      streams[name] = line[0]?.data
      return streams
    }, {} as any)

    return html`
      ${this.waitingProgress
        ? html` <md-linear-progress .progress=${asyncReplace(this.waitProgress()) as number}></md-linear-progress>`
        : html``}
      <ox-board-viewer
        style="background-color: white;"
        .board=${this.board}
        .provider=${provider}
        .data=${{ ...streams, ...result }}
        hide-fullscreen
        hide-navigation
      ></ox-board-viewer>
    `
  }

  getOutputFromBoard() {
    const outputSpec = this.getOutputSpec()
    const values = this.oxBoardViewer.getSceneValues()

    return outputSpec.reduce((output, spec) => {
      output[spec.name] = values[spec.name]
      return output
    }, {})
  }

  close() {
    history.back()
  }

  async *waitProgress() {
    var progress = 0
    while (this.waitingProgress) {
      yield Number((++progress % 11) / 10)
      await sleep(200)
    }
  }
}
