'use strict'
import { Animate } from '../animate/index'
import { Application } from '../Application'
import TransformHistory from './history'
import { TransformAnimateEvent, Module } from '../types'

type TransformToParam = [
  id: string,
  param?: string,
  history?: number,
  touches?: TouchEvent
]

class TransformSwitch extends TransformHistory {
  private readonly windowSet: string[] = []
  private readonly promiseQueue: (Promise<void> | undefined)[] = []
  private readonly promiseParamQueue: [...TransformToParam][] = []
  constructor (app: Application) {
    super(app)
  }

  public checkSingleLock (): boolean {
    return this.options.singleLock && this.module.config.level === 0 ? true : false
  }

  public createContainer (module: Module) {
    if (module.status.init) return
    const container = document.createElement('module-container')
    container.setAttribute('name', this.id)
    container.setAttribute('type', ['frameworks', 'system'].indexOf(this.id) !== -1 ? this.id : 'module')

    module.rigesterElement('container', container)
    this.resetContainer(module, this.switchover || !this.animation)
    this.getViewport(module).appendChild(container)
  }

  resetContainer (module: Module, situ: boolean = false) {
    const config = module.config
    const container = module.elements.container
    const systemLevel = ['frameworks', 'system'].includes(module.rel)
    const isDarkModel = this.app.properties.darkTheme
    container.style.cssText = ''
    if (!systemLevel) {
      container.style.cssText = `
        position: absolute;
        z-index: ${(Number(module.level) || 0) + 1};
        width: 100%;
        height: 100%;
        background: ${config.color || (isDarkModel ? '#000' : '#fff')};
        transform: ${situ ? 'translate3d(0, 0, 0)' : 'translate3d(200%, 200%, 0)'};
        contain: strict;
      `
    }
  }

  public getViewport (module: Module = this.module): HTMLElement | ShadowRoot {
    return module.rel === 'system' ? this.fixedViewport : module.config.free === false ? (this.relativeViewport.shadowRoot || this.relativeViewport) : (this.absoluteViewport.shadowRoot || this.absoluteViewport)
  }

  public checkSwitchover (modulu: Module = this.modulu, module: Module = this.module): boolean {
    return modulu?.rel === 'module' && module.config.free !== modulu.config.free ? true : false
  }

  public async to (...args: TransformToParam): Promise<void> {
    return this.pushPromise(this.next() || this.promise(...args), args)
  }

  private prev (): Promise<void> | undefined {
    return this.promiseQueue[0]
  }

  private next (): Promise<void> | undefined {
    const prev = this.prev()
    if (prev) {
      return new Promise((resolve, reject) => {
        prev.then(() => {
          if (this.promiseParamQueue[0]) {
            this.promise(...this.promiseParamQueue[0]).then(resolve).catch(reject)
          }
        })
      })
    }
    return
  }

  private pushPromise <P extends Promise<void>> (promise: P, param: TransformToParam): P {
    this.promiseParamQueue.push(param)
    this.promiseQueue.push(promise)
    return promise
  }

  private shiftPromise () {
    this.promiseParamQueue.shift()
    this.promiseQueue.shift()
  }

  public limit (id: string) {
    const limit = Math.max(this?.options?.limit || 3, 2)
    const index = this.windowSet.indexOf(id)

    if (this.module.rel !== 'module' || this.module.config.background === true) return
    if (index !== -1) this.windowSet.splice(index, 1)
    this.windowSet.push(id)

    if (this.windowSet.length > limit) {
      this.app.modules[this.windowSet.splice(0, 1)[0]]?.destroy()
    }
  }

  public destroy (module: Module) {
    if (module.transient && this.history === -1) {
      this.app.del(module).then(() => {
        const index = this.windowSet.indexOf(module.id)
        this.windowSet.splice(index, 1)
      })
    } else {
      module.fate().then(() => {
        module.destroy().then(() => {
          const index = this.windowSet.indexOf(module.id)
          this.windowSet.splice(index, 1)
        })
      })
    }
  }

  public beforehandDependencies (dependencies: string[] = []) {
    const allPromise: Promise<any>[] = []
    for (const dep of dependencies) {
      allPromise.push(new Promise((resolve, reject) => {
        this.app.get(dep).then((module) => {
          module.prerender().then(resolve).catch(resolve)
        }).catch(reject)
      }))
    }
    return new Promise((resolve, reject) => {
      Promise.all(allPromise).then(resolve).catch(reject)
    })
  }

  public checkPushState (): boolean {
    return this.history !== -1 && this.module.viewType !== 'portal'
  }

  private promise (id: string, param: string = location.search, history: number = 1, touches?: TouchEvent): Promise<void> {
    return new Promise((resolve, reject) => {
      const od = this.od
      const ids = od ? [id, od] : [id]
      const module = this.app.modules[id]
      const modulu = this.app.modules[od]
      const moduli = od ? [module, modulu] : [module]
      if (!module) {
        return this.app.get(id).then(() => {
          this.promise(id, param, history).then(() => {
            this.shiftPromise()
            resolve()
          }).catch(() => {
            reject()
          })
        })
      }

      this.id = id
      this.ids = ids
      this.param = param
      this.module = module
      this.modulu = modulu
      this.moduli = moduli
      this.history = history
      this.touches = touches
      this.animation = this.getAnimationGroup()
      this.switchover = this.checkSwitchover()
      this.target = this.getViewport()

      this.createContainer(module)
      this.start().then(() => {
        this.hintWillTrans(this.viewport)
        if (this.checkPushState() === true) {
          this.pushState(id, module.config.title, param as string)
        }
        module.create(!!this.animation).then(() => {
          document.title = module.config.title
          this.transform().then((stillness) => {
            this.od = id
            this.end(stillness)
            this.shiftPromise()
            this.limit(this.id)
            resolve()
          })
          this.beforehandDependencies(module.config?.prerender).then(() => {
            this.app.trigger('prerenderComplete')
          })
        })
      })
    })
  }

