(function(Prism) {
  // TODO:
  // - Add CSS highlighting inside <style> tags
  // - Add support for multi-line code blocks
  // - Add support for interpolation #{} and !{}
  // - Add support for tag interpolation #[]
  // - Add explicit support for plain text using |
  // - Add support for markup embedded in plain text

  Prism.languages.pug = {
    // Multiline stuff should appear before the rest

    // This handles both single-line and multi-line comments
    comment: {
      pattern: /(^([\t ]*))\/\/.*(?:(?:\r?\n|\r)\2[\t ]+.+)*/m,
      lookbehind: true,
    },

    // All the tag-related part is in lookbehind
    // so that it can be highlighted by the "tag" pattern
    'multiline-script': {
      pattern: /(^([\t ]*)script\b.*\.[\t ]*)(?:(?:\r?\n|\r(?!\n))(?:\2[\t ]+.+|\s*?(?=\r?\n|\r)))+/m,
      lookbehind: true,
      inside: {
        rest: Prism.languages.javascript,
      },
    },

    // See at the end of the file for known filters
    filter: {
      pattern: /(^([\t ]*)):.+(?:(?:\r?\n|\r(?!\n))(?:\2[\t ]+.+|\s*?(?=\r?\n|\r)))+/m,
      lookbehind: true,
      inside: {
        'filter-name': {
          pattern: /^:[\w-]+/,
          alias: 'variable',
        },
      },
    },

    'multiline-plain-text': {
      pattern: /(^([\t ]*)[\w\-#.]+\.[\t ]*)(?:(?:\r?\n|\r(?!\n))(?:\2[\t ]+.+|\s*?(?=\r?\n|\r)))+/m,
      lookbehind: true,
    },
    markup: {
      pattern: /(^[\t ]*)<.+/m,
      lookbehind: true,
      inside: {
        rest: Prism.languages.markup,
      },
    },
    doctype: {
      pattern: /((?:^|\n)[\t ]*)doctype(?: .+)?/,
      lookbehind: true,
    },

    // This handle all conditional and loop keywords
    'flow-control': {
      pattern: /(^[\t ]*)(?:if|unless|else|case|when|default|each|while)\b(?: .+)?/m,
      lookbehind: true,
      inside: {
        each: {
          pattern: /^each .+? in\b/,
          inside: {
            keyword: /\b(?:each|in)\b/,
            punctuation: /,/,
          },
        },
        branch: {
          pattern: /^(?:if|unless|else|case|when|default|while)\b/,
          alias: 'keyword',
        },
        rest: Prism.languages.javascript,
      },
    },
    keyword: {
      pattern: /(^[\t ]*)(?:block|extends|include|append|prepend)\b.+/m,
      lookbehind: true,
    },
    mixin: [
      // Declaration
      {
        pattern: /(^[\t ]*)mixin .+/m,
        lookbehind: true,
        inside: {
          keyword: /^mixin/,
          function: /\w+(?=\s*\(|\s*$)/,
          punctuation: /[(),.]/,
        },
      },
      // Usage
      {
        pattern: /(^[\t ]*)\+.+/m,
        lookbehind: true,
        inside: {
          name: {
            pattern: /^\+\w+/,
            alias: 'function',
          },
          rest: Prism.languages.javascript,
        },
      },
    ],
    script: {
      pattern: /(^[\t ]*script(?:(?:&[^(]+)?\([^)]+\))*[\t ]+).+/m,
      lookbehind: true,
      inside: {
        rest: Prism.languages.javascript,
      },
    },

    'plain-text': {
      pattern: /(^[\t ]*(?!-)[\w\-#.]*[\w\-](?:(?:&[^(]+)?\([^)]+\))*\/?[\t ]+).+/m,
      lookbehind: true,
    },
    tag: {
      pattern: /(^[\t ]*)(?!-)[\w\-#.]*[\w\-](?:(?:&[^(]+)?\([^)]+\))*\/?:?/m,
      lookbehind: true,
      inside: {
        attributes: [
          {
            pattern: /&[^(]+\([^)]+\)/,
            inside: {
              rest: Prism.languages.javascript,
            },
          },
          {
            pattern: /\([^)]+\)/,
            inside: {
              'attr-value': {
                pattern: /(=\s*)(?:\{[^}]*\}|[^,)\r\n]+)/,
                lookbehind: true,
                inside: {
                  rest: Prism.languages.javascript,
                },
              },
              'attr-name': /[\w-]+(?=\s*!?=|\s*[,)])/,
              punctuation: /[!=(),]+/,
            },
          },
        ],
        punctuation: /:/,
      },
    },
    code: [
      {
        pattern: /(^[\t ]*(?:-|!?=)).+/m,
        lookbehind: true,
        inside: {
          rest: Prism.languages.javascript,
        },
      },
    ],
    punctuation: /[.\-!=|]+/,
  };

  var filter_pattern =
    '(^([\\t ]*)):{{filter_name}}(?:(?:\\r?\\n|\\r(?!\\n))(?:\\2[\\t ]+.+|\\s*?(?=\\r?\\n|\\r)))+';

  // Non exhaustive list of available filters and associated languages
  var filters = [
    { filter: 'atpl', language: 'twig' },
    { filter: 'coffee', language: 'coffeescript' },
    'ejs',
    'handlebars',
    'hogan',
    'less',
    'livescript',
    'markdown',
    'mustache',
    'plates',
    { filter: 'sass', language: 'scss' },
    'stylus',
    'swig',
  ];
  var all_filters = {};
  for (var i = 0, l = filters.length; i < l; i++) {
    var filter = filters[i];
    filter =
      typeof filter === 'string'
        ? { filter: filter, language: filter }
        : filter;
    if (Prism.languages[filter.language]) {
      all_filters['filter-' + filter.filter] = {
        pattern: RegExp(
          filter_pattern.replace('{{filter_name}}', filter.filter),
          'm'
        ),
        lookbehind: true,
        inside: {
          'filter-name': {
            pattern: /^:[\w-]+/,
            alias: 'variable',
          },
          rest: Prism.languages[filter.language],
        },
      };
    }
  }

  Prism.languages.insertBefore('pug', 'filter', all_filters);
})(Prism);
