import { Grid, MantineProvider, Paper, v8CssVariablesResolver } from "@mantine/core";
import { MarkdownEditor } from "@src/primitives/MarkdownEditor";
import { MarkdownViewer } from "@src/primitives/MarkdownViewer";
import { MANTINE_THEME } from "@src/theme/mantine";
import { type FC, useState } from "react";
import { describe, expect, it } from "vitest";
import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
import { md, mdViewer } from "../../_setup/markdown-classes";

const PARITY_FIXTURE = `# Title

Paragraph *italic* and **bold** and \`code\`.

## Section

Visit [link](https://surrealdb.com).

- One
- Two

- [ ] todo one
- [x] todo two **with bold**

\`\`\`ts
return true;
\`\`\`

<details>
<summary>Expand</summary>

Inside details.

</details>
`;

const ParityLayout: FC = () => {
    const [doc] = useState(PARITY_FIXTURE);
    return (
        <MantineProvider
            theme={MANTINE_THEME}
            cssVariablesResolver={v8CssVariablesResolver}
            forceColorScheme="dark"
        >
            <Grid gap="md">
                <Grid.Col span={{ base: 12, md: 6 }}>
                    <Paper
                        p="md"
                        radius="md"
                        withBorder
                    >
                        <MarkdownEditor
                            mode="preview"
                            document={doc}
                            autoFocus={false}
                            mih={200}
                        />
                    </Paper>
                </Grid.Col>
                <Grid.Col span={{ base: 12, md: 6 }}>
                    <Paper
                        p="md"
                        radius="md"
                        withBorder
                    >
                        <MarkdownViewer content={doc} />
                    </Paper>
                </Grid.Col>
            </Grid>
        </MantineProvider>
    );
};

function visibleText(el: Element): string {
    const clone = el.cloneNode(true) as Element;

    for (const n of clone.querySelectorAll(`.${md.markHidden}, .cm-line-folded`)) {
        n.remove();
    }

    return clone.textContent?.replace(/\s+/g, "") ?? "";
}

const PARITY_CLASSES = [md.h1, md.h2, md.strong, md.emphasis, md.codeInline, md.link] as const;

describe("MarkdownViewer / editor parity", () => {
    it("renders checklist text alongside checkboxes in the viewer", async () => {
        mountStory(<ParityLayout />);
        await getEditorView();
        await nextFrame();

        const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
        expect(vrEl).not.toBeNull();
        if (vrEl === null) return;

        const taskItems = vrEl.querySelectorAll(`li.${md.taskItem}`);
        expect(taskItems.length).toBe(2);

        const text = (i: number) =>
            (taskItems[i]?.querySelector(`.${md.taskText}`)?.textContent ?? "").trim();
        expect(text(0)).toContain("todo one");
        expect(text(1)).toContain("todo two");
        expect(taskItems[1]?.querySelector(`.${md.strong}`)?.textContent).toBe("with bold");
    });

    it("uses the same link colour as the editor", async () => {
        mountStory(<ParityLayout />);
        const view = await getEditorView();
        view.contentDOM.blur();
        await nextFrame();

        const cmLink = document.querySelector(`.cm-content .${md.link}`);
        const vwLink = document.querySelector(`.${mdViewer.viewerRoot} .${md.link}`);
        expect(cmLink).not.toBeNull();
        expect(vwLink).not.toBeNull();
        if (cmLink === null || vwLink === null) return;

        expect(getComputedStyle(vwLink).color).toBe(getComputedStyle(cmLink).color);
    });

    it("matches visible text between preview editor (blurred) and MarkdownViewer", async () => {
        mountStory(<ParityLayout />);
        const view = await getEditorView();
        view.contentDOM.blur();
        await nextFrame();
        await nextFrame();

        const cmEl = document.querySelector(".cm-content");
        const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
        expect(cmEl).not.toBeNull();
        expect(vrEl).not.toBeNull();
        if (cmEl === null || vrEl === null) {
            return;
        }

        expect(visibleText(vrEl)).toBe(visibleText(cmEl));
    });

    it("matches counts for shared structural classes", async () => {
        mountStory(<ParityLayout />);
        const view = await getEditorView();
        view.contentDOM.blur();
        await nextFrame();

        const cmEl = document.querySelector(".cm-content");
        const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
        expect(cmEl).not.toBeNull();
        expect(vrEl).not.toBeNull();
        if (cmEl === null || vrEl === null) {
            return;
        }

        for (const cls of PARITY_CLASSES) {
            expect(vrEl.querySelectorAll(`.${cls}`).length).toBe(
                cmEl.querySelectorAll(`.${cls}`).length,
            );
        }
    });

    it("matches computed font size on first heading", async () => {
        mountStory(<ParityLayout />);
        const view = await getEditorView();
        view.contentDOM.blur();
        await nextFrame();

        const cmH1 = document.querySelector(`.cm-content .${md.h1}`);
        const vwH1 = document.querySelector(`.${mdViewer.viewerRoot} .${md.h1}`);
        expect(cmH1).not.toBeNull();
        expect(vwH1).not.toBeNull();
        if (cmH1 === null || vwH1 === null) {
            return;
        }

        const a = getComputedStyle(cmH1);
        const b = getComputedStyle(vwH1);
        expect(a.fontSize).toBe(b.fontSize);
        expect(a.fontWeight).toBe(b.fontWeight);
    });

    it("renders HTML details in both editor preview and viewer", async () => {
        mountStory(<ParityLayout />);
        const view = await getEditorView();
        view.contentDOM.blur();
        await nextFrame();

        const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
        expect(vrEl?.textContent).toContain("Expand");
        expect(vrEl?.textContent).toContain("Inside details");
        expect(document.querySelector(".cm-content")?.textContent).toContain("Inside details");
    });
});
