import { Module } from '../Module/index'
import { Transform } from '../Transform'
import { ApplicationBase } from './base'
import provider from './provider'
import { ModuleManifest, ModuleConfig } from '../types'

class Application extends ApplicationBase {
  public transform = new Transform(this)

  constructor () {
    super()
    provider(this)
  }

  get activeModule (): Module | undefined {
    const id = this.transform.id
    const module = this.modules[id]
    return module
  }

  public add (id: string, manifest: ModuleManifest) {
    return this.modules[id] = new Module(id, manifest, this)
  }

  public del (module: Module) {
    return new Promise<void>((resolve, reject) => {
      delete this.modules[module.id]
      if (module.status.init) {
        module.destroy().then(resolve).catch(reject)
      } else {
        resolve()
      }
    })
  }

  public get (id: string): Promise<Module> {
    return new Promise((resolve, reject) => {
      if (!id) return reject()
      if (typeof this.modules[id] === 'object') return resolve(this.modules[id])
      const modulePromise = this.options.modules[id]
      switch (typeof modulePromise) {
        case 'function':
          this.promiseModule(modulePromise).then((manifest) => {
            resolve(this.add(id, manifest))
          }).catch(reject)
          break
        case 'object':
          resolve(this.add(id, modulePromise))
          break
        default:
          const url = id
          if (!this.moduleSrcVerify(url)) {
            reject()
            break
          }
          const module = this.createModuleByURL(url)
          if (module) {
            resolve(module)
          } else {
            reject()
          }
          break
      }
    })
  }

  public createModuleByURL (url: string, config?: {
    [key in Extract<keyof ModuleConfig, string>]?: string | number
  }): Module | undefined {
    const newModuleId = decodeURIComponent(url)
    const modules = this.modules
    if (modules[newModuleId]) return modules[newModuleId]
    const sameModule = this.getModuleByURL(url)
    if (sameModule) return sameModule
    return this.add(newModuleId, {
      config: Object.assign({
        title: '',
        rel: 'module',
        level: (this.activeModule?.config.level ?? 0) + 1,
        free: true,
        source: {
          src: url
        },
        background: 'auto',
        timeout: 0,
        animation: 'inherit',
        transient: true
      }, config)
    })
  }

  public pushWindow (url: string, title = '', preset = 'slide') {
    const resolve = this.resolveURL(url)
    const search = resolve.search
    if (!this.moduleSrcVerify(url)) {
      return Promise.reject('Illegal')
    }
    return new Promise<void>((resolve, reject) => {
      const module = this.createModuleByURL(url, {
        title,
        animation: preset
      })
      if (module) {
        this.transform.to(module.id, module.config.source.html ? search : '').then(resolve).catch(reject)
      }
    })
  }

  private mountSystem () {
    if (this.options.modules['system']) {
      this.get('system').then(() => {
        this.transform.to('system', undefined, -1).then(() => {
          this.trigger('systemDidMount')
        })
      })
    }
  }

  private mountFramework () {
    this.get('frameworks').then((module) => {
      const route = this.route
      const index = module.config.index || ''
      const id = route.id || index
      const config = module.config
      this.config = config
      this.transform.setup({
        singleFlow: config.singleFlow,
        index,
        defaultIndex: id,
        notFound: config.notFind,
        limit: Math.max(config.limit || 7, 2),
        exists: this.exists,
        defaultAnimation: config.animation
      })
      this.transform.to('frameworks', undefined, -1).then(() => {
        this.trigger('frameworksDidMount')
      })
      if (id !== index) {
        this.transform.pushState(index)
        if (id) this.transform.to(id)
        return
      }
      if (id) this.transform.to(id)
    }).catch(() => {
      this.console.error('Module frameworks must be included!', 'Serious!', '')
    })
  }

  public start () {
    this.setExists()
    this.mountFramework()
    this.mountSystem()
  }
}

export {
  Application
}
