if (!Object.assign) { throw “GraphQLChannel requires Object.assign” }

var GraphQLChannel = {

_getQueryId: function() {
  return Date.now()
},

// Override to log sends and receives
log: function() {
  // pass
},

subscription: {
  _backlog: [],

  // Handle any queries set up before the channel was ready
  connected: function() {
    this._graphQLReady = true
    var _this = this
    this._backlog.forEach(function(req) {
      _this.logOrPerform(req[0], req[1])
    })
  },

  // Called by server-sent events
  received: function(data) {
    GraphQLChannel.log("[GraphQLChannel received]", data)
    var queryId = data.query_id
    var entry = GraphQLChannel.registry.get(queryId)
    if (!entry) {
      // The queryId doesn't exist on this client,
      // it must be for another one of this user's tabs
    } else if (data.close) {
      // An existing request is now completed
      GraphQLChannel.log("[GraphQLChannel closing]", queryId)
      GraphQLChannel.registry.unset(queryId)
    } else {
      // Patch for an existing request
      GraphQLChannel.registry.mergePatch(entry, data.patch)
      entry.onResponse(entry.responseData)
    }
  },

  // @return [Any] A handle for cancelling this query
  fetch: function(queryString, variables, onResponse) {
    var queryId = GraphQLChannel._getQueryId()
    GraphQLChannel.registry.set(queryId, onResponse)

    GraphQLChannel.log("[GraphQLChannel sending]", queryString, variables, queryId)
    this.logOrPerform("fetch", {
      query: queryString,
      variables: JSON.stringify(variables),
      query_id: queryId,
    })
    return queryId
  },

  // Unsubscribe from any future patches
  //
  // @example Ignoring any subscription or deferred fields
  //    var queryHandle = App.GraphqlChannel.fetch(/* ... */)
  //    App.GraphqlChannel.clear(queryHandle)
  //
  // @param [Any] a handle returned by `fetch`
  // @return void
  clear: function(queryId) {
    GraphQLChannel.log("[GraphQLChannel clearing]", queryId)
    GraphQLChannel.registry.unset(queryId)
    this.logOrPerform("clear", {query_id: queryId})
  },

  logOrPerform(operation, params) {
    if (this._graphQLReady) {
      this.perform(operation, params)
    } else {
      this._backlog.push([operation, params])
    }
  },
},

// This registry keeps track of outstanding requests
registry: {
  // {queryId => entry} pairs of outstanding queries.
  // They should be removed when we receive a payload with `close: true`
  _requests: {},

  // Store handlers & results for `queryId`.
  // These handlers & results can be fetched by `.get(queryId)`
  set: function(queryId, onResponse) {
    var entry = {
      queryId: queryId,
      onResponse: onResponse,
      responseData: {},
    }
    this._requests[queryId] = entry
  },

  get: function(queryId) {
    return this._requests[queryId]
  },

  unset: function(queryId) {
    delete this._requests[queryId]
  },

  // Mutate `entry` by merging `patch` into its `responseData`
  mergePatch: function(entry, patch) {
    if (patch.path.length === 0) {
      entry.responseData = patch.value
    } else {
      var targetHash = entry.responseData
      var steps = patch.path.slice(0, patch.path.length - 1)
      var lastKey = patch.path[patch.path.length - 1]
      steps.forEach(function(step) {
        var nextStep = targetHash[step]
        if (nextStep == null) {
          nextStep = targetHash[step] = {}
        }
        targetHash = nextStep
      })
      targetHash[lastKey] = patch.value
    }
  },
}

}