import {
    JSX_ATTR_NAME_NODE,
    JSX_ATTR_STRING_NODE,
    JSX_EXPRESSION_CONTENT_NODE,
    JSX_EXPRESSION_MARK_NODE,
} from "@src/lib/markdown/editor/jsx-tag";
import { createMarkdownLanguage } from "@src/lib/markdown/editor/language";
import { describe, expect, it } from "vitest";

function parse(text: string) {
    const support = createMarkdownLanguage({ addKeymap: false, pasteURLAsLink: false });
    return support.language.parser.parse(text);
}

function nodeNames(text: string): string[] {
    const tree = parse(text);
    const names: string[] = [];
    tree.iterate({
        enter: (n) => {
            names.push(n.name);
        },
    });
    return names;
}

function htmlTagSpan(text: string): { from: number; to: number } | null {
    const tree = parse(text);
    let span: { from: number; to: number } | null = null;
    tree.iterate({
        enter: (n) => {
            if (n.name === "HTMLTag") span = { from: n.from, to: n.to };
        },
    });
    return span;
}

describe("JsxTag grammar extension", () => {
    it("keeps plain quoted attributes on the built-in HTMLTag parser", () => {
        expect(nodeNames('<Since v="1" />')).toEqual(["Document", "Paragraph", "HTMLTag"]);
        expect(nodeNames('<Since v="1" />')).not.toContain(JSX_EXPRESSION_CONTENT_NODE);
    });

    it("parses a full HTMLTag when the attribute value is a JSX expression", () => {
        const text = '<Component prop={"a"} />';
        expect(htmlTagSpan(text)).toEqual({ from: 0, to: text.length });
        expect(nodeNames(text)).toContain(JSX_EXPRESSION_CONTENT_NODE);
        expect(nodeNames(text)).toContain(JSX_EXPRESSION_MARK_NODE);
    });

    it("does not truncate at > inside a JSX expression", () => {
        const text = "<Component prop={foo > bar} />";
        expect(htmlTagSpan(text)).toEqual({ from: 0, to: text.length });
    });

    it("parses multiple JSX tags on one line", () => {
        const text = '<Tabs defaultValue={"a"}><TabItem label={x} /></Tabs>';
        const tree = parse(text);
        const tags: string[] = [];
        tree.iterate({
            enter: (n) => {
                if (n.name === "HTMLTag") tags.push(text.slice(n.from, n.to));
            },
        });
        expect(tags).toEqual(['<Tabs defaultValue={"a"}>', "<TabItem label={x} />", "</Tabs>"]);
    });

    it("highlights quoted attributes that follow a JSX expression", () => {
        const text = '<Since v={"2.0.0"} t="true" b="false" />';
        expect(htmlTagSpan(text)).toEqual({ from: 0, to: text.length });

        const tree = parse(text);
        const names = collectNodes(tree, text, JSX_ATTR_NAME_NODE);
        const strings = collectNodes(tree, text, JSX_ATTR_STRING_NODE);

        expect(names).toEqual(["v", "t", "b"]);
        expect(strings).toEqual(['"true"', '"false"']);
    });
});

function collectNodes(tree: ReturnType<typeof parse>, text: string, name: string): string[] {
    const out: string[] = [];
    tree.iterate({
        enter: (n) => {
            if (n.name === name) out.push(text.slice(n.from, n.to));
        },
    });
    return out;
}
