import { EditorSelection } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
import { composeStories } from "@storybook/react-vite";
import { describe, expect, it } from "vitest";
import {
    focusAndType,
    mountStory,
    nextFrame,
    preparePreviewEditor,
    scrollEditorTo,
    sleep,
} from "../../_setup/e2e-helpers";
import { md } from "../../_setup/markdown-classes";

const Stories = composeStories(MarkdownStories);

describe("MarkdownEditor / content blocks", () => {
    it("continues an ordered list when Enter is pressed at the end of a numbered line", async () => {
        mountStory(<Stories.Editor />);
        const view = await preparePreviewEditor();
        const doc = view.state.doc.toString();

        const line = "4. Fourth step";
        const idx = doc.indexOf(line);
        expect(idx).toBeGreaterThan(-1);
        const eol = idx + line.length;

        await scrollEditorTo(view, idx);
        view.dispatch({ selection: EditorSelection.cursor(eol) });
        await nextFrame();

        await focusAndType("{Enter}fifth step");
        await nextFrame();

        expect(view.state.doc.toString()).toMatch(/\n5\.\s*fifth step/);
    });

    it("shows blockquote text from the sample document", async () => {
        mountStory(<Stories.Editor />);
        const view = await preparePreviewEditor();
        const doc = view.state.doc.toString();
        expect(doc).toContain("> A blockquote");

        const quotePos = doc.indexOf("> A blockquote");
        await scrollEditorTo(view, quotePos);
        await nextFrame();

        expect(document.querySelector(`.${md.quote}`)?.textContent ?? "").toMatch(/blockquote/i);
    });

    it("renders a horizontal rule as a separator", async () => {
        mountStory(<Stories.Editor />);
        const view = await preparePreviewEditor();
        const hrPos = view.state.doc.toString().indexOf("***");
        expect(hrPos).toBeGreaterThan(-1);
        await scrollEditorTo(view, hrPos);
        view.dispatch({ selection: EditorSelection.cursor(0) });
        await nextFrame();

        let hrs = 0;
        for (let i = 0; i < 40; i++) {
            hrs = document.querySelectorAll(`hr.${md.hr}[aria-label="Horizontal rule"]`).length;
            if (hrs > 0) break;
            await nextFrame();
        }

        expect(hrs).toBeGreaterThan(0);
    });

    it("renders a read-only table preview when the table block is inactive", async () => {
        mountStory(<Stories.Editor />);
        const view = await preparePreviewEditor();
        const doc = view.state.doc.toString();
        const tablePos = doc.indexOf("| Feature | Status");
        const afterTable = doc.indexOf("## HTML table");
        expect(tablePos).toBeGreaterThan(-1);
        expect(afterTable).toBeGreaterThan(-1);
        view.dispatch({
            selection: EditorSelection.cursor(afterTable),
            effects: EditorView.scrollIntoView(tablePos, { y: "center" }),
        });

        let tables = 0;
        for (let i = 0; i < 40; i++) {
            tables = document.querySelectorAll(
                `.cm-editor [data-md-widget="TableWidget"] table.${md.table}`,
            ).length;
            if (tables > 0) break;
            await nextFrame();
        }

        expect(tables).toBeGreaterThan(0);
    });

    it("shows GFM table source when the table block is active", async () => {
        mountStory(<Stories.Editor />);
        const view = await preparePreviewEditor();
        const doc = view.state.doc.toString();
        const marker = "| Feature | Status";
        const idx = doc.indexOf(marker);
        expect(idx).toBeGreaterThan(-1);

        await scrollEditorTo(view, idx);
        view.dispatch({ selection: EditorSelection.cursor(idx + 2) });
        await nextFrame();

        expect(document.querySelector(`.${md.tableWidget}`)).toBeNull();
        expect(view.state.doc.sliceString(idx, idx + marker.length)).toBe(marker);
    });

    it("shows fenced TypeScript sample code in the document", async () => {
        mountStory(<Stories.Editor />);
        const view = await preparePreviewEditor();
        const doc = view.state.doc.toString();
        expect(doc).toContain("function add");

        const codePos = doc.indexOf("function add");
        await scrollEditorTo(view, codePos);
        await nextFrame();

        expect(document.querySelector(".cm-editor")?.textContent ?? "").toContain("function add");
    });

    it("exposes clickable links with an href matching the Markdown target", async () => {
        mountStory(<Stories.Editor />);
        const view = await preparePreviewEditor();
        const linkPos = view.state.doc.toString().indexOf("[SurrealDB website]");
        expect(linkPos).toBeGreaterThan(-1);
        await scrollEditorTo(view, linkPos);
        view.dispatch({ selection: EditorSelection.cursor(0) });
        await nextFrame();

        expect(document.querySelector(`.${md.link}[data-href*='surrealdb.com']`)).not.toBeNull();
    });

    it("renders a diagram for the fenced mermaid block", async () => {
        mountStory(<Stories.Editor />);
        const view = await preparePreviewEditor();
        const mermaidPos = view.state.doc.toString().indexOf("```mermaid");
        expect(mermaidPos).toBeGreaterThan(-1);
        await scrollEditorTo(view, mermaidPos);

        let found = false;
        for (let i = 0; i < 60; i++) {
            found = !!document.querySelector('.cm-editor [aria-label="Mermaid diagram"] svg');
            if (found) break;
            await sleep(250);
        }
        expect(found).toBe(true);
    });
});
