<template>
  <wrapper
    auto
    margin
    padding
    background
  >
    <div class="l-flex__none l-flex c-device-detail has-bottom-padding">
      <div
        class="l-flex__none c-device-detail__screen o-program"
        :class="{ 'u-pointer': programProxy }"
        :style="programStyle"
        @click="onView"
      />
      <div class="l-flex__auto l-flex--col">
        <div class="l-flex__none c-device-detail__name u-ellipsis">{{ deivceName }}</div>
        <template v-if="programProxy">
          <div class="l-flex__none c-device-detail__program u-ellipsis">
            <span
              class="u-pointer"
              @click="onView"
            >{{ programName }}</span>
          </div>
          <div class="l-flex__none c-device-detail__time">{{ programTime }}</div>
        </template>
      </div>
    </div>
    <div
      v-loading="deviceOptions.loading"
      class="l-flex__fill l-flex--col c-timeline"
    >
      <div class="l-flex__none l-flex--row has-bottom-padding">
        <div class="l-flex__auto c-sibling-item" />
        <schema-select
          v-model="deviceOptions.params.productId"
          class="c-sibling-item"
          placeholder="全部产品"
          :schema="productSelectSchema"
        />
        <el-date-picker
          v-model="current"
          class="l-flex__none c-sibling-item u-pointer"
          type="date"
          placeholder="选择日期"
          :picker-options="pickerOptions"
          :editable="false"
          :clearable="false"
          @change="onTimeChange"
        />
        <search-input
          v-model.trim="deviceOptions.params.name"
          class="l-flex__none c-sibling-item"
          placeholder="设备名称"
          @search="search"
        />
        <button
          class="l-flex__none c-sibling-item o-button"
          @click="search"
        >
          搜索
        </button>
      </div>
      <div class="l-flex__none l-flex c-timeline__row header">
        <div class="l-flex__none c-timeline__left">
          <span class="o-priority priority3">高</span>
          <span class="o-priority priority2">中</span>
          <span class="o-priority priority1">低</span>
        </div>
        <div class="l-flex__auto l-flex--row c-timeline__right">
          <i
            class="l-flex__none c-timeline__arrow left el-icon-arrow-left u-pointer"
            :class="{ display: canPrevious }"
            @click="offsetTime(-1)"
          />
          <div class="l-flex__auto l-flex--row c-timeline__time">
            <div
              v-for="time in times"
              :key="time"
              class="l-flex__none"
            >
              {{ time }}
            </div>
          </div>
          <i
            class="l-flex__none c-timeline__arrow right display el-icon-arrow-right u-pointer"
            @click="offsetTime(1)"
          />
        </div>
      </div>
      <div class="l-flex--col c-timeline__main u-relative">
        <div class="l-flex__auto u-overflow-y--auto">
          <div
            v-for="item in deviceOptions.list"
            :key="item.id"
            class="l-flex c-timeline__row"
            :class="{ selected: item.id === deviceId }"
            @click="chooseProgramProxy(item)"
          >
            <div class="l-flex__none c-timeline__left u-relative u-pointer">
              <div class="u-ellipsis">{{ item.name }}</div>
            </div>
            <div class="l-flex__auto l-flex--col c-timeline__right">
              <div
                v-if="item.options.loading"
                class="l-flex--row c-timeline__programs"
              >
                <i class="el-icon-loading has-padding--h" />加载中...
              </div>
              <div
                v-else-if="item.options.error"
                class="l-flex--row c-timeline__programs has-padding--h"
              >
                <el-link
                  type="warning"
                  @click.stop="getTimeline(item)"
                >
                  获取失败，点击重试
                </el-link>
              </div>
              <template v-else-if="item.options.list.length">
                <div
                  v-for="(programs, index) in item.options.list"
                  :key="index"
                  class="c-timeline__programs l-flex--row u-relative"
                >
                  <div
                    v-for="program in programs"
                    :key="program.key"
                    class="l-flex__none l-flex--row c-event-program u-pointer"
                    :class="[{ 'selected': program.event.selected }, `priority${program.event.priority}`]"
                    :style="program.style"
                    @click.stop="chooseProgramProxy(item, program)"
                  >
                    <i
                      class="l-flex__none c-event-program__img o-program"
                      :style="program.event.style"
                    />
                    <div class="l-flex__auto">
                      <auto-text
                        class="c-event-program__time"
                        :text="program.time"
                        :tag="program.style.width"
                      />
                      <auto-text
                        class="c-event-program__name"
                        :text="program.event.target.name"
                        :tag="program.style.width"
                      />
                    </div>
                  </div>
                </div>
              </template>
              <div
                v-else
                class="l-flex--row c-timeline__programs has-padding--h"
              >
                当前时段暂无节目
              </div>
            </div>
          </div>
        </div>
        <div
          v-show="style"
          class="c-timeline__line"
        >
          <div
            class="c-timeline__mask"
            :style="style"
          />
        </div>
      </div>
      <status-wrapper
        v-if="isAbnormal"
        :error="deviceOptions.error"
        @click="getDevices"
      />
      <pagination
        :total="deviceOptions.totalCount"
        :page.sync="deviceOptions.params.pageNum"
        :limit.sync="deviceOptions.params.pageSize"
        @pagination="getDevices"
      />
    </div>
    <schedule-dialog ref="scheduleDialog" />
  </wrapper>
