import '../viewparts/board-info'
import '../viewparts/group-info'
import '../board-list/board-tile-list'
import '../board-list/group-bar'

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

import { client } from '@operato/graphql'
import { i18next, localize } from '@operato/i18n'
import { openOverlay } from '@operato/layout'
import { pulltorefresh } from '@operato/pull-to-refresh'
import { navigate, PageView, store } from '@operato/shell'
import { ScrollbarStyles } from '@operato/styles'
import InfiniteScrollable from '@operato/utils/mixins/infinite-scrollable.js'
import { UPDATE_FAVORITES } from '@things-factory/fav-base/client'
import { swipe } from '@things-factory/utils/src/index.js'

import {
  cloneBoard,
  releaseBoard,
  revertBoardVersion,
  createBoard,
  createGroup,
  deleteBoard,
  deleteGroup,
  fetchBoardList,
  fetchFavoriteBoardList,
  fetchMyBoardList,
  fetchGroupList,
  updateBoard,
  updateGroup,
  importBoards
} from '../graphql'

@customElement('board-list-page')
export class BoardListPage extends localize(i18next)(connect(store)(InfiniteScrollable(PageView))) {
  static styles = [
    ScrollbarStyles,
    css`
      :host {
        display: flex;
        flex-direction: column;
        position: relative;

        overflow: hidden;
      }

      board-tile-list {
        flex: 1;
        overflow-y: auto;
      }

      #create {
        position: absolute;
        bottom: 15px;
        right: 16px;
      }
    `
  ]

  @property({ type: String }) groupId?: string
  @property({ type: Array }) groups: any[] = []
  @property({ type: Array }) boards: any[] = []
  @property({ type: Array }) favorites: any[] = []

  @state() searchText: string = ''

  @query('board-tile-list') scrollTargetEl!: HTMLElement

  private _page: number = 1
  private _total: number = 0 /* required for infinite-scrolling */

  private page?: string

  get context() {
    var groupId = this.groupId
    var group = this.groups && this.groups.find(group => group.id === groupId)

    return {
      title: {
        icon: 'dashboard',
        text: group
          ? 'Group ' + group.name
          : groupId == 'favor'
            ? 'Favorite'
            : groupId == 'mywork'
              ? 'My works'
              : 'Board List'
      },
      search: {
        handler: search => {
          this.searchText = search
          this.refreshBoards()
        },
        value: this.searchText || ''
      },
      board_topmenu: true
    }
  }

  constructor() {
    super()

    this._infiniteScrollOptions.limit = 50
  }

  render() {
    return html`
      <group-bar
        .groups=${this.groups}
        .groupId=${this.groupId}
        targetPage="board-list"
        @info-group=${e => this.onInfoGroup(e.detail)}
      ></group-bar>

      <board-tile-list
        id="list"
        .boards=${this.boards}
        .favorites=${this.favorites}
        .groups=${this.groups}
        .group=${this.groupId}
        search-text=${this.searchText}
        @info-board=${e => this.onInfoBoard(e.detail)}
        @scroll=${e => {
          this.onScroll(e)
        }}
        @create-board=${e => this.onCreateBoard(e.detail)}
        @refresh-favorites=${e => this.refreshFavorites()}
        creatable
      ></board-tile-list>
    `
  }

  async refresh() {
    this.groups = (await fetchGroupList()).groups.items

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

  async getBoards({ page = 1, limit = this._infiniteScrollOptions.limit } = {}) {
    if (this.groupId && this.groupId == 'favor') {
      return await this.getFavoriteBoards({
        page,
        limit
      })
    } else if (this.groupId && this.groupId == 'mywork') {
      return await this.getMyBoards({
        page,
        limit
      })
    }

    const filters = [] as any[]

    if (this.groupId) {
      filters.push({
        name: 'groupId',
        operator: 'eq',
        value: this.groupId
      })
    }

    if (this.searchText) {
      filters.push({
        name: 'name',
        operator: 'search',
        value: '%' + this.searchText + '%'
      })
    }

    var listParam = {
      filters,
      sortings: [
        {
          name: 'name',
          desc: false
        }
      ],
      pagination: {
        page,
        limit
      }
    }

    return (await fetchBoardList(listParam)).boards
  }

  async getFavoriteBoards({ page = 1, limit = this._infiniteScrollOptions.limit } = {}) {
    const filters = [] as any[]

    if (this.searchText) {
      filters.push({
        name: 'name',
        operator: 'search',
        value: '%' + this.searchText + '%'
      })
    }

    var listParam = {
      filters,
      pagination: {
        page,
        limit
      }
    }

    return (await fetchFavoriteBoardList(listParam)).favoriteBoards
  }

  async getMyBoards({ page = 1, limit = this._infiniteScrollOptions.limit } = {}) {
    const filters = [] as any[]

    if (this.searchText) {
      filters.push({
        name: 'name',
        operator: 'search',
        value: '%' + this.searchText + '%'
      })
    }

    var listParam = {
      filters,
      pagination: {
        page,
        limit
      }
    }

    return (await fetchMyBoardList(listParam)).boardsCreatedByMe
  }

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

    var { items: boards, total } = await this.getBoards()
    this.boards = boards
    this._page = 1
    this._total = total

    this.updateContext()

    this.scrollTargetEl.style.transition = ''
    this.scrollTargetEl.style.transform = `translate3d(0, 0, 0)`
  }

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

