import '@material/web/icon/icon.js'
import '@operato/data-grist/ox-grist.js'
import '@operato/data-grist/ox-sorters-control.js'
import '@operato/data-grist/ox-filters-form.js'
import '@operato/input/ox-select.js'
import '@operato/input/ox-input-select-buttons.js'
import '@operato/popup/ox-popup-list.js'
import '@operato/process' /* for process-creation-popup */
import '@sted/process-modeller'
import '../viewpart/process-info'

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

import { client } from '@operato/graphql'
import { DataGrist } from '@operato/data-grist'
import { i18next } from '@operato/i18n'
import { p13n } from '@operato/p13n'
import { openOverlay, openPopup } from '@operato/layout'
import { PopupHandle } from '@operato/popup'
import { navigate, PageView, store } from '@operato/shell'
import { CommonGristStyles } from '@operato/styles'
import { sleep } from '@operato/utils'
import InfiniteScrollable from '@operato/utils/mixins/infinite-scrollable.js'

import { notify } from '../utils/notify'

import {
  cloneProcess,
  releaseProcess,
  revertProcessVersion,
  createProcess,
  deleteProcess,
  evaluateEnergyConsumption,
  fetchProcessList,
  fetchProcessGroupList,
  updateProcess
} from '../graphql'

@customElement('process-list')
export class ProcessListPage extends connect(store)(InfiniteScrollable(p13n(PageView))) {
  static styles = [
    CommonGristStyles,
    css`
      :host {
        display: flex;

        width: 100%;

        --grid-record-emphasized-background-color: red;
        --grid-record-emphasized-color: yellow;
      }

      #add button {
        color: var(--theme-white-color);
        background-color: var(--primary-background-color);
      }
    `
  ]

  @property({ type: Object }) config: any
  @property({ type: Object }) data: any
  @property({ type: String }) mode: 'GRID' | 'LIST' | 'CARD' = 'CARD'
  @property({ type: String }) groupId?: string
  @property({ type: Array }) groups: any[] = []

  @query('ox-grist') grist!: DataGrist

  private popup?: PopupHandle

  get context() {
    return {
      search: {
        handler: (search: string) => {
          this.grist.searchText = search
        },
        placeholder: '공정디자인 리스트',
        value: this.grist.searchText
      },
      filter: {
        handler: () => {
          this.grist.toggleHeadroom()
        }
      }
    }
  }

  render() {
    const mode = this.mode || 'CARD'
    const options = (this.groups || []).map(group => {
      return { display: group.description, value: group.id }
    })

    return html`
      <ox-grist
        .config=${this.config}
        .mode=${mode}
        auto-fetch
        .fetchHandler=${this.fetchHandler.bind(this)}
        .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}
      >
        <div slot="headroom">
          <div id="filters">
            <ox-input-select-buttons
              .value=${this.groupId}
              .options=${options}
              @change=${e => {
                this.groupId = e.currentTarget.value
              }}
              multiple
            ></ox-input-select-buttons>
          </div>

          <div id="modes">
            <md-icon @click=${() => this.changeMode('GRID')} ?active=${mode == 'GRID'}>grid_on</md-icon>
            <md-icon @click=${() => this.changeMode('CARD')} ?active=${mode == 'CARD'}>apps</md-icon>
          </div>

          <div id="add">
            <button><md-icon @click=${e => this.onCreateProcess()}>add</md-icon></button>
          </div>
        </div>

        <ox-grist-personalizer slot="setting"></ox-grist-personalizer>
      </ox-grist>
    `
  }

  async changeMode(mode: 'GRID' | 'LIST' | 'CARD') {
    const pagePreferenceProvider = this.getPagePreferenceProvider('main')!
    const pagePreference = pagePreferenceProvider.load() || {}
    await pagePreferenceProvider.save({ ...pagePreference, mode })
    this.mode = mode
  }

  async fetchHandler() {
    const { items: records, total } = await this.getProcesses(...arguments)
    this.updateContext()

    return {
      total,
      records
    }
  }

