import {
  type InlineContext,
  type MarkdownConfig,
} from "@lezer/markdown"

/**
 * InlineParser for Comark span attributes:
 *
 *   [text content]{.class-name #id key="val"}
 *
 * Produces a `Span` node with optional `SpanAttrs` child.
 *
 * IMPORTANT: The `{...}` must immediately follow `]` without whitespace.
 */

const SPAN_OPEN_RE = /^\[([^\]]*)\]\{/

function parseSpan(cx: InlineContext, next: number, pos: number): number {
  if (next !== 91 /* left square bracket */) return -1

  const text = cx.slice(pos, cx.end)
  const match = text.match(SPAN_OPEN_RE)
  if (!match) return -1

  const body = match[1]!
  let cur = pos + match[0].length

  // Find the closing }
  const rest = cx.slice(cur, cx.end)
  let depth = 1
  let attrEnd = cur
  for (let i = 0; i < rest.length; i++) {
    if (rest[i] === "{") depth++
    else if (rest[i] === "}") {
      depth--
      if (depth === 0) {
        attrEnd = cur + i
        break
      }
    }
  }
  if (depth !== 0) return -1 // unmatched braces

  const rawAttrs = cx.slice(cur, attrEnd + 1)
  cur = attrEnd + 1

  const children: import("@lezer/markdown").Element[] = []

  // SpanBody child (with inline markdown parsing)
  const bodyStart = pos + 1
  const bodyEnd = bodyStart + body.length
  children.push(
    cx.elt("SpanBody", bodyStart, bodyEnd, [
      ...cx.parser.parseInline(body, bodyStart),
    ]),
  )

  // SpanAttrs child
  if (rawAttrs) {
    children.push(
      cx.elt("SpanAttrs", cur - rawAttrs.length, cur),
    )
  }

  cx.addElement(cx.elt("Span", pos, cur, children))
  return cur - pos
}

export const spanAttrConfig: MarkdownConfig = {
  defineNodes: [
    { name: "Span" },
    { name: "SpanBody" },
    { name: "SpanAttrs" },
  ],
  parseInline: [
    {
      name: "Span",
      parse: parseSpan,
      before: "Link",
    },
  ],
}
