import _ from 'lodash/fp'
import {defaultsOn, setOn} from './conversion'
import {throws, tapError} from './lang'

// Core
export let aspect = ({
  name = 'aspect',
  init = _.noop,
  after = _.noop,
  before = _.noop,
  always = _.noop,
  onError = throws
  // ?: interceptParams, interceptResult, wrap
}) => f => {
  let {state = {}} = f
  init(state)
  // Trick to set function.name of anonymous function
  let x = {
    [name]: (...args) => {
      let result
      let error
      return Promise.resolve()
        .then(() => before(args, state))
        .then(() => f(...args))
        .then(r => {
          result = r
        })
        .then(() => after(result, state, args))
        .catch(e => onError(e, state, args))
        .catch(e => {
          error = e
        })
        .then(() => always(state, args))
        .then(() => {
          if (error) throw error
        })
        .then(() => result)
    }
  }
  x[name].state = state
  return x[name]
}

// Example Aspects
let logs = (extend = defaultsOn) => aspect({
  init: extend({ logs: [] }),
  after: (result, state) => state.logs.push(result),
  name: 'logs'
})
let error = (extend = defaultsOn) => aspect({
  init: extend({ error: null }),
  onError: setOn('error'),
  name: 'error'
})
let errors = (extend = defaultsOn) => aspect({
  init: extend({ errors: [] }),
  onError: (e, state) => state.errors.push(e),
  name: 'errors'
})
let status = (extend = defaultsOn) => aspect({
  init: extend({
    status: null,
    processing: false,
    succeeded: false,
    failed: false,
    // Computed get/set properties don't work, probably because lodash extend methods don't support copying them
    setStatus (x) {
      this.status = x
      this.failed = x === 'failed'
      this.succeeded = x === 'succeeded'
      this.processing = x === 'processing'
    }
  }),
  before (params, state) {
    state.setStatus('processing')
  },
  after (result, state) {
    state.setStatus('succeeded')
  },
  onError: tapError((e, state) => {
    state.setStatus('failed')
  }),
  name: 'status'
})
let clearStatus = (timeout = 500) => aspect({
  always (state) {
    setTimeout(() => {
      state.setStatus(null)
    }, timeout)
  },
  name: 'clearStatus'
})
// This is a function just for consistency
let concurrency = () => aspect({
  before (params, state) {
    if (state.processing) {
      throw Error({
        message: 'Concurrent Runs Not Allowed'
      })
    }
  },
  name: 'concurrency'
})

export let aspects = {
  logs,
  error,
  errors,
  status,
  clearStatus,
  concurrency
}

@@ -2115,24 +2115,54 @@
 s (state) %7B%0A
+    if (timeout !== null) %7B%0A  
     setTimeo
@@ -2174,24 +2174,26 @@
  =%3E %7B%0A      
+  
 state.setSta
@@ -2206,16 +2206,18 @@
 ll)%0A    
+  
 %7D, timeo
@@ -2220,16 +2220,22 @@
 imeout)%0A
+    %7D%0A
   %7D,%0A  n
@@ -2500,16 +2500,143 @@
 cy'%0A%7D)%0A%0A
+let command = (extend, timeout) =%3E _.flow(%0A  status(extend),%0A  clearStatus(timeout),%0A  concurrency(extend),%0A  error(extend)%0A)%0A%0A
 export l
@@ -2702,16 +2702,16 @@
 Status,%0A
-
   concur
@@ -2715,11 +2715,22 @@
 currency
+,%0A  command
 %0A%7D%0A

add `command` and null timeout support to clearStatus