text: "use strict";
  
  const createError = require("../common/parser-create-error");
  const parseFrontMatter = require("../utils/front-matter");
  const lineColumnToIndex = require("../utils/line-column-to-index");
  
  // utils
  const utils = require("./utils");
  
  const isSCSS = utils.isSCSS;
  const isSCSSNestedPropertyNode = utils.isSCSSNestedPropertyNode;
  
  function parseValueNodes(nodes) {
    let parenGroup = {
      open: null,
      close: null,
      groups: [],
      type: "paren_group"
    };
    const parenGroupStack = [parenGroup];
    const rootParenGroup = parenGroup;
    let commaGroup = {
      groups: [],
      type: "comma_group"
    };
    const commaGroupStack = [commaGroup];
  
    for (let i = 0; i < nodes.length; ++i) {
      const node = nodes[i];
      const isUnquotedDataURLCall =
        node.type === "func" &&
        node.value === "url" &&
        node.group &&
        node.group.groups &&
        node.group.groups[0] &&
        node.group.groups[0].groups &&
        node.group.groups[0].groups.length > 2 &&
        node.group.groups[0].groups[0].type === "word" &&
        node.group.groups[0].groups[0].value === "data" &&
        node.group.groups[0].groups[1].type === "colon" &&
        node.group.groups[0].groups[1].value === ":";
  
      if (isUnquotedDataURLCall) {
        node.group.groups = [stringifyGroup(node)];
      }
  
      if (node.type === "paren" && node.value === "(") {
        parenGroup = {
          open: node,
          close: null,
          groups: [],
          type: "paren_group"
        };
        parenGroupStack.push(parenGroup);
  
        commaGroup = {
          groups: [],
          type: "comma_group"
        };
        commaGroupStack.push(commaGroup);
      } else if (node.type === "paren" && node.value === ")") {
        if (commaGroup.groups.length) {
          parenGroup.groups.push(commaGroup);
        }
        parenGroup.close = node;
  
        if (commaGroupStack.length === 1) {
          throw new Error("Unbalanced parenthesis");
        }
  
        commaGroupStack.pop();
        commaGroup = commaGroupStack[commaGroupStack.length - 1];
        commaGroup.groups.push(parenGroup);
  
        parenGroupStack.pop();
        parenGroup = parenGroupStack[parenGroupStack.length - 1];
      } else if (node.type === "comma") {
        parenGroup.groups.push(commaGroup);
        commaGroup = {
          groups: [],
          type: "comma_group"
        };
        commaGroupStack[commaGroupStack.length - 1] = commaGroup;
      } else {
        commaGroup.groups.push(node);
      }
    }
    if (commaGroup.groups.length > 0) {
      parenGroup.groups.push(commaGroup);
    }
    return rootParenGroup;
  }
  
  function stringifyGroup(node) {
    if (node.group) {
      return stringifyGroup(node.group);
    }
  
    if (node.groups) {
      return node.groups.reduce((previousValue, currentValue, index) => {
        return (
          previousValue +
          stringifyGroup(currentValue) +
          (currentValue.type === "comma_group" && index !== node.groups.length - 1
            ? ","
            : "")
        );
      }, "");
    }
  
    const before = node.raws && node.raws.before ? node.raws.before : "";
    const value = node.value ? node.value : "";
    const unit = node.unit ? node.unit : "";
    const after = node.raws && node.raws.after ? node.raws.after : "";
  
    return before + value + unit + after;
  }
  
  function flattenGroups(node) {
    if (
      node.type === "paren_group" &&
      !node.open &&
      !node.close &&
      node.groups.length === 1
    ) {
      return flattenGroups(node.groups[0]);
    }
  
    if (node.type === "comma_group" && node.groups.length === 1) {
      return flattenGroups(node.groups[0]);
    }
  
    if (node.type === "paren_group" || node.type === "comma_group") {
      return Object.assign({}, node, { groups: node.groups.map(flattenGroups) });
    }
  
    return node;
  }
  
  function addTypePrefix(node, prefix) {
    if (node && typeof node === "object") {
      delete node.parent;
      for (const key in node) {
        addTypePrefix(node[key], prefix);
        if (key === "type" && typeof node[key] === "string") {
          if (!node[key].startsWith(prefix)) {
            node[key] = prefix + node[key];
          }
        }
      }
    }
    return node;
  }
  
  function addMissingType(node) {
    if (node && typeof node === "object") {
      delete node.parent;
      for (const key in node) {
        addMissingType(node[key]);
      }
      if (!Array.isArray(node) && node.value && !node.type) {
        node.type = "unknown";
      }
    }
    return node;
  }
  
  function parseNestedValue(node) {
    if (node && typeof node === "object") {
      delete node.parent;
      for (const key in node) {
        parseNestedValue(node[key]);
        if (key === "nodes") {
          node.group = flattenGroups(parseValueNodes(node[key]));
          delete node[key];
        }
      }
    }
    return node;
  }
  
  function parseValue(value) {
    const valueParser = require("postcss-values-parser");
  
    let result = null;
  
    try {
      result = valueParser(value, { loose: true }).parse();
    } catch (e) {
      return {
        type: "value-unknown",
        value: value
      };
    }
  
    const parsedResult = parseNestedValue(result);
  
    return addTypePrefix(parsedResult, "value-");
  }
  
  function parseSelector(selector) {
    // If there's a comment inside of a selector, the parser tries to parse
    // the content of the comment as selectors which turns it into complete
    // garbage. Better to print the whole selector as-is and not try to parse
    // and reformat it.
    if (selector.match(/\/\/|\/\*/)) {
      return {
        type: "selector-unknown",
        value: selector.replace(/^ +/, "").replace(/ +$/, "")
      };
    }
  
    const selectorParser = require("postcss-selector-parser");
  
    let result = null;
  
    try {
      selectorParser(result_ => {
        result = result_;
      }).process(selector);
    } catch (e) {
      // Fail silently. It's better to print it as is than to try and parse it
      // Note: A common failure is for SCSS nested properties. `background:
      // none { color: red; }` is parsed as a NestedDeclaration by
      // postcss-scss, while `background: { color: red; }` is parsed as a Rule
      // with a selector ending with a colon. See:
      // https://github.com/postcss/postcss-scss/issues/39
      return {
        type: "selector-unknown",
        value: selector
      };
    }
  
    return addTypePrefix(result, "selector-");
  }
  
  function parseMediaQuery(params) {
    const mediaParser = require("postcss-media-query-parser").default;
  
    let result = null;
  
    try {
      result = mediaParser(params);
    } catch (e) {
      // Ignore bad media queries
      return {
        type: "selector-unknown",
        value: params
      };
    }
  
    return addTypePrefix(addMissingType(result), "media-");
  }
  
  const DEFAULT_SCSS_DIRECTIVE = /(\s*?)(!default).*$/;
  const GLOBAL_SCSS_DIRECTIVE = /(\s*?)(!global).*$/;
  
  function parseNestedCSS(node) {
    if (node && typeof node === "object") {
      delete node.parent;
  
      for (const key in node) {
        parseNestedCSS(node[key]);
      }
  
      if (!node.type) {
        return node;
      }
  
      if (!node.raws) {
        node.raws = {};
      }
  
      let selector = "";
  
      if (typeof node.selector === "string") {
        selector = node.raws.selector
          ? node.raws.selector.scss
            ? node.raws.selector.scss
            : node.raws.selector.raw
          : node.selector;
  
        if (node.raws.between && node.raws.between.trim().length > 0) {
          selector += node.raws.between;
        }
  
        node.raws.selector = selector;
      }
  
      let value = "";
  
      if (typeof node.value === "string") {
        value = node.raws.value
          ? node.raws.value.scss
            ? node.raws.value.scss
            : node.raws.value.raw
          : node.value;
  
        value = value.trim();
  
        node.raws.value = selector;
      }
  
      let params = "";
  
      if (typeof node.params === "string") {
        params = node.raws.params
          ? node.raws.params.scss
            ? node.raws.params.scss
            : node.raws.params.raw
          : node.params;
  
        if (node.raws.afterName && node.raws.afterName.trim().length > 0) {
          params = node.raws.afterName + params;
        }
  
        if (node.raws.between && node.raws.between.trim().length > 0) {
          params = params + node.raws.between;
        }
  
        params = params.trim();
  
        node.raws.params = params;
      }
  
      // Ignore LESS mixin declaration
      if (selector.trim().length > 0) {
        if (selector.startsWith("@") && selector.endsWith(":")) {
          return node;
        }
  
        // Ignore LESS mixins
        if (node.mixin) {
          node.selector = parseValue(selector);
  
          return node;
        }
  
        // Check on SCSS nested property
        if (isSCSSNestedPropertyNode(node)) {
          node.isSCSSNesterProperty = true;
        }
  
        node.selector = parseSelector(selector);
  
        return node;
      }
  
      if (node.type !== "css-comment-yaml" && value.length > 0) {
        const defaultSCSSDirectiveIndex = value.match(DEFAULT_SCSS_DIRECTIVE);
  
        if (defaultSCSSDirectiveIndex) {
          value = value.substring(0, defaultSCSSDirectiveIndex.index);
          node.scssDefault = true;
  
          if (defaultSCSSDirectiveIndex[0].trim() !== "!default") {
            node.raws.scssDefault = defaultSCSSDirectiveIndex[0];
          }
        }
  
        const globalSCSSDirectiveIndex = value.match(GLOBAL_SCSS_DIRECTIVE);
  
        if (globalSCSSDirectiveIndex) {
          value = value.substring(0, globalSCSSDirectiveIndex.index);
          node.scssGlobal = true;
  
          if (globalSCSSDirectiveIndex[0].trim() !== "!global") {
            node.raws.scssGlobal = globalSCSSDirectiveIndex[0];
          }
        }
  
        if (value.startsWith("progid:")) {
          return {
            type: "value-unknown",
            value: value
          };
        }
  
        node.value = parseValue(value);
      }
  
      if (node.type === "css-atrule" && params.length > 0) {
        const name = node.name;
        const lowercasedName = node.name.toLowerCase();
  
        if (name === "warn" || name === "error") {
          node.params = {
            type: "media-unknown",
            value: params
          };
  
          return node;
        }
  
        if (name === "extend" || name === "nest") {
          node.selector = parseSelector(params);
          delete node.params;
  
          return node;
        }
  
        if (name === "at-root") {
          if (/^\(\s*(without|with)\s*:[\s\S]+\)$/.test(params)) {
            node.params = parseValue(params);
          } else {
            node.selector = parseSelector(params);
            delete node.params;
          }
  
          return node;
        }
  
        if (lowercasedName === "import") {
          node.params = parseValue(params);
          return node;
        }
  
        if (
          [
            "namespace",
            "supports",
            "if",
            "else",
            "for",
            "each",
            "while",
            "debug",
            "mixin",
            "include",
            "function",
            "return",
            "define-mixin",
            "add-mixin"
          ].indexOf(name) !== -1
        ) {
          // Remove unnecessary spaces in SCSS variable arguments
          params = params.replace(/(\$\S+?)\s+?\.\.\./, "$1...");
          // Remove unnecessary spaces before SCSS control, mixin and function directives
          params = params.replace(/^(?!if)(\S+)\s+\(/, "$1(");
  
          node.value = parseValue(params);
          delete node.params;
  
          return node;
        }
  
        if (name === "custom-selector") {
          const customSelector = params.match(/:--\S+?\s+/)[0].trim();
  
          node.customSelector = customSelector;
          node.selector = parseSelector(params.substring(customSelector.length));
          delete node.params;
  
          return node;
        }
  
        if (["media", "custom-media"].indexOf(lowercasedName) !== -1) {
          if (params.includes("#{")) {
            // Workaround for media at rule with scss interpolation
            return {
              type: "media-unknown",
              value: params
            };
          }
  
          node.params = parseMediaQuery(params);
  
          return node;
        }
  
        node.params = params;
  
        return node;
      }
    }
  
    return node;
  }
  
  function parseWithParser(parser, text) {
    const parsed = parseFrontMatter(text);
    const frontMatter = parsed.frontMatter;
    text = parsed.content;
  
    let result;
  
    try {
      result = parser.parse(text);
    } catch (e) {
      if (typeof e.line !== "number") {
        throw e;
      }
      throw createError("(postcss) " + e.name + " " + e.reason, { start: e });
    }
  
    result = parseNestedCSS(addTypePrefix(result, "css-"));
  
    if (frontMatter) {
      result.nodes.unshift({
        type: "front-matter",
        value: frontMatter
      });
    }
  
    return result;
  }
  
  function requireParser(isSCSSParser) {
    if (isSCSSParser) {
      return require("postcss-scss");
    }
  
    // TODO: Remove this hack when this issue is fixed:
    // https://github.com/shellscape/postcss-less/issues/88
    const LessParser = require("postcss-less/dist/less-parser");
    LessParser.prototype.atrule = function() {
      return Object.getPrototypeOf(LessParser.prototype).atrule.apply(
        this,
        arguments
      );
    };
  
    return require("postcss-less");
  }
  
  function parse(text, parsers, opts) {
    const hasExplicitParserChoice =
      opts.parser === "less" || opts.parser === "scss";
    const isSCSSParser = isSCSS(opts.parser, text);
  
    try {
      return parseWithParser(requireParser(isSCSSParser), text);
    } catch (originalError) {
      if (hasExplicitParserChoice) {
        throw originalError;
      }
  
      try {
        return parseWithParser(requireParser(!isSCSSParser), text);
      } catch (_secondError) {
        throw originalError;
      }
    }
  }
  
  const parser = {
    parse,
    astFormat: "postcss",
    locStart(node) {
      if (node.source) {
        return lineColumnToIndex(node.source.start, node.source.input.css) - 1;
      }
      return null;
    },
    locEnd(node) {
      const endNode = node.nodes && node.nodes[node.nodes.length - 1];
      if (endNode && node.source && !node.source.end) {
        node = endNode;
      }
      if (node.source) {
        return lineColumnToIndex(node.source.end, node.source.input.css);
      }
      return null;
    }
  };
  
  // Export as a plugin so we can reuse the same bundle for UMD loading
  module.exports = {
    parsers: {
      css: parser,
      less: parser,
      scss: parser
    }
  };
  