import { ModuleState } from './state'
import { ModuleManifest, Application } from '../types'

class ModuleMount extends ModuleState {
  constructor (id: string, model: ModuleManifest, application: Application) {
    super(id, model, application)
  }

  public timeTick () {
    if (Date.now() - this.createTime > (this.config.timeout || 3600000)) {
      if (this.shadowView) this.destroy()
    }
  }

  public show () {
    if (this.viewType !== 'iframe') return
    const sandbox = this.shadowView as HTMLIFrameElement
    const contentWindow = sandbox?.contentWindow
    if (contentWindow) {
      contentWindow.window.postMessage('module-visible', '*')
      if (this.sameOrigin) {
        contentWindow.window['moduleVisibilityState'] = 'visible'
      }
    }
  }

  public hide () {
    if (this.viewType !== 'iframe') return
    const sandbox = this.shadowView as HTMLIFrameElement
    const contentWindow = sandbox?.contentWindow?.window
    if (contentWindow) {
      contentWindow.postMessage('module-hidden', '*')
      if (this.sameOrigin) {
        contentWindow['moduleVisibilityState'] = 'visible'
      }
    }
  }

  public destroy () {
    return new Promise<void>((resolve, reject) => {
      if (this.rel !== 'module') return reject()
      if (this.application.transform?.id === this.id) return reject()
      if (this.viewType === 'iframe') this.unload().catch(reject)
      this.elements.container.parentElement?.removeChild(this.elements.container)
      this.status.prefetch = this.status.preload = this.status.prerender = false
      this.shadowView = null
      this.status.init = false
      this.events?.destroy.bind(this)()
      resolve()
    })
  }

  public fate () {
    return new Promise<void>((resolve, reject) => {
      if (this.rel !== 'module') return reject()
      if (this.config.background === false) return resolve()
      if (this.viewType !== 'iframe') return resolve()
      if (this.sameOrigin === false) return resolve()
      const sandbox = this.shadowView as HTMLIFrameElement
      if (!sandbox) return
      try {
        const contentDocumentElement = sandbox.contentDocument?.documentElement
        if (document.getElementsByTagName('video')[0]) return resolve()
        if (document.getElementsByTagName('source')[0]) return resolve()
        if (document.getElementsByTagName('object')[0]) return resolve()
        if (document.getElementsByTagName('audio')[0]) return resolve()
        if (document.getElementsByTagName('embed')[0]) return resolve()
        if (document.getElementsByTagName('applet')[0]) return resolve()
        if (document.getElementsByTagName('iframe')[0]) return resolve()
        if (contentDocumentElement) {
          const counter = {
            times: 0
          }
          this.mutationObserver = new MutationObserver(() => {
            counter.times++
            if (counter.times > 1000) {
              resolve()
              this.mutationObserver.disconnect()
            }
          })
          this.mutationObserver.observe(contentDocumentElement, {
            subtree: true,
            attributes: true,
            childList: true,
            characterData: true,
            attributeOldValue: true,
            characterDataOldValue: true
          })
          setTimeout(() => {
            if (counter.times > 10) resolve()
          }, 3000)
        } else {
          reject()
        }
      } catch (error) {
        reject()
      }
    })
  }

  public unfate () {
    this.mutationObserver?.disconnect()
  }

  private unload () {
    return new Promise<void>((resolve, reject) => {
      this.unfate()
      const sandbox = this.shadowView as HTMLIFrameElement
      if (!sandbox) return resolve()
      sandbox.style.display = 'none'
      sandbox.src = 'about:blank'
      try {
        const contentWindow = sandbox.contentWindow?.window
        const contentDocument = sandbox.contentDocument
        contentWindow?.location.reload()
        contentDocument?.open()
        contentDocument?.write('')
        contentDocument?.close()
      } catch (error) {
        reject()
      }
      sandbox.parentElement?.removeChild(sandbox)
      resolve()
    })
  }
}

export {
  ModuleMount
}
