import * as fs from 'fs-extra'
import * as chokidar from 'chokidar'
import * as path from 'path'
import { each } from 'realm-utils'
import { ensureDir, string2RegExp, ensureUserPath } from './Utils'
import { SparkyFile } from './SparkyFile'
import { log } from './Sparky'
import { Plugin } from './FuseBox'
import { parse, SparkyFilePatternOptions } from './SparkyFilePattern'

const glob = require('glob')

export class SparkFlow {
  private activities = []

  private watcher: any

  private files: SparkyFile[]

  private completedCallback: any

  private initialWatch = false

  public glob(globs: string[], opts?: SparkyFilePatternOptions): SparkFlow {
    this.activities.push(() => this.getFiles(globs, opts))
    return this
  }

  public createFiles(paths: string[]) {
    this.files = []
    paths.forEach(p => {
      const isAbsolute = path.isAbsolute(p)
      const fpath = isAbsolute ? p : path.join(process.cwd(), p)
      this.files.push(
        new SparkyFile(fpath, isAbsolute ? path.dirname(p) : process.cwd())
      )
    })
  }

  public stopWatching() {
    if (this.watcher) {
      this.watcher.close()
    }
  }

  public watch(
    globs: string[],
    opts?: SparkyFilePatternOptions,
    fn?: any
  ): SparkFlow {
    this.files = []
    log.echoStatus(`Watch ${globs}`)
    this.activities.push(
      () =>
        new Promise((resolve, reject) => {
          const chokidarOptions = {
            cwd: opts ? ensureUserPath(opts.base) : null
          }

          this.watcher = chokidar
            .watch(globs, chokidarOptions)
            .on('all', (event, fp) => {
              if (event === 'addDir' || event === 'unlinkDir') {
                return
              }
              if (this.initialWatch) {
                this.files = []
                log.echoStatus(`Changed ${fp}`)
                if (fn) {
                  fn(event, fp)
                }
              }
              const info = parse(fp, opts)
              this.files.push(new SparkyFile(info.filepath, info.root))
              if (this.initialWatch) {
                // call it again
                this.exec()
              }
            })
            .on('ready', () => {
              this.initialWatch = true
              log.echoStatus(`Resolved ${this.files.length} files`)
              this.activities[0] = undefined
              resolve()
            })
        })
    )
    return this
  }

  public completed(fn: any): SparkFlow {
    this.completedCallback = fn
    return this
  }

  /** Gets all user files */
  protected getFiles(
    globs: string[],
    opts?: SparkyFilePatternOptions
  ): Promise<SparkyFile[]> {
    this.files = []
    const getFilePromises: Promise<SparkyFile[]>[] = []
    globs.forEach(g => {
      getFilePromises.push(this.getFile(g, opts))
    })
    return Promise.all(getFilePromises).then(results => {
      this.files = [].concat(...results)
      return this.files
    })
  }

  protected getFile(
    globString,
    opts?: SparkyFilePatternOptions
  ): Promise<SparkyFile[]> {
    const info = parse(globString, opts)

    return new Promise((resolve, reject) => {
      if (!info.isGlob) {
        resolve([new SparkyFile(info.filepath, info.root)])
      } else {
        glob(info.glob, (err, files: string[]) => {
          if (err) {
            reject(err)
          } else {
            resolve(files.map(file => new SparkyFile(file, info.root)))
          }
        })
      }
    })
  }

  /**
   * Removes folder if exists
   * @param dest
   */
  public clean(dest: string): SparkFlow {
    this.activities.push(
      () =>
        new Promise((resolve, reject) => {
          fs.remove(ensureDir(dest), err => {
            if (err) {
              return reject(err)
            }
            return resolve()
          })
        })
    )
    return this
  }

  public plugin(plugin: Plugin): SparkFlow {
    this.activities.push(() => {
      // Promise.all(this.files.map(file => file.copy(dest)))
      // File.createByName(collection, "sd")
    })
    return this
  }

  public each(fn: (file: SparkyFile) => void) {
    this.activities.push(() => {
      return each(this.files, (file: SparkyFile) => {
        return fn(file)
      })
    })
    return this
  }

  public file(mask: string, fn: any) {
    this.activities.push(() => {
      const regexp = string2RegExp(mask)
      return each(this.files, (file: SparkyFile) => {
        if (regexp.test(file.filepath)) {
          log.echoStatus(`Captured file ${file.homePath}`)
          return fn(file)
        }
        return false
      })
    })
    return this
  }

  public next(fn: (file: SparkyFile) => void) {
    this.activities.push(() => {
      return each(this.files, (file: SparkyFile) => fn(file))
    })
    return this
  }

  public dest(dest: string): SparkFlow {
    log.echoStatus(`Copy to ${dest}`)
    this.activities.push(() =>
      Promise.all(
        this.files.map(async file => {
          await file.copy(dest)
        })
      )
    )
    return this
  }

  public exec() {
    return each(
      this.activities,
      (activity: any) => activity && activity()
    ).then(() => {
      if (this.completedCallback) {
        this.completedCallback(this.files)
      }
      this.files = []
    })
  }
}
