(function(Prism) {
  var handlebars_pattern = /\{\{\{[\s\S]+?\}\}\}|\{\{[\s\S]+?\}\}/;

  Prism.languages.handlebars = Prism.languages.extend('markup', {
    handlebars: {
      pattern: handlebars_pattern,
      inside: {
        delimiter: {
          pattern: /^\{\{\{?|\}\}\}?$/i,
          alias: 'punctuation',
        },
        string: /(["'])(?:\\.|(?!\1)[^\\\r\n])*\1/,
        number: /\b0x[\dA-Fa-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee][+-]?\d+)?/,
        boolean: /\b(?:true|false)\b/,
        block: {
          pattern: /^(\s*~?\s*)[#\/]\S+?(?=\s*~?\s*$|\s)/i,
          lookbehind: true,
          alias: 'keyword',
        },
        brackets: {
          pattern: /\[[^\]]+\]/,
          inside: {
            punctuation: /\[|\]/,
            variable: /[\s\S]+/,
          },
        },
        punctuation: /[!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]/,
        variable: /[^!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~\s]+/,
      },
    },
  });

  // Comments are inserted at top so that they can
  // surround markup
  Prism.languages.insertBefore('handlebars', 'tag', {
    'handlebars-comment': {
      pattern: /\{\{![\s\S]*?\}\}/,
      alias: ['handlebars', 'comment'],
    },
  });

  // Tokenize all inline Handlebars expressions that are wrapped in {{ }} or {{{ }}}
  // This allows for easy Handlebars + markup highlighting
  Prism.hooks.add('before-highlight', function(env) {
    if (env.language !== 'handlebars') {
      return;
    }

    env.tokenStack = [];

    env.backupCode = env.code;
    env.code = env.code.replace(handlebars_pattern, function(match) {
      var i = env.tokenStack.length;
      // Check for existing strings
      while (env.backupCode.indexOf('___HANDLEBARS' + i + '___') !== -1) ++i;

      // Create a sparse array
      env.tokenStack[i] = match;

      return '___HANDLEBARS' + i + '___';
    });
  });

  // Restore env.code for other plugins (e.g. line-numbers)
  Prism.hooks.add('before-insert', function(env) {
    if (env.language === 'handlebars') {
      env.code = env.backupCode;
      delete env.backupCode;
    }
  });

  // Re-insert the tokens after highlighting
  // and highlight them with defined grammar
  Prism.hooks.add('after-highlight', function(env) {
    if (env.language !== 'handlebars') {
      return;
    }

    for (var i = 0, keys = Object.keys(env.tokenStack); i < keys.length; ++i) {
      var k = keys[i];
      var t = env.tokenStack[k];

      // The replace prevents $$, $&, $`, $', $n, $nn from being interpreted as special patterns
      env.highlightedCode = env.highlightedCode.replace(
        '___HANDLEBARS' + k + '___',
        Prism.highlight(t, env.grammar, 'handlebars').replace(/\$/g, '$$$$')
      );
    }

    env.element.innerHTML = env.highlightedCode;
  });
})(Prism);
