// @vitest-environment jsdom
//
// Markdown wrapper contract. Scope: the customisations our wrapper
// adds on top of @assistant-ui/react-markdown's MarkdownTextPrimitive.
// The primitive itself needs a deep assistant-ui MessagePart context
// to render, so we mock it and verify the wrapper:
//   - mounts the primitive with `remark-gfm` in remarkPlugins,
//   - forwards the `smooth` prop and the `text` content,
//   - passes a `components` map with our custom Blockquote (warning
//     variant when text starts with the ⚠️ glyph), TableWithScroll,
//     ShikiSyntaxHighlighter, and CodeHeader entries.
//
// Then we exercise the captured custom components directly against
// jsdom to assert their per-component contracts (warning class,
// table-wrap container, copy-button click).

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { cleanup, fireEvent, render } from "@testing-library/react";
import remarkGfm from "remark-gfm";

vi.mock("../../hooks/useShikiTheme", () => ({
  useShikiTheme: () => ({ theme: "vitesse-dark", appearance: "dark" }),
}));

vi.mock("../../lib/highlighter", () => ({
  ensureThemeLoaded: vi.fn().mockResolvedValue("vitesse-dark"),
  getHighlighter: vi.fn().mockResolvedValue({
    codeToHtml: () => "<pre><code>highlighted</code></pre>",
  }),
  langKeyForExt: (s: string) => s,
  loadLanguage: vi.fn().mockResolvedValue(undefined),
}));

interface PrimitiveCall {
  text: string;
  smooth: boolean;
  remarkPlugins: unknown[];
  components: Record<string, React.ComponentType<unknown>>;
}

const primitiveCalls: PrimitiveCall[] = [];

vi.mock("@assistant-ui/react-markdown", () => ({
  MarkdownTextPrimitive: (props: {
    preprocess: () => string;
    smooth?: boolean;
    remarkPlugins?: unknown[];
    className?: string;
    components?: Record<string, React.ComponentType<unknown>>;
  }) => {
    primitiveCalls.push({
      text: props.preprocess(),
      smooth: !!props.smooth,
      remarkPlugins: props.remarkPlugins ?? [],
      components: props.components ?? {},
    });
    return <div data-testid="markdown-primitive" className={props.className} />;
  },
}));

import { Markdown } from "./Markdown";

beforeEach(() => {
  primitiveCalls.length = 0;
});

afterEach(() => {
  cleanup();
});

describe("Markdown wrapper config", () => {
  it("renders the assistant-ui markdown primitive", () => {
    const { getByTestId } = render(<Markdown text="hi" />);
    expect(getByTestId("markdown-primitive")).toBeTruthy();
  });

  it("forwards the source text via preprocess", () => {
    render(<Markdown text="hello world" />);
    expect(primitiveCalls).toHaveLength(1);
    expect(primitiveCalls[0]!.text).toBe("hello world");
  });

  it("defaults smooth=false and forwards smooth=true on demand", () => {
    render(<Markdown text="a" />);
    render(<Markdown text="b" smooth />);
    expect(primitiveCalls[0]!.smooth).toBe(false);
    expect(primitiveCalls[1]!.smooth).toBe(true);
  });

  it("registers remark-gfm in the plugin list", () => {
    render(<Markdown text="x" />);
    expect(primitiveCalls[0]!.remarkPlugins).toContain(remarkGfm);
  });

  it("registers cockpit-specific component overrides", () => {
    render(<Markdown text="x" />);
    const keys = Object.keys(primitiveCalls[0]!.components);
    expect(keys).toEqual(
      expect.arrayContaining([
        "SyntaxHighlighter",
        "CodeHeader",
        "table",
        "blockquote",
      ]),
    );
  });

  it("attaches the cockpit-markdown class for global styling", () => {
    const { container } = render(<Markdown text="x" />);
    const node = container.querySelector(".cockpit-markdown");
    expect(node).not.toBeNull();
  });
});

describe("Blockquote override", () => {
  function getBlockquote(): React.ComponentType<{
    children: React.ReactNode;
  }> {
    render(<Markdown text="x" />);
    const Comp = primitiveCalls.at(-1)!.components.blockquote;
    return Comp as React.ComponentType<{ children: React.ReactNode }>;
  }

  it("applies the warning variant when the text starts with the warning glyph", () => {
    const Blockquote = getBlockquote();
    const { container } = render(<Blockquote>⚠️ context reset</Blockquote>);
    const bq = container.querySelector("blockquote");
    expect(bq).not.toBeNull();
    expect(bq?.className).toContain("cockpit-callout-warn");
  });

  it("uses no warning class for plain text", () => {
    const Blockquote = getBlockquote();
    const { container } = render(<Blockquote>just a quote</Blockquote>);
    const bq = container.querySelector("blockquote");
    expect(bq).not.toBeNull();
    expect(bq?.className ?? "").not.toContain("cockpit-callout-warn");
  });

  it("strips leading whitespace before checking for the warning glyph", () => {
    const Blockquote = getBlockquote();
    const { container } = render(<Blockquote>   ⚠️ warning</Blockquote>);
    const bq = container.querySelector("blockquote");
    expect(bq?.className).toContain("cockpit-callout-warn");
  });

  it("walks nested React children when inspecting the text", () => {
    const Blockquote = getBlockquote();
    const { container } = render(
      <Blockquote>
        <span>
          <strong>⚠️</strong> nested warning
        </span>
      </Blockquote>,
    );
    expect(container.querySelector("blockquote")?.className).toContain(
      "cockpit-callout-warn",
    );
  });
});

describe("TableWithScroll override", () => {
  function getTable(): React.ComponentType<{
    children: React.ReactNode;
  }> {
    render(<Markdown text="x" />);
    const Comp = primitiveCalls.at(-1)!.components.table;
    return Comp as React.ComponentType<{ children: React.ReactNode }>;
  }

  it("wraps the rendered table in a scroll container", () => {
    const Table = getTable();
    const { container } = render(
      <Table>
        <tbody>
          <tr>
            <td>cell</td>
          </tr>
        </tbody>
      </Table>,
    );
    const wrap = container.querySelector(".cockpit-table-wrap");
    expect(wrap).not.toBeNull();
    expect(wrap?.querySelector("table")).not.toBeNull();
    expect(wrap?.querySelector("td")?.textContent).toBe("cell");
  });
});

describe("CodeHeader override", () => {
  function getCodeHeader(): React.ComponentType<{
    language?: string;
    code: string;
  }> {
    render(<Markdown text="x" />);
    return primitiveCalls.at(-1)!.components.CodeHeader as React.ComponentType<{
      language?: string;
      code: string;
    }>;
  }

  it("renders the language label", () => {
    const Header = getCodeHeader();
    const { container } = render(<Header language="rust" code="fn main(){}" />);
    expect(container.textContent).toContain("rust");
  });

  it("falls back to 'text' when no language is provided", () => {
    const Header = getCodeHeader();
    const { container } = render(<Header code="abc" />);
    expect(container.textContent).toContain("text");
  });

  it("copies the raw source to the clipboard when the copy button is clicked", () => {
    const Header = getCodeHeader();
    const writeText = vi.fn().mockResolvedValue(undefined);
    Object.defineProperty(navigator, "clipboard", {
      value: { writeText },
      configurable: true,
    });
    const { getByText } = render(<Header language="js" code="alert('hi')" />);
    fireEvent.click(getByText("copy"));
    expect(writeText).toHaveBeenCalledWith("alert('hi')");
  });
});