  get gristConfig() {
    return {
      list: {
        thumbnail: 'thumbnail',
        fields: ['name', 'description'],
        details: ['updatedAt']
      },
      columns: [
        {
          type: 'gutter',
          gutterName: 'dirty'
        },
        {
          type: 'gutter',
          gutterName: 'sequence'
        },
        {
          type: 'gutter',
          gutterName: 'button',
          icon: 'dashboard',
          handlers: {
            click: (columns, data, column, record, rowIndex) => {
              this.onShowReport(record.id)
            }
          }
        },
        {
          type: 'gutter',
          gutterName: 'button',
          icon: 'drive_file_rename_outline',
          handlers: {
            click: (columns, data, column, record, rowIndex) => {
              this.onInfoProcess(record)
            }
          }
        },
        {
          type: 'string',
          name: 'id',
          hidden: true
        },
        {
          type: 'string',
          name: 'name',
          header: 'name',
          record: {
            editable: false,
            align: 'left'
          },
          width: 200,
          filter: 'search',
          sortable: true
        },
        {
          type: 'string',
          name: 'description',
          header: 'description',
          record: {
            editable: false,
            align: 'left'
          },
          width: 360,
          handlers: {
            dblclick: (columns, data, column, record, rowIndex) => {
              alert(`${column.name} ${record[column.name]}, row : ${rowIndex}`)
            }
          },
          filter: 'search'
        },
        {
          type: 'image',
          name: 'thumbnail',
          hidden: true,
          record: {
            editable: false,
            renderer: function (value, column, record, rowIndex, owner) {
              return html`
                <img src=${value} style="width: 100%; height: 100%;" /></a>
              `
            }
          }
        },
        // {
        //   type: 'select-buttons',
        //   name: 'groupId',
        //   header: 'group',
        //   hidden: true,
        //   filter: {
        //     operator: 'in',
        //     options: () => {
        //       return this.groups.map(group => {
        //         return { display: group.description, value: group.id }
        //       })
        //     }
        //   }
        // },
        {
          type: 'datetime',
          name: 'updatedAt',
          header: 'updated at',
          sortable: true,
          width: 180
        },
        {
          type: 'datetime',
          name: 'createdAt',
          header: 'created at',
          width: 180
        }
      ],
      rows: {
        appendable: false,
        selectable: {
          multiple: true
        },
        handlers: {
          click: (columns, data, column, record, rowIndex) => {
            column.type != 'gutter' && navigate(`process-viewer/${record.id}`)
          }
        },
        classifier: function (record, rowIndex) {}
      },
      sorters: [
        {
          name: 'name',
          desc: false
        }
      ],
      pagination: {
        pages: [20, 30, 50],
        limit: 30
      }
    }
  }

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

  pageUpdated(changes, lifecycle) {
    if (this.active) {
      this.grist.fetch()
    }
  }

  async pageInitialized() {
    this.config = this.gristConfig

    const pagePreferenceProvider = this.getPagePreferenceProvider('main')!
    const pagePreference = (await pagePreferenceProvider.load()) || {}
    this.mode = pagePreference['mode'] || 'CARD'

    this.refresh()
  }

  async onInfoProcess(process) {
    openOverlay('viewpart-info', {
      template: html`
        <process-info
          .process=${process}
          .groupId=${this.groupId}
          @clone-process=${(e: CustomEvent) => this.onCloneProcess(e.detail)}
          @release-process=${(e: CustomEvent) => this.onReleaseProcess(e.detail)}
          @revert-process-version=${(e: CustomEvent) => this.onRevertProcessVersion(e.detail)}
          @update-process=${(e: CustomEvent) => this.onUpdateProcess(e.detail)}
          @delete-process=${(e: CustomEvent) => this.onDeleteProcess(e.detail)}
          @register-template=${(e: CustomEvent) => this.onRegisterTemplate(e.detail)}
        ></process-info>
      `
    })
  }

