import { MantineProvider, v8CssVariablesResolver } from "@mantine/core";
import { extractHeadings, markdownSourceFromString, parseMarkdownTree } from "@src/lib/markdown";
import { MarkdownViewer } from "@src/primitives/MarkdownViewer";
import { MANTINE_THEME } from "@src/theme/mantine";
import type { ReactElement } from "react";
import { renderToString } from "react-dom/server";
import { beforeAll, describe, expect, it } from "vitest";

beforeAll(() => {
    if (typeof window !== "undefined" && typeof window.matchMedia !== "function") {
        Object.defineProperty(window, "matchMedia", {
            writable: true,
            value: (query: string) => ({
                matches: false,
                media: query,
                onchange: null,
                addListener: () => {},
                removeListener: () => {},
                addEventListener: () => {},
                removeEventListener: () => {},
                dispatchEvent: () => false,
            }),
        });
    }
});

function shell(node: ReactElement): string {
    return renderToString(
        <MantineProvider
            theme={MANTINE_THEME}
            cssVariablesResolver={v8CssVariablesResolver}
            forceColorScheme="dark"
        >
            {node}
        </MantineProvider>,
    );
}

describe("MarkdownViewer / JSX and HTML", () => {
    it("renders a JSX component when jsxMode is render", () => {
        const html = shell(<MarkdownViewer content={'<Since v="2.0.0" />'} />);
        expect(html).toContain("2.0.0");
        expect(html).not.toContain("<Since");
    });

    it("shows graceful fallback when jsxMode is graceful and component is missing", () => {
        const html = shell(
            <MarkdownViewer
                content="<UnknownComponent />"
                jsxMode="graceful"
            />,
        );
        expect(html).toContain("unsupported JSX");
    });

    it("parses JSON attribute values on JSX components", () => {
        function ValueEcho({ value }: { value: unknown }) {
            return <span data-value={JSON.stringify(value)} />;
        }

        const html = shell(
            <MarkdownViewer
                content={"<ValueEcho value={42} />"}
                jsxMode="render"
                components={{ ValueEcho }}
            />,
        );
        expect(html).toContain("data-value");
        expect(html).toContain("42");
    });

    it("leaves inline brace text as plain text", () => {
        const html = shell(<MarkdownViewer content="Count: {count}" />);
        expect(html).toContain("{count}");
    });

    it("renders details/summary HTML blocks", () => {
        const html = shell(
            <MarkdownViewer
                content={`<details>
<summary>Toggle</summary>

Hidden **bold** text

</details>`}
                jsxMode="render"
            />,
        );
        expect(html).toContain("Toggle");
        expect(html).toContain("bold");
    });

    it("extractHeadings returns h2 and h3 slugs", () => {
        const source = "## Section\n\n### Sub\n";
        const tree = parseMarkdownTree(source);
        const headings = extractHeadings(tree, markdownSourceFromString(source));
        expect(headings).toHaveLength(2);
        expect(headings[0]?.text).toBe("Section");
        expect(headings[1]?.text).toBe("Sub");
    });

    it("onImage rewrites image src", () => {
        const html = shell(
            <MarkdownViewer
                content='<img src="@ui/pictoAI" />'
                jsxMode="render"
                onImage={(node) => ({ ...node, src: "https://example.com/resolved.png" })}
            />,
        );
        expect(html).toContain("https://example.com/resolved.png");
    });

    it("renders inline paired HTML anchor without duplicating link text", () => {
        const html = shell(
            <MarkdownViewer content={'Visit <a href="https://example.com">link text</a> here.'} />,
        );
        const matches = html.match(/link text/g) ?? [];
        expect(matches).toHaveLength(1);
    });

    it("throws when jsxMode is throw and a JSX component is unsupported", () => {
        expect(() =>
            shell(
                <MarkdownViewer
                    content="<Foo />"
                    jsxMode="throw"
                />,
            ),
        ).toThrow(/Unsupported JSX component <Foo \/>/);
    });

    it("omits unsupported JSX when jsxMode is omit", () => {
        const html = shell(
            <MarkdownViewer
                content="<Unknown />"
                jsxMode="omit"
            />,
        );
        expect(html).not.toContain("unsupported");
        expect(html).not.toContain("Unsupported JSX");
    });

    it("adds heading id and autolink anchor by default", () => {
        const html = shell(<MarkdownViewer content="## Hello" />);
        expect(html).toMatch(/id="hello"/);
        expect(html).toContain('href="#hello"');
    });

    it("renders the same output when tree and source are passed via tree prop", () => {
        const content = "# Tree prop\n\nParagraph body.";
        const tree = parseMarkdownTree(content);
        const source = markdownSourceFromString(content);
        const fromTree = shell(<MarkdownViewer tree={{ tree, source }} />);
        const fromContent = shell(<MarkdownViewer content={content} />);
        expect(fromTree).toBe(fromContent);
    });

    it("uses paragraph renderer override when provided", () => {
        const html = shell(
            <MarkdownViewer
                content="Custom paragraph."
                renderers={{
                    paragraph: () => <span data-custom-paragraph>PARAGRAPH_MARKER</span>,
                }}
            />,
        );
        expect(html).toContain("PARAGRAPH_MARKER");
        expect(html).not.toContain("Custom paragraph.");
    });

    it("renders multi-line paragraph JSX tags", () => {
        const html = shell(<MarkdownViewer content={'<Since\n  v="2.0.0"\n/>'} />);
        expect(html).toContain("2.0.0");
        expect(html).not.toContain("<Since");
    });

    it("renders multiple JSX components on one line", () => {
        const html = shell(
            <MarkdownViewer content={'<Label label="Required" /><Label label="Optional" />'} />,
        );
        expect(html.match(/mantine-Badge-label/g)?.length).toBe(2);
        expect(html).toContain("Required");
        expect(html).toContain("Optional");
    });

    it("renders inline HTML on separate source lines with line breaks", () => {
        const html = shell(
            <MarkdownViewer
                content={`<span>foo</span> <span>bar</span>
<span>hello</span>
<span>world</span>`}
            />,
        );
        expect(html).toContain("foo");
        expect(html).toContain("bar");
        expect(html).toContain("hello");
        expect(html).toContain("world");
        expect(html.match(/<br\s*\/?>/g)?.length).toBe(2);
    });

    it("renders jsx-only paragraphs on separate lines as separate p elements", () => {
        const html = shell(
            <MarkdownViewer
                content={'<Since v="1.5.0" />\n\n<Since v="2.0.0" prefix="Available since" />'}
            />,
        );
        expect(html.match(/mantine-Badge-label/g)?.length).toBe(2);
        const paragraphs = html.match(/<p[^>]*>[\s\S]*?<\/p>/g) ?? [];
        expect(paragraphs.length).toBeGreaterThanOrEqual(2);
    });
});
