import { ModulePrefetch } from './prefetch'
import { ModuleManifest, ModuleConfig, Application } from '../types'
import inject from './inject'

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

  public rigesterElement (name: string, element: HTMLElement) {
    this.elements[name] = element
    if (name === 'container' && element) {
      this.status.init = true
    }
  }

  private createSandbox (uri?: string, config?: ModuleConfig['sandbox']): HTMLIFrameElement {
    const sandbox = document.createElement('iframe')
    if (config !== undefined) sandbox.setAttribute('sandbox', config)
    if (uri) sandbox.src = uri
    return sandbox
  }

  private writeSandbox (sandbox: HTMLIFrameElement, container: HTMLElement) {
    sandbox.src = 'about:blank'
    sandbox.setAttribute('seamless', 'seamless')
    container.appendChild(sandbox)
    const contentDocument = sandbox.contentDocument
    const contentWindow = sandbox.contentWindow
    if (contentWindow && contentDocument) {
      contentDocument.open()
      contentDocument.write(this.config?.source?.html || '<head><meta charset="utf-8"></head>')
      contentDocument.close()
      inject(contentWindow?.window, this.config, this.application)
    }
  }

  private createShadowbox (render: (target: HTMLElement) => void): HTMLElement {
    const shadowbox = document.createElement('div')
    const shadowboxInner = document.createElement('div')
    shadowbox?.attachShadow({ mode: 'open' })
    const shadowRoot = shadowbox.shadowRoot ? shadowbox.shadowRoot : shadowbox
    shadowRoot.appendChild(shadowboxInner)
    render(shadowboxInner)
    this.elements.container.appendChild(shadowbox)
    return shadowbox
  }

  private createPortals (uri: string): HTMLPortalElement {
    const portal = document.createElement('portal') as HTMLPortalElement
    portal.src = uri
    return portal
  }

  private createShadowView (): HTMLElement | HTMLPortalElement | HTMLIFrameElement | undefined {
    if (this.shadowView) return this.shadowView
    if (this.config.render) {
      return this.shadowView = this.createShadowbox(this.config.render)
    }
    if (this.rel !== 'module') return
    return this.shadowView = this.viewType === 'portal' ? this.createPortals(this.uri) : this.createSandbox(this.uri, this.config.sandbox)
  }

  private loadShadowView () {
    const container = this.elements.container
    const shadowView = this.shadowView
    if (!shadowView) return
    switch (this.viewType) {
      case 'portal':
        container.appendChild(shadowView as HTMLPortalElement)
        break
      case 'iframe':
      default:
        if (this.uri) {
          container.appendChild(shadowView)
          const contentWindow = (shadowView as HTMLIFrameElement).contentWindow
          if (contentWindow && this.sameOrigin) {
            inject(contentWindow?.window, this.config, this.application)
            contentWindow?.window.postMessage('container-ready', '*')
          }
        } else {
          this.writeSandbox(shadowView as HTMLIFrameElement, container)
        }
        break
    }
  }

  public create (prepare = true): Promise<Event | string> {
    this.unfate()
    this.timeTick()
    if (this.status.prerender) {
      return Promise.resolve('prerender')
    }
    return new Promise((resolve, reject) => {
      this.createTime = Date.now()
      const shadowView = this.createShadowView()
      if (!shadowView) {
        return resolve('null')
      }
      if (this.viewType === 'shadow') {
        return resolve('shadow')
      }
      shadowView.style.cssText = `
        position: absolute;
        z-index: 0;
        width: 100%;
        height: 100%;
        border: 0;
        transform: translate3d(0, 0, 0);
      `
      shadowView.onload = (e) => {
        this.status.prerender = true
        this.events?.load.bind(this)()
        resolve(e)
      }
      shadowView.onerror = (e) => {
        this.events?.loadError.bind(this)()
        reject(e)
      }
      this.loadShadowView()
      if (prepare === false || this.config.animation === false) {
        return resolve('instant')
      }
      setTimeout(() => {
        resolve('timeout')
      }, this.status.preload ? 800 : 200)
    })
  }

  public preload (): Promise<Event> {
    return new Promise((resolve, reject) => {
      const uri = this.uri
      const head = document.head
      const sandbox = this.createSandbox('', '')
      sandbox.style.display = 'none'
      sandbox.onload = (e) => {
        this.status.preload = true
        head.removeChild(sandbox)
        this.events?.preload.bind(this)()
        resolve(e)
      }
      sandbox.onerror = (e) => {
        head.removeChild(sandbox)
        reject(e)
      }
      if (uri) {
        sandbox.src = this.uri
        head.appendChild(sandbox)
      } else {
        this.writeSandbox(sandbox, head)
      }
    })
  }

  public prerender (): Promise<Event | void> {
    if (this.status.preload || this.status.prerender) {
      return Promise.resolve()
    }
    return new Promise((resolve, reject) => {
      this.preload().then(resolve).catch(reject)
      this.beforehandLink(this.uri, 'prerender', 'document')
    })
  }
}

export {
  ModuleView
}