  public pos () {
    let x = 0
    let y = 0
    let attach: string | Array<number> = 'center'
    let origin: string | Array<number> = 'center'
    const width = this.relativeViewport.offsetWidth
    const height = this.relativeViewport.offsetHeight
    const event = this.touches ? this.touches['srcEvent'] : {}
    if (event.changedTouches) {
      x = event.changedTouches[0].pageX
      y = event.changedTouches[0].pageY
    } else {
      x = event.x
      y = event.y
    }
    if (x && y) {
      origin = [x, y]
      if (x < width / 4) {
        x = 0
      } else if (x > width * 3 / 4) {
        x = width
      }
      if (y < height / 4) {
        y = 0
      } else if (y > height * 3 / 4) {
        y = height
      }
      attach = [x, y]
    }
    return {
      x, y, width, height, attach, origin
    }
  }

  get backsetState () {
    return (this.moduli.length === 1 || this.module.level === this.modulu.level) ? -1 : (this.module.level > this.modulu.level ? 0 : 1)
  }

  get viewport (): [HTMLElement, HTMLElement] {
    return this.switchover ? [
      this.module.config.free === false ? this.relativeViewport : this.absoluteViewport,
      this.modulu?.config.free === false ? this.relativeViewport : this.absoluteViewport
    ] : [
      this.module.elements.container,
      this.modulu?.elements.container
    ]
  }

  public modulation (callback: (stillness: boolean) => void): TransformAnimateEvent {
    const viewport = this.viewport
    const backset = this.backsetState
    const reverse = backset === 0 ? false : true
    const pos = this.pos()
    const animateEvent: TransformAnimateEvent = {
      x: pos.x,
      y: pos.y,
      in: new Animate(viewport[0]),
      out: new Animate(viewport[1]),
      view: viewport,
      width: pos.width,
      height: pos.height,
      viewport: [this.relativeViewport, this.absoluteViewport],
      modules: this.moduli,
      reverse: reverse,
      direction: reverse ? -1 : 1,
      backset: backset,
      origin: origin,
      attach: pos.attach,
      touches: this.touches,
      callback: callback
    }

    return animateEvent
  }

  public transform (): Promise<boolean> {
    return new Promise((resolve) => {
      if (!this.animation) {
        this.switchViewport()
        resolve(true)
        return
      }
      const backset = this.backsetState
      const animation = this.getAnimationOneSide(backset)
      const end = (stillness: boolean) => resolve(stillness)
      if (!animation) return end(true)
      const prosise = animation(this.modulation(end))
      if (prosise instanceof Promise) prosise.then(end)
    })
  }

  private hintWillTrans (viewport: [HTMLElement, HTMLElement]) {
    viewport[0].style.willChange = 'transform, opacity'
    if (viewport[1]) {
      viewport[1].style.willChange = 'transform, opacity'
    }
  }

  private removeTransHint (viewport: [HTMLElement, HTMLElement]) {
    viewport[0].style.willChange = 'auto'
    if (viewport[1]) {
      viewport[1].style.willChange = 'auto'
    }
  }

  public start (): Promise<void> {
    return new Promise((resolve, reject) => {
      this.app.trigger('transformStart', this.moduli)
      const transformStart = this.module.events.transformStart
      if (typeof transformStart === 'function') {
        if (transformStart.call(this.module) === false) return reject()
      }
      if (!this.animation || this.switchover) {
        this.module.elements.container.style.transform = 'translate3d(0, 0, 0)'
      }
      resolve()
    })
  }

  public end (stillness: boolean = false) {
    const transformEnd = this.module.events.transformEnd
    this.removeTransHint(this.viewport)
    if (this.switchover) {
      this.switchViewport()
    }
    if (this.modulu) {
      if (!this.animation || !stillness) {
        if (this.modulu.rel === 'module') {
          this.modulu.elements.container.style.transform = 'translate3d(200%, 200%, 0)'
        }
        this.resetContainer(this.module, true)
      }
      this.modulu.elements.container.style.transitionDuration = '0ms'
      if (this.modulu.config.background === true) {
        this.modulu.hide()
      } else {
        this.destroy(this.modulu)
      }
    }
    this.module.show()
    if (typeof transformEnd === 'function') {
      transformEnd.call(this.module)
    }
    this.app.trigger('transformEnd', this.moduli)
  }

  public switchViewport () {
    this.resetViewport()
    if (this.module?.config?.free === false) {
      this.absoluteViewport.style.transform = 'translate3d(200%, 200%, 0)'
      this.relativeViewport.style.transform = 'translate3d(0, 0, 0)'
    } else {
      this.relativeViewport.style.transform = 'translate3d(200%, 200%, 0)'
      this.absoluteViewport.style.transform = 'translate3d(0, 0, 0)'
    }
  }
}

export default TransformSwitch
