import {Tree} from "@lezer/common"
import {MarkdownParser} from ".."

const abbrev: {[abbr: string]: string} = {
  __proto__: null as any,
  CB: "CodeBlock",
  FC: "FencedCode",
  Q: "Blockquote",
  HR: "HorizontalRule",
  BL: "BulletList",
  OL: "OrderedList",
  LI: "ListItem",
  H1: "ATXHeading1",
  H2: "ATXHeading2",
  H3: "ATXHeading3",
  H4: "ATXHeading4",
  H5: "ATXHeading5",
  H6: "ATXHeading6",
  SH1: "SetextHeading1",
  SH2: "SetextHeading2",
  HB: "HTMLBlock",
  PI: "ProcessingInstructionBlock",
  CMB: "CommentBlock",
  LR: "LinkReference",
  P: "Paragraph",
  Esc: "Escape",
  Ent: "Entity",
  BR: "HardBreak",
  Em: "Emphasis",
  St: "StrongEmphasis",
  Ln: "Link",
  Al: "Autolink",
  Im: "Image",
  C: "InlineCode",
  HT: "HTMLTag",
  CM: "Comment",
  Pi: "ProcessingInstruction",
  h: "HeaderMark",
  q: "QuoteMark",
  l: "ListMark",
  L: "LinkMark",
  e: "EmphasisMark",
  c: "CodeMark",
  cI: "CodeInfo",
  cT: "CodeText",
  LT: "LinkTitle",
  LL: "LinkLabel"
}

export class SpecParser {
  constructor(readonly parser: MarkdownParser, readonly localAbbrev?: {[name: string]: string}) {}

  type(name: string) {
    name = (this.localAbbrev && this.localAbbrev[name]) || abbrev[name] || name
    return this.parser.nodeSet.types.find(t => t.name == name)?.id
  }

  parse(spec: string, specName: string) {
    let doc = "", buffer = [], stack: number[] = []
    for (let pos = 0; pos < spec.length; pos++) {
      let ch = spec[pos]
      if (ch == "{") {
        let name = /^(\w+):/.exec(spec.slice(pos + 1)), tag = name && this.type(name[1])
        if (tag == null) throw new Error(`Invalid node opening mark at ${pos} in ${specName}`)
        pos += name![0].length
        stack.push(tag, doc.length, buffer.length)
      } else if (ch == "}") {
        if (!stack.length) throw new Error(`Mismatched node close mark at ${pos} in ${specName}`)
        let bufStart = stack.pop()!, from = stack.pop()!, type = stack.pop()!
        buffer.push(type, from, doc.length, 4 + buffer.length - bufStart)
      } else {
        doc += ch
      }
    }
    if (stack.length) throw new Error(`Unclosed node in ${specName}`)
    return {tree: Tree.build({buffer, nodeSet: this.parser.nodeSet, topID: this.type("Document")!, length: doc.length}), doc}
  }
}