    var { items: boards, total } = await this.getBoards({ page: this._page + 1 })
    this.boards = [...this.boards, ...boards]
    this._page = this._page + 1
    this._total = total
  }

  async scrollAction() {
    return this.appendBoards()
  }

  async pageInitialized() {
    this.refresh()
  }

  async pageUpdated(changes, lifecycle) {
    if (this.active) {
      this.page = lifecycle.page
      this.groupId = lifecycle.resourceId
      this.searchText = lifecycle.params?.search || ''

      await this.updateComplete

      this.refreshBoards()
    }
  }

  stateChanged(state) {
    this.favorites = state.favorite.favorites
  }

  firstUpdated() {
    const list = this.scrollTargetEl

    pulltorefresh({
      container: this.renderRoot,
      scrollable: list,
      refresh: () => {
        return this.refresh()
      }
    })

    swipe({
      container: list,
      animates: {
        dragging: async (d, opts) => {
          var groups = [{ id: '' }, { id: 'favor' }, ...this.groups]
          var currentIndex = groups.findIndex(group => group.id == this.groupId)

          if ((d > 0 && currentIndex <= 0) || (d < 0 && currentIndex >= groups.length - 1)) {
            /* TODO blocked gesture */
            return false
          }

          list.style.transform = `translate3d(${d}px, 0, 0)`
        },
        aborting: async opts => {
          list.style.transition = 'transform 0.3s'
          list.style.transform = `translate3d(0, 0, 0)`

          setTimeout(() => {
            list.style.transition = ''
          })
        },
        swiping: async (d, opts) => {
          var groups = [{ id: '' }, { id: 'favor' }, ...this.groups]
          var currentIndex = groups.findIndex(group => group.id == this.groupId)

          if ((d > 0 && currentIndex <= 0) || (d < 0 && currentIndex >= groups.length - 1)) {
            list.style.transition = ''
            list.style.transform = `translate3d(0, 0, 0)`
          } else {
            list.style.transition = 'transform 0.3s'
            list.style.transform = `translate3d(${d < 0 ? '-100%' : '100%'}, 0, 0)`

            navigate(
              `${this.page}/${groups[currentIndex + (d < 0 ? 1 : -1)].id}${this.searchText ? '?search=' + this.searchText : ''}`
            )
          }
        }
      }
    })
  }

  async onInfoBoard(board) {
    openOverlay('viewpart-info', {
      template: html`
        <board-info
          .board=${board}
          .groupId=${this.groupId}
          @clone-board=${e => this.onCloneBoard(e.detail)}
          @release-board=${e => this.onReleaseBoard(e.detail)}
          @revert-board-version=${e => this.onRevertBoardVersion(e.detail)}
          @update-board=${e => this.onUpdateBoard(e.detail)}
          @delete-board=${e => this.onDeleteBoard(e.detail)}
          @join-playgroup=${e => this.onJoinPlayGroup(e.detail)}
          @leave-playgroup=${e => this.onLeavePlayGroup(e.detail)}
          @register-template=${e => this.onRegisterTemplate(e.detail)}
        ></board-info>
      `
    })
  }

  async onInfoGroup(groupId) {
    openOverlay('viewpart-info', {
      template: html`
        <group-info
          .groupId=${groupId}
          @update-group=${e => this.onUpdateGroup(e.detail)}
          @delete-group=${e => this.onDeleteGroup(e.detail)}
          @create-group=${e => this.onCreateGroup(e.detail)}
          @import-boards=${e => this.onImportBoards(e.detail)}
        ></group-info>
      `
    })
  }

  async onCreateGroup(group) {
    try {
      const data = await createGroup(group)
      data && this._notify('info', i18next.t('text.group created', { group: group.name }))
    } catch (ex) {
      this._notify('error', ex, ex)
    }

    this.refresh()
  }

  async onUpdateGroup(group) {
    try {
      const data = await updateGroup(group)
      data && this._notify('info', i18next.t('text.group updated', { group: group.name }))
    } catch (ex) {
      this._notify('error', ex, ex)
    }

    this.refresh()
  }

  async onDeleteGroup(group) {
    try {
      const data = await deleteGroup(group.id)
      data && this._notify('info', i18next.t('text.group deleted', { group: group.name }))
    } catch (ex) {
      this._notify('error', ex, ex)
    }

    this.refresh()
  }

  async onImportBoards({ group, files, overwrite }) {
    try {
      const data = await importBoards({ groupId: group.id, files, overwrite })
      data && this._notify('info', i18next.t('text.boards imported', { count: files.length, group: group.name }))
    } catch (ex) {
      this._notify('error', ex, ex)
    }

    this.refresh()
  }

  async onCreateBoard(board) {
    try {
      if (!board.model) {
        board.model = {
          width: 800,
          height: 600
        }
      }

      const data = await createBoard(board)

      data && this._notify('info', i18next.t('text.board created', { board: board.name }))
    } catch (ex) {
      this._notify('error', ex, ex)
    }

    this.refreshBoards()
  }

  async onCloneBoard({ id, name, description, targetSubdomain, targetGroupId }) {
    try {
      const data = await cloneBoard({
        id,
        name,
        description,
        targetSubdomain,
        targetGroupId
      })
      data && this._notify('info', i18next.t('text.board cloned', { board: name }))
    } catch (ex) {
      this._notify('error', ex, ex)
    }

    this.refreshBoards()
  }

  async onReleaseBoard(board) {
    try {
      const data = await releaseBoard(board)
      data && this._notify('info', i18next.t('text.board released', { board: data?.releaseBoard?.name }))

      history.back() // 릴리즈 후 정보 갱신을 위해 창 닫기
    } catch (ex) {
      this._notify('error', ex, ex)
    }

    this.refreshBoards()
  }

  async onRevertBoardVersion({ id, version }) {
    try {
      const board = await revertBoardVersion(id, version)
      board && this._notify('info', i18next.t('text.board reverted', { board: board.name, version }))
    } catch (ex) {
      this._notify('error', ex, ex)
    }

    this.refreshBoards()
  }

  async onUpdateBoard(board) {
    try {
      const data = await updateBoard(board)
      data && this._notify('info', i18next.t('text.board updated', { board: board.name }))
    } catch (ex) {
      this._notify('error', ex, ex)
    }

    this.refreshBoards()
  }

  async onDeleteBoard(board) {
    try {
      const data = await deleteBoard(board.id)
      data && this._notify('info', i18next.t('text.board deleted', { board: board.name }))
    } catch (ex) {
      this._notify('error', ex, ex)
    }

    this.refreshBoards()
  }

  async onJoinPlayGroup({ board, playGroup }) {
    try {
      const { data } = await client.mutate({
        mutation: gql`
          mutation JoinPlayGroup($id: String!, $boardIds: [String!]!) {
            joinPlayGroup(id: $id, boardIds: $boardIds) {
              id
            }
          }
        `,
        variables: {
          id: playGroup.id,
          boardIds: [board.id]
        }
      })

      data &&
        this._notify('info', i18next.t('text.joined into play-group', { board: board.name, playGroup: playGroup.name }))
    } catch (ex) {
      this._notify('error', ex, ex)
    }
  }

  async onLeavePlayGroup({ board, playGroup }) {
    try {
      const { data } = await client.mutate({
        mutation: gql`
          mutation ($id: String!, $boardIds: [String!]!) {
            leavePlayGroup(id: $id, boardIds: $boardIds) {
              id
            }
          }
        `,
        variables: {
          id: playGroup.id,
          boardIds: [board.id]
        }
      })

      data &&
        this._notify('info', i18next.t('text.leaved from play-group', { board: board.name, playGroup: playGroup.name }))
    } catch (ex) {
      this._notify('error', ex, ex)
    }
  }

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

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

  async refreshFavorites() {
    const { data } = await client.query({
      query: gql`
        query {
          myFavorites {
            id
            routing
          }
        }
      `
    })

    data &&
      store.dispatch({
        type: UPDATE_FAVORITES,
        favorites: data.myFavorites.map(favorite => favorite.routing)
      })
  }

  _notify(level: 'info' | 'error' | 'warn', message: any, ex?: any) {
    document.dispatchEvent(
      new CustomEvent('notify', {
        detail: {
          level,
          message,
          ex
        }
      })
    )
  }
}