</template>

<script>
import {
  getProducts,
  getDevices,
  getTimeline
} from '@/api/device'
import {
  EventFreq,
  ScheduleType
} from '@/constant'
import { createListOptions } from '@/utils'
import {
  toDate,
  toDateStr,
  toTimeStr,
  toZeroPoint,
  getNearestHitDate,
  getStartDate,
  getFinishDate,
  pickMin,
  pickMax
} from '@/utils/event'
import { EventCache } from '@/utils/cache'

export default {
  name: 'ScheduleTimeline',
  data () {
    return {
      productSelectSchema: {
        remote: getProducts,
        pagination: true,
        value: 'id',
        label: 'name'
      },
      deviceOptions: createListOptions({
        productId: '',
        name: '',
        activate: 2,
        pageSize: 5
      }),
      style: null,
      canPrevious: false,
      startHour: 0,
      times: [],
      current: null,
      device: null,
      programProxy: null
    }
  },
  computed: {
    deviceId () {
      return this.device?.id
    },
    deivceName () {
      return this.device?.name
    },
    programName () {
      return this.programProxy?.event.target.name
    },
    programTime () {
      return this.programProxy?.event.time
    },
    programStyle () {
      return this.programProxy?.event.style
    },
    isAbnormal () {
      const deviceOptions = this.deviceOptions
      return deviceOptions.error || !deviceOptions.loading && deviceOptions.totalCount === 0
    },
    pickerOptions () {
      return {
        disabledDate: this.isDisableDate
      }
    }
  },
  created () {
    this.initTimes(new Date())
    this.getDevices()
    this.$timer = setInterval(this.calcLine, 1000)
  },
  beforeDestroy () {
    clearInterval(this.$timer)
  },
  methods: {
    getDevices () {
      this.device = null
      if (this.programProxy) {
        this.programProxy.event.selected = false
      }
      this.programProxy = null
      const options = this.deviceOptions
      options.error = false
      options.loading = true
      getDevices(options.params).then(({ data, totalCount }) => {
        options.list = data.map(this.transform)
        options.totalCount = totalCount
        options.list.forEach(this.getTimeline)
      }, () => {
        options.error = true
        options.list = []
      }).finally(() => {
        options.loading = false
      })
    },
    search () {
      const options = this.deviceOptions
      options.list = []
      options.totalCount = 0
      options.params.pageNum = 1
      this.getDevices()
    },
    transform (device) {
      const { id, name } = device
      return {
        id, name,
        options: {
          loading: true,
          error: false,
          events: [],
          list: []
        }
      }
    },
    getTimeline (device) {
      const options = device.options
      options.error = false
      options.loading = true
      getTimeline(device.id, { custom: true }).finally(() => {
        options.loading = false
      }).then(
        events => {
          const now = Date.now()
          options.events = this.transformEvents(events.map(this.transformEvent).filter(({ until }) => !until || now < toDate(until)))
          this.calcEvents(options)
        },
        () => {
          options.error = true
          options.list = []
        }
      )
    },
    transformEvent (event) {
      return {
        ...event,
        time: this.getTime(event),
        startDateTime: toDate(event.start),
        endDateTime: toDate(event.until),
        style: null,
        selected: false,
        img () {
          EventCache.getImage(this.target.type, this.target.id).then(img => {
            if (img) {
              this.style = { backgroundImage: `url("${img}")` }
            }
          })
        }
      }
    },
    transformEvents (events) {
      const map = {}
      for (let i = 0; i < events.length; i++) {
        const event = events[i]
        if (!map[event.priority]) {
          map[event.priority] = []
        }
        map[event.priority].push(event)
      }
      return Object.keys(map)
        .sort((a, b) => a > b ? -1 : 1)
        .map(key => map[key].sort((a, b) => toDate(a.start) - toDate(b.start)))
    },
    getTime (event) {
      const { freq, start, until, byDay, startTime, endTime } = event
      switch (freq) {
        case EventFreq.WEEKLY:
          return `自${start.split(' ')[0]}开始${until ? `至${until.split(' ')[0]}前` : ''} 每周${byDay.split(',').map(val => ['日', '一', '二', '三', '四', '五', '六'][val]).join('、')} ${startTime} - ${endTime}`
        default:
          return until ? `${start} - ${until}` : `自${start}开始`
      }
    },
    isDisableDate (date) {
      const now = new Date()
      const min = new Date(now.getFullYear(), now.getMonth(), now.getDate())
      const max = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 30)
      return date < min || date > max
    },
    initTimes (date) {
      const maxDate = pickMax(toDate(date), new Date())
      this.current = toZeroPoint(maxDate)
      this.startHour = Math.min(20, maxDate.getHours())
      this.refreshTimes(this.startHour)
    },
    refreshTimes (start) {
      const times = []
      for (let i = 0; i < 5; i++) {
        times.push(`${(start + i).toString().padStart(2, '0')}:00`)
      }
      this.times = times

      this.$startDateTime = toDate(this.current.getTime() + start * 3600000)
      this.$endDateTime = toDate(this.current.getTime() + (start + 4) * 3600000)
      this.calcLine()

      this.refreshTimeline()
    },
    calcLine () {
      const now = Date.now()
      this.canPrevious = now < this.$startDateTime
      if (now < this.$startDateTime || now >= this.$endDateTime) {
        this.style = null
      } else {
        this.style = {
          left: `${Math.min(100, (now - this.$startDateTime) / 144000)}%`
        }
      }
    },
    refreshTimeline () {
      this.deviceOptions.list.forEach(device => {
        this.calcEvents(device.options)
      })
    },
    offsetTime (offset) {
      const next = this.startHour + offset
      if (offset < 0) {
        const timestamp = this.current.getTime() + next * 3600000
        const now = new Date()
        now.setMinutes(0)
        now.setSeconds(0)
        now.setMilliseconds(0)
        if (timestamp >= now) {
          if (next < 0) {
            this.initTimes(timestamp)
          } else {
            this.refreshTimes(this.startHour = next)
          }
        }
      } else if (next > 20) {
        this.initTimes(this.$endDateTime)
      } else {
        this.refreshTimes(this.startHour = next)
      }
    },
    onTimeChange (val) {
      this.initTimes(val)
    },
    calcSamePriorityEvents (events) {
      const total = 144000
      const arr = []
      for (let i = 0; i < events.length; i++) {
        const event = events[i]
        const { startDateTime, endDateTime } = event
        if (endDateTime && endDateTime <= this.$startDateTime || startDateTime >= this.$endDateTime) {
          continue
        }
        const hit = getNearestHitDate(event, this.$startDateTime, this.$endDateTime)
        if (hit) {
          const startDate = getStartDate(event, hit)
          const endDate = getFinishDate(event, hit)
          arr.push({
            key: event.target.id,
            event,
            time: `${toDateStr(startDate)} ${toTimeStr(startDate)} - ${toDateStr(endDate)} ${toTimeStr(endDate)}`,
            style: {
              left: `${(hit - this.$startDateTime) / total}%`,
              width: `${(pickMin(this.$endDateTime, getFinishDate(event, hit)) - hit) / total}%`,
              zIndex: event.priority
            }
          })
          event.img?.()
          if (endDate >= this.$endDateTime) {
            break
          }
        }
      }
      return arr
    },
    calcEvents (options) {
      if (options.loading || options.error) {
        return
      }
      options.list = options.events.map(this.calcSamePriorityEvents).filter(events => events.length)
    },
    chooseProgramProxy (device, programProxy) {
      this.device = device
      if (this.programProxy) {
        this.programProxy.event.selected = false
      }
      if (programProxy) {
        programProxy.event.selected = true
      }
      this.programProxy = programProxy
    },
    onView () {
      if (this.programProxy) {
        switch (this.programProxy.event.target.type) {
          case ScheduleType.CALENDAR:
            window.open(this.$router.resolve({
              name: 'program',
              params: { id: this.programProxy.event.target.id }
            }).href, '_blank')
            break
          case ScheduleType.RECUR:
            this.$refs.scheduleDialog.show(this.programProxy.event.target.id)
            break
          default:
            break
        }
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.c-device-detail {
  height: 200px;

  &__screen {
    width: 354px;
    margin-right: 24px;
    border-radius: $radius--mini;
  }

  &__name {
    color: $black;
    font-size: 20px;
    font-weight: bold;
    line-height: 1;
  }

  &__program {
    margin-top: 20px;
    color: $blue;
    font-size: 20px;
    font-weight: bold;
  }

  &__time {
    margin-top: $spacing;
    color: $info--dark;
    font-size: 20px;
  }
}

.c-timeline {
  min-height: 400px;

  &__main {
    flex: 0 1 auto;
    min-height: 0;
    border: 1px solid $border;
  }

  &__row {
    & + & {
      border-top: 1px solid $border;
    }
  }

  &__row.header {
    .c-timeline__left {
      border-right: none;
    }
  }

  &__row.header &__right {
    color: $black;
    user-select: none;
    background-color: transparent;
  }

  &__row.selected &__left {
    color: #fff;
    background-color: #9fbfe8;
  }

  &__row.selected &__left::after {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    width: 4px;
    background-color: $blue;
  }

  &__left {
    display: inline-flex;
    align-items: center;
    width: 210px;
    padding: 0 10px 0 20px;
    margin-right: 1px;
    color: $black;
    font-size: 16px;
    background-color: #fafbfc;
    border-right: 1px solid $border;
  }

  &__right {
    position: relative;
    color: $info--dark;
    font-size: 14px;
    line-height: 1;
    overflow: hidden;
  }

  &__programs {
    box-sizing: content-box;
    height: 48px;

    & + & {
      border-top: 1px dashed $border;
    }
  }

  &__arrow {
    visibility: hidden;
    position: absolute;
    top: 50%;
    padding: 6px;
    color: #fff;
    font-size: 12px;
    border-radius: $radius--mini;
    background-color: $blue;
    transform: translateY(-50%);

    &.left {
      left: 0;
    }

    &.right {
      right: 0;
    }

    &.display {
      visibility: visible;
    }
  }

  &__time {
    justify-content: space-around;
    padding: 10px 0;
    color: $black;
    font-size: 14px;
  }

  &__line {
    position: absolute;
    top: -12px;
    left: 210px;
    right: 0;
    bottom: 0;
    pointer-events: none;
  }

  &__mask {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    color: #ff0000;
    border-right: 1px solid currentColor;
    z-index: 999;

    &::after {
      content: "";
      position: absolute;
      top: 0;
      right: -5px;
      width: 9px;
      height: 9px;
      border-radius: 50%;
      background-color: currentColor;
    }
  }
}

.c-event-program {
  position: absolute;
  top: 0;
  bottom: 0;
  font-size: 12px;
  overflow: hidden;

  &:hover {
    color: #fff;
    background-color: rgba($blue, 0.4);
  }

  &.selected {
    color: #fff;
    background-color: $blue;
    z-index: 99;
  }

  &__img {
    width: 84px;
    height: 48px;
    margin-right: 8px;
    border-radius: $radius--mini;
  }

  &__name {
    margin-top: 10px;
  }
}

.o-priority {
  display: inline-block;
  padding: 4px;
  font-size: 12px;
  border-radius: $radius--mini;

  & + & {
    margin-left: 4px;
  }
}

.priority1 {
  color: #8e929c;
  background-color: #edf0f6;
}

.priority2 {
  color: #7642fd;
  background-color: #eae2fe;
}

.priority3 {
  color: #ff2222;
  background-color: #ffecec;
}

.o-program {
  background: url("~@/assets/program_bg.png") 0 0 / 100% 100% no-repeat;
}
</style>
