import {
    collectHtmlBlockTokens,
    collectParagraphJsxDescriptors,
    findDescriptorForOpenBlock,
    isBlockHtmlNodeDescriptor,
    outermostHtmlDescriptors,
    pairDocumentHtmlBlocks,
    pairHtmlTokens,
    pairInlineHtmlTags,
} from "@src/lib/markdown/tree/html";
import { parseMarkdownTree } from "@src/lib/markdown/tree/parse";
import { markdownSourceFromString } from "@src/lib/markdown/tree/source";
import { describe, expect, it } from "vitest";

const TABLE_MARKDOWN = `<table>
<thead><tr><th>Fn</th></tr></thead>
<tbody><tr><td>**bold**</td></tr></tbody>
</table>`;

describe("HTML descriptors", () => {
    it("findDescriptorForOpenBlock picks the outermost table, not nested th/td", () => {
        const tree = parseMarkdownTree(TABLE_MARKDOWN);
        const node = tree.topNode.firstChild;
        expect(node?.name).toBe("HTMLBlock");
        if (!node) return;

        const source = markdownSourceFromString(TABLE_MARKDOWN);
        const cross = pairDocumentHtmlBlocks([node], source);
        const picked = findDescriptorForOpenBlock(node, cross);

        expect(picked?.tagName).toBe("table");
        expect(picked?.closeTo).not.toBeNull();
    });

    it("outermostHtmlDescriptors keeps only the table for a single HTML block", () => {
        const tree = parseMarkdownTree(TABLE_MARKDOWN);
        const node = tree.topNode.firstChild;
        expect(node).toBeDefined();
        if (!node) return;

        const source = markdownSourceFromString(TABLE_MARKDOWN);
        const tokens = collectHtmlBlockTokens(node, source);
        const outer = outermostHtmlDescriptors(pairHtmlTokens(tokens));

        expect(outer).toHaveLength(1);
        expect(outer[0]?.tagName).toBe("table");
    });

    it("treats inline <Since /> as non-block inside a paragraph", () => {
        const text = 'Text <Since v="1" /> end.';
        const source = markdownSourceFromString(text);
        const tree = parseMarkdownTree(text);
        const para = tree.topNode.firstChild;
        expect(para?.name).toBe("Paragraph");
        if (!para) return;

        const descriptors = pairInlineHtmlTags(para, source);
        const since = descriptors.find((d) => d.isJsx && d.rawTagName === "Since");
        expect(since).toBeDefined();
        // Unregistered components default to inline.
        expect(since && isBlockHtmlNodeDescriptor(since, undefined)).toBe(false);
        // Explicitly registering as block flips the resolved block-ness.
        expect(
            since &&
                isBlockHtmlNodeDescriptor(since, {
                    Since: { component: () => null, block: true },
                }),
        ).toBe(true);
    });

    it("pairs multiple self-closing tags in one paragraph", () => {
        const text = '<Label label="Required" /><Label label="Optional" />';
        const source = markdownSourceFromString(text);
        const tree = parseMarkdownTree(text);
        const para = tree.topNode.firstChild;
        expect(para?.name).toBe("Paragraph");
        if (!para) return;

        expect(pairInlineHtmlTags(para, source)).toHaveLength(2);
    });

    it("collectParagraphJsxDescriptors scans plain paragraphs without HTMLTag nodes", () => {
        const text = 'Before <Card label="x" /> after.';
        const source = markdownSourceFromString(text);
        const tree = parseMarkdownTree(text);
        const para = tree.topNode.firstChild;
        expect(para?.name).toBe("Paragraph");
        if (!para) return;

        const descriptors = collectParagraphJsxDescriptors(para, source);
        expect(descriptors).toHaveLength(1);
        expect(descriptors[0]?.rawTagName).toBe("Card");
    });
});
