import {
  type BlockContext,
  type LeafBlock,
  type LeafBlockParser,
  type MarkdownConfig,
} from "@lezer/markdown"

/**
 * LeafBlockParser for Comark block components:
 *
 *   ::component-name{key="val"}
 *   Content lines
 *   ::
 *
 * Uses the same pattern as GFM's TableParser:
 * - nextLine() always returns false — the second line is handled via endLeaf
 * - endLeaf() accumulates body lines and detects the closing ::
 * - finish() creates the BlockComponent node
 */

const BLOCK_COMPONENT_OPEN = /^::([\w-]+)(\{.*\})?\s*$/
const BLOCK_COMPONENT_CLOSE = /^::\s*$/

class BlockComponentParser implements LeafBlockParser {
  name: string
  rawAttrs: string
  bodyLines: string[] = []

  constructor(name: string, rawAttrs: string) {
    this.name = name
    this.rawAttrs = rawAttrs
  }

  /**
   * nextLine handles lines only in the initial leaf-parsing pass.
   * Returning false immediately defers body accumulation to endLeaf,
   * which is called for each subsequent line.
   */
  nextLine(): boolean {
    return false
  }

  finish(cx: BlockContext, leaf: LeafBlock) {
    const content = leaf.content
    const children: import("@lezer/markdown").Element[] = []
    const body = this.bodyLines.join("\n")

    // ComponentName child (after "::")
    children.push(cx.elt("ComponentName", leaf.start + 2, leaf.start + 2 + this.name.length))

    // ComponentAttrs child (if present on the opening line)
    if (this.rawAttrs) {
      const attrsStart = leaf.start + content.indexOf(this.rawAttrs)
      children.push(cx.elt("ComponentAttrs", attrsStart, attrsStart + this.rawAttrs.length))
    }

    // ComponentBody child (if body was accumulated via endLeaf)
    if (body) {
      const bodyStart = leaf.start + content.length + 1 // past opening line + \n
      const bodyEnd = bodyStart + body.length
      children.push(cx.elt("ComponentBody", bodyStart, bodyEnd, [
        ...cx.parser.parseInline(body, bodyStart),
      ]))
    }

    // BlockComponent spans the opening line (children may extend beyond this
    // range via absolute positions — same pattern as GFM's Table parser).
    const endPos = leaf.start + content.length
    cx.addLeafElement(leaf, cx.elt("BlockComponent", leaf.start, endPos, children))
    return true
  }
}

export const blockComponentConfig: MarkdownConfig = {
  defineNodes: [
    { name: "BlockComponent", block: true },
    { name: "ComponentName" },
    { name: "ComponentAttrs" },
    { name: "ComponentBody" },
  ],
  parseBlock: [
    {
      name: "BlockComponent",
      leaf(_cx, leaf) {
        const match = leaf.content.match(BLOCK_COMPONENT_OPEN)
        return match
          ? new BlockComponentParser(match[1]!, match[2] ?? "")
          : null
      },
      endLeaf(_cx, line, leaf) {
        const parser = leaf.parsers.find(
          (p): p is BlockComponentParser => p instanceof BlockComponentParser,
        )
        if (!parser) return false
        // Closing :: ends the block; body content extends it
        if (BLOCK_COMPONENT_CLOSE.test(line.text)) return false
        parser.bodyLines.push(line.text)
        return true
      },
      after: "SetextHeading",
    },
  ],
}
