(function() {
  var bridge = {};
  var stack = [];
  var handlers = {};
  var channel = webkit.messageHandlers['__jsBridge__'];
  var messageId = 0;

  var nextId = function() {
    if (messageId == Number.MAX_SAFE_INTEGER) {
      messageId = 0;
    } else {
      messageId++
    }
    return `${messageId}`;
  };
  var find = function(id) {
    var result;
    for (var i = 0; i <= stack.length; i++) {
      if (stack[i].id === id) {
        result = stack[i];
        stack.splice(i, 1);
        break;
      }
    }
    return result;
  };

  Object.defineProperty(bridge, 'call', {
    value: function(command, args, onSuccess, onError) {
      var message = {
        id: nextId(),
        command: command,
        args: args || '',
        response: onSuccess,
        error: onError,
        promise: promise,
      };
      var promise = new Promise(function(resolve, reject) {
        message.response = onSuccess || resolve;
        message.error = onError || reject;
      });

      stack.push(message);
      channel.postMessage(JSON.stringify(message));

      return promise;
    },
    configurable: false,
  });

  Object.defineProperty(bridge, 'receive', {
    value: function(action) {
      let { type, payload } = action;
      let callback = handlers[type];

      if (!callback) {
        console.warn(`missing a handler to call for: ${type}`);
        return;
      }

      callback(payload);
    },
    configurable: false,
  });

  Object.defineProperty(bridge, 'onReceive', {
    value: function(handler, callback) {
      handlers[handler] = callback;
    },
    configurable: false,
  });

  Object.defineProperty(bridge, 'success', {
    value: function(data) {
      var item = find(data.id);
      if (item) item.response(data.payload);
    },
    configurable: false,
  });

  Object.defineProperty(bridge, 'error', {
    value: function(data) {
      var item = find(data.id);
      if (item) item.error(data.payload);
    },
    configurable: false,
  });

  Object.defineProperty(window, 'JSBridge', {
    value: bridge,
    configurable: false,
  });
})();
