var TOKEN_STYLES = {
  addition: "inserted",
  attributes: "propertyName",
  bold: "strong",
  cite: "keyword",
  code: "monospace",
  definitionList: "list",
  deletion: "deleted",
  div: "punctuation",
  em: "emphasis",
  footnote: "variable",
  footCite: "qualifier",
  header: "heading",
  html: "comment",
  image: "atom",
  italic: "emphasis",
  link: "link",
  linkDefinition: "link",
  list1: "list",
  list2: "list.special",
  list3: "list",
  notextile: "string.special",
  pre: "operator",
  p: "content",
  quote: "bracket",
  span: "quote",
  specialChar: "character",
  strong: "strong",
  sub: "content.special",
  sup: "content.special",
  table: "variableName.special",
  tableHeading: "operator"
};
function startNewLine(stream, state) {
  state.mode = Modes.newLayout;
  state.tableHeading = false;
  if (state.layoutType === "definitionList" && state.spanningLayout && stream.match(RE("definitionListEnd"), false))
    state.spanningLayout = false;
}
function handlePhraseModifier(stream, state, ch) {
  if (ch === "_") {
    if (stream.eat("_"))
      return togglePhraseModifier(stream, state, "italic", /__/, 2);
    else
      return togglePhraseModifier(stream, state, "em", /_/, 1);
  }
  if (ch === "*") {
    if (stream.eat("*")) {
      return togglePhraseModifier(stream, state, "bold", /\*\*/, 2);
    }
    return togglePhraseModifier(stream, state, "strong", /\*/, 1);
  }
  if (ch === "[") {
    if (stream.match(/\d+\]/))
      state.footCite = true;
    return tokenStyles(state);
  }
  if (ch === "(") {
    var spec = stream.match(/^(r|tm|c)\)/);
    if (spec)
      return TOKEN_STYLES.specialChar;
  }
  if (ch === "<" && stream.match(/(\w+)[^>]+>[^<]+<\/\1>/))
    return TOKEN_STYLES.html;
  if (ch === "?" && stream.eat("?"))
    return togglePhraseModifier(stream, state, "cite", /\?\?/, 2);
  if (ch === "=" && stream.eat("="))
    return togglePhraseModifier(stream, state, "notextile", /==/, 2);
  if (ch === "-" && !stream.eat("-"))
    return togglePhraseModifier(stream, state, "deletion", /-/, 1);
  if (ch === "+")
    return togglePhraseModifier(stream, state, "addition", /\+/, 1);
  if (ch === "~")
    return togglePhraseModifier(stream, state, "sub", /~/, 1);
  if (ch === "^")
    return togglePhraseModifier(stream, state, "sup", /\^/, 1);
  if (ch === "%")
    return togglePhraseModifier(stream, state, "span", /%/, 1);
  if (ch === "@")
    return togglePhraseModifier(stream, state, "code", /@/, 1);
  if (ch === "!") {
    var type = togglePhraseModifier(stream, state, "image", /(?:\([^\)]+\))?!/, 1);
    stream.match(/^:\S+/);
    return type;
  }
  return tokenStyles(state);
}
function togglePhraseModifier(stream, state, phraseModifier, closeRE, openSize) {
  var charBefore = stream.pos > openSize ? stream.string.charAt(stream.pos - openSize - 1) : null;
  var charAfter = stream.peek();
  if (state[phraseModifier]) {
    if ((!charAfter || /\W/.test(charAfter)) && charBefore && /\S/.test(charBefore)) {
      var type = tokenStyles(state);
      state[phraseModifier] = false;
      return type;
    }
  } else if ((!charBefore || /\W/.test(charBefore)) && charAfter && /\S/.test(charAfter) && stream.match(new RegExp("^.*\\S" + closeRE.source + "(?:\\W|$)"), false)) {
    state[phraseModifier] = true;
    state.mode = Modes.attributes;
  }
  return tokenStyles(state);
}
function tokenStyles(state) {
  var disabled = textileDisabled(state);
  if (disabled)
    return disabled;
  var styles = [];
  if (state.layoutType)
    styles.push(TOKEN_STYLES[state.layoutType]);
  styles = styles.concat(activeStyles(state, "addition", "bold", "cite", "code", "deletion", "em", "footCite", "image", "italic", "link", "span", "strong", "sub", "sup", "table", "tableHeading"));
  if (state.layoutType === "header")
    styles.push(TOKEN_STYLES.header + "-" + state.header);
  return styles.length ? styles.join(" ") : null;
}
function textileDisabled(state) {
  var type = state.layoutType;
  switch (type) {
    case "notextile":
    case "code":
    case "pre":
      return TOKEN_STYLES[type];
    default:
      if (state.notextile)
        return TOKEN_STYLES.notextile + (type ? " " + TOKEN_STYLES[type] : "");
      return null;
  }
}
function activeStyles(state) {
  var styles = [];
  for (var i = 1; i < arguments.length; ++i) {
    if (state[arguments[i]])
      styles.push(TOKEN_STYLES[arguments[i]]);
  }
  return styles;
}
function blankLine(state) {
  var spanningLayout = state.spanningLayout, type = state.layoutType;
  for (var key in state)
    if (state.hasOwnProperty(key))
      delete state[key];
  state.mode = Modes.newLayout;
  if (spanningLayout) {
    state.layoutType = type;
    state.spanningLayout = true;
  }
}
var REs = {
  cache: {},
  single: {
    bc: "bc",
    bq: "bq",
    definitionList: /- .*?:=+/,
    definitionListEnd: /.*=:\s*$/,
    div: "div",
    drawTable: /\|.*\|/,
    foot: /fn\d+/,
    header: /h[1-6]/,
    html: /\s*<(?:\/)?(\w+)(?:[^>]+)?>(?:[^<]+<\/\1>)?/,
    link: /[^"]+":\S/,
    linkDefinition: /\[[^\s\]]+\]\S+/,
    list: /(?:#+|\*+)/,
    notextile: "notextile",
    para: "p",
    pre: "pre",
    table: "table",
    tableCellAttributes: /[\/\\]\d+/,
    tableHeading: /\|_\./,
    tableText: /[^"_\*\[\(\?\+~\^%@|-]+/,
    text: /[^!"_=\*\[\(<\?\+~\^%@-]+/
  },
  attributes: {
    align: /(?:<>|<|>|=)/,
    selector: /\([^\(][^\)]+\)/,
    lang: /\[[^\[\]]+\]/,
    pad: /(?:\(+|\)+){1,2}/,
    css: /\{[^\}]+\}/
  },
  createRe: function(name) {
    switch (name) {
      case "drawTable":
        return REs.makeRe("^", REs.single.drawTable, "$");
      case "html":
        return REs.makeRe("^", REs.single.html, "(?:", REs.single.html, ")*", "$");
      case "linkDefinition":
        return REs.makeRe("^", REs.single.linkDefinition, "$");
      case "listLayout":
        return REs.makeRe("^", REs.single.list, RE("allAttributes"), "*\\s+");
      case "tableCellAttributes":
        return REs.makeRe("^", REs.choiceRe(REs.single.tableCellAttributes, RE("allAttributes")), "+\\.");
      case "type":
        return REs.makeRe("^", RE("allTypes"));
      case "typeLayout":
        return REs.makeRe("^", RE("allTypes"), RE("allAttributes"), "*\\.\\.?", "(\\s+|$)");
      case "attributes":
        return REs.makeRe("^", RE("allAttributes"), "+");
      case "allTypes":
        return REs.choiceRe(REs.single.div, REs.single.foot, REs.single.header, REs.single.bc, REs.single.bq, REs.single.notextile, REs.single.pre, REs.single.table, REs.single.para);
      case "allAttributes":
        return REs.choiceRe(REs.attributes.selector, REs.attributes.css, REs.attributes.lang, REs.attributes.align, REs.attributes.pad);
      default:
        return REs.makeRe("^", REs.single[name]);
    }
  },
  makeRe: function() {
    var pattern = "";
    for (var i = 0; i < arguments.length; ++i) {
      var arg = arguments[i];
      pattern += typeof arg === "string" ? arg : arg.source;
    }
    return new RegExp(pattern);
  },
  choiceRe: function() {
    var parts = [arguments[0]];
    for (var i = 1; i < arguments.length; ++i) {
      parts[i * 2 - 1] = "|";
      parts[i * 2] = arguments[i];
    }
    parts.unshift("(?:");
    parts.push(")");
    return REs.makeRe.apply(null, parts);
  }
};
function RE(name) {
  return REs.cache[name] || (REs.cache[name] = REs.createRe(name));
}
var Modes = {
  newLayout: function(stream, state) {
    if (stream.match(RE("typeLayout"), false)) {
      state.spanningLayout = false;
      return (state.mode = Modes.blockType)(stream, state);
    }
    var newMode;
    if (!textileDisabled(state)) {
      if (stream.match(RE("listLayout"), false))
        newMode = Modes.list;
      else if (stream.match(RE("drawTable"), false))
        newMode = Modes.table;
      else if (stream.match(RE("linkDefinition"), false))
        newMode = Modes.linkDefinition;
      else if (stream.match(RE("definitionList")))
        newMode = Modes.definitionList;
      else if (stream.match(RE("html"), false))
        newMode = Modes.html;
    }
    return (state.mode = newMode || Modes.text)(stream, state);
  },
  blockType: function(stream, state) {
    var match, type;
    state.layoutType = null;
    if (match = stream.match(RE("type")))
      type = match[0];
    else
      return (state.mode = Modes.text)(stream, state);
    if (match = type.match(RE("header"))) {
      state.layoutType = "header";
      state.header = parseInt(match[0][1]);
    } else if (type.match(RE("bq"))) {
      state.layoutType = "quote";
    } else if (type.match(RE("bc"))) {
      state.layoutType = "code";
    } else if (type.match(RE("foot"))) {
      state.layoutType = "footnote";
    } else if (type.match(RE("notextile"))) {
      state.layoutType = "notextile";
    } else if (type.match(RE("pre"))) {
      state.layoutType = "pre";
    } else if (type.match(RE("div"))) {
      state.layoutType = "div";
    } else if (type.match(RE("table"))) {
      state.layoutType = "table";
    }
    state.mode = Modes.attributes;
    return tokenStyles(state);
  },
  text: function(stream, state) {
    if (stream.match(RE("text")))
      return tokenStyles(state);
    var ch = stream.next();
    if (ch === '"')
      return (state.mode = Modes.link)(stream, state);
    return handlePhraseModifier(stream, state, ch);
  },
  attributes: function(stream, state) {
    state.mode = Modes.layoutLength;
    if (stream.match(RE("attributes")))
      return TOKEN_STYLES.attributes;
    else
      return tokenStyles(state);
  },
  layoutLength: function(stream, state) {
    if (stream.eat(".") && stream.eat("."))
      state.spanningLayout = true;
    state.mode = Modes.text;
    return tokenStyles(state);
  },
  list: function(stream, state) {
    var match = stream.match(RE("list"));
    state.listDepth = match[0].length;
    var listMod = (state.listDepth - 1) % 3;
    if (!listMod)
      state.layoutType = "list1";
    else if (listMod === 1)
      state.layoutType = "list2";
    else
      state.layoutType = "list3";
    state.mode = Modes.attributes;
    return tokenStyles(state);
  },
  link: function(stream, state) {
    state.mode = Modes.text;
    if (stream.match(RE("link"))) {
      stream.match(/\S+/);
      return TOKEN_STYLES.link;
    }
    return tokenStyles(state);
  },
  linkDefinition: function(stream) {
    stream.skipToEnd();
    return TOKEN_STYLES.linkDefinition;
  },
  definitionList: function(stream, state) {
    stream.match(RE("definitionList"));
    state.layoutType = "definitionList";
    if (stream.match(/\s*$/))
      state.spanningLayout = true;
    else
      state.mode = Modes.attributes;
    return tokenStyles(state);
  },
  html: function(stream) {
    stream.skipToEnd();
    return TOKEN_STYLES.html;
  },
  table: function(stream, state) {
    state.layoutType = "table";
    return (state.mode = Modes.tableCell)(stream, state);
  },
  tableCell: function(stream, state) {
    if (stream.match(RE("tableHeading")))
      state.tableHeading = true;
    else
      stream.eat("|");
    state.mode = Modes.tableCellAttributes;
    return tokenStyles(state);
  },
  tableCellAttributes: function(stream, state) {
    state.mode = Modes.tableText;
    if (stream.match(RE("tableCellAttributes")))
      return TOKEN_STYLES.attributes;
    else
      return tokenStyles(state);
  },
  tableText: function(stream, state) {
    if (stream.match(RE("tableText")))
      return tokenStyles(state);
    if (stream.peek() === "|") {
      state.mode = Modes.tableCell;
      return tokenStyles(state);
    }
    return handlePhraseModifier(stream, state, stream.next());
  }
};
const textile = {
  startState: function() {
    return { mode: Modes.newLayout };
  },
  token: function(stream, state) {
    if (stream.sol())
      startNewLine(stream, state);
    return state.mode(stream, state);
  },
  blankLine
};
export { textile };
