import { beforeAll, describe, expect, it } from "vitest"
import { parser } from "@lezer/markdown"
import { GFM } from "@lezer/markdown"
import { createComarkExtensions } from "./index"

let fullParser: ReturnType<typeof parser.configure>

beforeAll(() => {
  fullParser = parser.configure([GFM, ...createComarkExtensions()])
})

function parse(text: string) {
  const tree = fullParser.parse(text)
  const nodes: { name: string; from: number; to: number }[] = []
  tree.iterate({ enter: (n) => nodes.push({ name: n.type.name, from: n.from, to: n.to }) })
  return nodes
}

function nodeNames(text: string): string[] {
  return parse(text).map((n) => n.name)
}

describe("InlineComponent (props only)", () => {
  it("parses inline component with value attr", () => {
    const names = nodeNames(":priority{value=\"high\"}")
    expect(names).toContain("InlineComponent")
    expect(names).toContain("ComponentName")
    expect(names).toContain("ComponentAttrs")
  })

  it("extracts component name", () => {
    const nodes = parse(":status{value=\"active\"}")
    const nameNode = nodes.find((n) => n.name === "ComponentName")
    expect(nameNode).toBeDefined()
    expect(":status{value=\"active\"}".slice(nameNode!.from, nameNode!.to)).toBe("status")
  })

  it("extracts attrs", () => {
    const nodes = parse(":due{value=\"2026-07-01\"}")
    const attrsNode = nodes.find((n) => n.name === "ComponentAttrs")
    expect(attrsNode).toBeDefined()
  })

  it("sits within a paragraph alongside text", () => {
    const names = nodeNames("Task is :priority{value=\"high\"} and :status{value=\"active\"}")
    expect(names.filter((n) => n === "InlineComponent").length).toBe(2)
    expect(names).toContain("ComponentName")
    expect(names).toContain("ComponentAttrs")
  })
})

describe("InlineComponent (body + props)", () => {
  it("parses inline component with body and attrs", () => {
    const names = nodeNames(":badge[Active]{color=\"green\"}")
    expect(names).toContain("InlineComponent")
    expect(names).toContain("ComponentName")
    expect(names).toContain("ComponentBody")
    expect(names).toContain("ComponentAttrs")
  })

  it("parses inline component with body only (no attrs)", () => {
    const names = nodeNames(":icon[Check]")
    expect(names).toContain("InlineComponent")
    expect(names).toContain("ComponentBody")
    expect(names).not.toContain("ComponentAttrs")
  })

  it("extracts body text", () => {
    const nodes = parse(":btn[Click Me]{color=\"blue\"}")
    const bodyNode = nodes.find((n) => n.name === "ComponentBody")
    expect(bodyNode).toBeDefined()
    expect(":btn[Click Me]{color=\"blue\"}".slice(bodyNode!.from, bodyNode!.to)).toBe("Click Me")
  })

  it("sits inline within a paragraph", () => {
    const names = nodeNames("Click :btn[Submit]{type=\"primary\"} to continue.")
    expect(names).toContain("InlineComponent")
  })
})

describe("InlineComponent (standalone — no body, no attrs)", () => {
  it("parses a standalone inline component", () => {
    const names = nodeNames(":icon-check")
    expect(names).toContain("InlineComponent")
    expect(names).toContain("ComponentName")
    expect(names).not.toContain("ComponentBody")
    expect(names).not.toContain("ComponentAttrs")
  })

  it("parses standalone component within text", () => {
    const names = nodeNames("Check out :icon-star in text.")
    expect(names.filter((n) => n === "InlineComponent").length).toBe(1)
  })
})

describe("InlineComponent — no match", () => {
  it("does not match plain colon text", () => {
    const names = nodeNames("just some text: not a component")
    expect(names).not.toContain("InlineComponent")
  })

  it("does not match emoji syntax (single char after colon)", () => {
    const names = nodeNames(":smile:")
    expect(names).not.toContain("InlineComponent")
  })
})
