"use strict";

/*
   remark-shortcodes

   A remark plugin to allow custom block-level Wordpress/Hugo-like
   shortcodes to be parsed as part of the Remark Markdown AST. Note
   that this plugin does not apply any transformations, but simply
   parses the shortcodes and adds the relevant data to the AST. The
   start & end blocks are configurable, but the inner shortcode
   including the name and the key="value" pairs must be as expected.

   To apply transformations from the shortcodes to HTML or other
   formats, please see the example in the README.

   e.g
        // with no attributes
        [[ ShortcodeNameA ]]

        // with multiple attributes
        [[ ShortcodeNameC a="b" c="d" ]]

        // with custom start/end Blocks
        {{% ShortcodeNameD a="1" c="2" %}}

        // An example node format for a shortcode with attributes
        {
          type: 'shortcode',
          identifier: 'ShortcodeNameC',
          attributes: {
              a: '1',
              c: '2'
          }
        }

   With thanks to official remark plugins:
   https://github.com/wooorm/remark-breaks/blob/master/index.js
   https://github.com/wooorm/remark-gemoji/blob/master/index.js
*/

module.exports = shortcodes;

function shortcodes(options) {
  var startBlock = (options || {}).startBlock || "[[";
  var endBlock = (options || {}).endBlock || "]]";

  if (!isRemarkParser(this.Parser)) {
    throw new Error("Missing parser, cannot attach `remark-shortcodes`");
  }

  var parser = this.Parser.prototype;
  var compiler = this.Compiler.prototype;

  parser.blockTokenizers.shortcode = shortcodeTokenizer;
  parser.blockMethods.splice(
    parser.blockMethods.indexOf("html"),
    0,
    "shortcode"
  );
  compiler.visitors.shortcode = shortcodeCompiler;

  function locator(value, fromIndex) {
    return value.indexOf(startBlock, fromIndex);
  }

  function shortcodeTokenizer(eat, value, silent) {
    var entireShortcode;
    var innerShortcode;
    var parsedShortcode;
    var endPosition;
    var endBlockPosition;

    if (!value.startsWith(startBlock)) return;

    endBlockPosition = value.indexOf(endBlock, startBlock.length);
    if (endBlockPosition === -1) return;

    endPosition = endBlockPosition + endBlock.length;
    entireShortcode = value.slice(0, endPosition);
    innerShortcode = value.slice(startBlock.length, endBlockPosition);

    parsedShortcode = parseShortcode(innerShortcode);

    // If there is no parsed data, something fishy is up - return nothing.
    if (!parsedShortcode) return;

    /* Exit with true in silent mode after successful parse - never used (yet) */
    /* istanbul ignore if */
    if (silent) {
      return true;
    }

    return eat(entireShortcode)({
      type: "shortcode",
      identifier: parsedShortcode.identifier,
      attributes: parsedShortcode.attributes
    });
  }
  shortcodeTokenizer.locator = locator;

  function shortcodeCompiler(node) {
    var attributes = "";
    var keys = Object.keys(node.attributes || {});
    if (keys.length > 0) {
      attributes =
        " " +
        keys
          .map(function(key) {
            return key + '="' + node.attributes[key] + '"';
          })
          .join(" ");
    }
    return startBlock + " " + node.identifier + attributes + " " + endBlock;
  }
}

/**
 * Parses the inner shortcode to extract shortcode name & key-value attributes.
 * @param {string} innerShortcode - Extracted shortcode from between the start & end blocks.
 */
function parseShortcode(innerShortcode) {
  var trimmedInnerShortcode = innerShortcode.trim();

  // If no shortcode, it was blank between the blocks - return nothing.
  if (!trimmedInnerShortcode) return;

  // If no whitespace, then shortcode is just name with no attributes.
  if (!hasWhiteSpace(trimmedInnerShortcode)) {
    return { identifier: trimmedInnerShortcode, attributes: {} };
  }

  var splitShortcode = trimmedInnerShortcode.match(/^(\S+)\s(.*)/).slice(1);
  var shortcodeName = splitShortcode[0];
  var attributeString = splitShortcode[1];
  var attributes = parseShortcodeAttributes(attributeString);

  // If no attributes parsed, something went wrong - return nothing.
  if (!attributes) return;

  return {
    identifier: shortcodeName,
    attributes: attributes
  };
}

/**
 * Discovers if a string contains any whitespace.
 * @param {string} s - the string to test for whitespace.
 */
function hasWhiteSpace(s) {
  return /\s/g.test(s);
}

/**
 * Parses the key/value attributes from the attribute string.
 * @param {string} attributeString - e.g 'a="b" c=2 e="3"'
 */
function parseShortcodeAttributes(attributeString) {
  var attributes = {};
  var attrMatch = attributeString
    .trim()
    .match(/(?:[\w-]*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^}\s]+))/g);

  if (attrMatch) {
    attrMatch.map(function(item) {
      var split = item.split("=");
      var key = split.shift().trim();
      // Strip surrounding quotes from value, if they exist.
      var val = split
        .join("=")
        .trim()
        .replace(/^"(.*)"$/, "$1");
      attributes[key] = val;
    });
  }
  return attributes;
}

function isRemarkParser(parser) {
  return Boolean(
    parser &&
      parser.prototype &&
      parser.prototype.inlineTokenizers &&
      parser.prototype.inlineTokenizers.break &&
      parser.prototype.inlineTokenizers.break.locator
  );
}

@@ -1366,17 +1366,16 @@
 %0A%0A  if (
-!
 isRemark
@@ -1405,164 +1405,46 @@
     
-throw new Error(%22Missing parser, cannot attach %60remark-shortcodes%60%22);%0A  %7D%0A%0A  var parser = this.Parser.prototype;%0A  var compiler = this.Compiler.prototype;%0A%0A
+var parser = this.Parser.prototype;%0A  
   pa
@@ -1496,16 +1496,18 @@
 enizer;%0A
+  
   parser
@@ -1528,16 +1528,18 @@
 splice(%0A
+  
     pars
@@ -1579,15 +1579,19 @@
     
+  
 0,%0A    
+ 
+ 
 %22sho
@@ -1598,21 +1598,114 @@
 rtcode%22%0A
+  
   );%0A
+  %7D%0A  if (isRemarkCompiler(this.Compiler)) %7B%0A    var compiler = this.Compiler.prototype;%0A  
   compil
@@ -1746,16 +1746,20 @@
 ompiler;
+%0A  %7D
 %0A%0A  func
@@ -5329,12 +5329,103 @@
 ator%0A  );%0A%7D%0A
+%0Afunction isRemarkCompiler(compiler) %7B%0A  return Boolean(compiler && compiler.prototype);%0A%7D%0A

make both compiler and parser optional