  async onShowReport(processId) {
    const { evaluateEnergyConsumption: evaluated, process } = await evaluateEnergyConsumption(processId)
    const { stedNodes } = evaluated || {}

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

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

  async onCreateProcess() {
    if (this.popup) {
      delete this.popup
    }

    /*
     * 기존 설정된 이미지가 선택된 상태가 되게 하기 위해서는 selector에 value를 전달해줄 필요가 있음.
     * 주의. value는 object일 수도 있고, string일 수도 있다.
     * string인 경우에는 해당 보드의 id로 해석한다.
     */
    var template = html`
      <process-creation-popup
        .groups=${this.groups}
        @create-process=${async e => {
          try {
            var { name, description, groupId, model, thumbnail } = e.detail
            var process = {
              name,
              description,
              groupId,
              model,
              thumbnail
            } as any

            if (!process.model) {
              process.model = {
                width: 1600,
                height: 1200
              }
            }

            const { createProcess: created } = (await createProcess(process)) || {}
            if (!created) {
              return
            }

            this.popup && this.popup.close()

            await sleep(100)

            navigate(`process-modeller/${created.id}`)

            notify('info', i18next.t('text.process created'))
          } catch (ex) {
            console.error(ex)
            notify('error', ex, ex)
          }
        }}
      ></process-creation-popup>
    `

    this.popup = openPopup(template, {
      backdrop: true,
      size: 'large',
      title: i18next.t('title.create-process')
    })
  }

  async onUpdateProcess(process) {
    try {
      const data = await updateProcess(process)
      data && notify('info', 'saved')
      data && notify('info', i18next.t('text.process updated', { process: process.name }))
    } catch (ex) {
      notify('error', ex, ex)
    }

    this.refreshProcesses()
  }

  async onDeleteProcess(process) {
    try {
      const data = await deleteProcess(process.id)
      data && notify('info', i18next.t('text.process deleted', { process: process.name }))
    } catch (ex) {
      notify('error', ex, ex)
    }

    this.refreshProcesses()
  }

  async refresh() {
    this.groups = (await fetchProcessGroupList()).processGroups.items

    if (this.groups) {
      await this.refreshProcesses()
    }
  }

  async getProcesses({ filters = [], page = 1, limit = 30, sortings = [] } = {}) {
    return (
      await fetchProcessList({
        filters:
          this.groupId && this.groupId.length > 0
            ? [
                ...filters,
                {
                  name: 'groupId',
                  operator: 'in',
                  value: this.groupId
                }
              ]
            : filters,
        sortings,
        page,
        limit
      })
    ).processes
  }

  async refreshProcesses() {
    if (!this.groups) {
      await this.refresh()
      return
    }

    this.grist.fetch()

    this.updateContext()
  }

  async onCloneProcess({ id, name, description, targetSubdomain, targetGroupId }) {
    try {
      const data = await cloneProcess({
        id,
        name,
        description,
        targetSubdomain,
        targetGroupId
      })
      data && notify('info', i18next.t('text.process cloned', { process: name }))
    } catch (ex) {
      notify('error', ex, ex)
    }

    this.refreshProcesses()
  }

  async onReleaseProcess(process) {
    try {
      const data = await releaseProcess(process)
      data && notify('info', i18next.t('text.process released', { process: process.name }))
    } catch (ex) {
      notify('error', ex, ex)
    }

    this.refreshProcesses()
  }

  async onRevertProcessVersion({ id, version }) {
    try {
      const process = await revertProcessVersion(id, version)
      process && notify('info', i18next.t('text.process reverted', { process: process.name, version }))
    } catch (ex) {
      notify('error', ex, ex)
    }

    this.refreshProcesses()
  }

  async onRegisterTemplate({ id, name, description, visibility }) {
    try {
      const { data } = await client.mutate({
        mutation: gql`
          mutation ($id: String!, $name: String!, $description: String!, $visibility: String!) {
            registerProcessAsTemplate(id: $id, name: $name, description: $description, visibility: $visibility) {
              id
            }
          }
        `,
        variables: {
          id,
          name,
          description,
          visibility
        }
      })

      data &&
        notify('info', i18next.t('text.info_x_successfully', { x: i18next.t('text.register-template') }), {
          name
        })
    } catch (ex) {
      notify('error', ex, ex)
    }
  }
}
