// @vitest-environment jsdom

import { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { StudioPromptBar } from "./StudioPromptBar";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;

describe("StudioPromptBar", () => {
  let container: HTMLDivElement;
  let root: ReturnType<typeof createRoot> | null = null;

  beforeEach(() => {
    container = document.createElement("div");
    document.body.appendChild(container);
    root = null;
  });

  afterEach(() => {
    if (root) {
      act(() => {
        root!.unmount();
      });
      root = null;
    }
    if (container.parentNode) container.remove();
  });

  function render(onClassified = vi.fn(), onFallbackToAssistant = vi.fn()) {
    root = createRoot(container);
    act(() => {
      root!.render(
        <StudioPromptBar
          onClassified={onClassified}
          onFallbackToAssistant={onFallbackToAssistant}
        />,
      );
    });
    return {
      input: container.querySelector("input#studio-prompt-input") as HTMLInputElement,
      form: container.querySelector("form") as HTMLFormElement,
      submit: container.querySelector("button[type='submit']") as HTMLButtonElement,
      onClassified,
      onFallbackToAssistant,
    };
  }

  function type(input: HTMLInputElement, text: string) {
    // React tracks a hidden "value" setter on the native input prototype;
    // we must call it so React sees the change when we dispatch.
    const setter = Object.getOwnPropertyDescriptor(
      window.HTMLInputElement.prototype,
      "value",
    )?.set;
    act(() => {
      setter?.call(input, text);
      input.dispatchEvent(new Event("input", { bubbles: true }));
    });
  }

  function submit(form: HTMLFormElement) {
    act(() => {
      form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
    });
  }

  it("renders the input with the spec placeholder", () => {
    const { input } = render();
    expect(input).not.toBeNull();
    expect(input.getAttribute("placeholder")).toContain("Or just describe it");
    expect(input.getAttribute("placeholder")).toContain("wallpaper");
  });

  it("disables the submit button while the input is empty", () => {
    const { submit: button } = render();
    expect(button.disabled).toBe(true);
  });

  it("enables the submit button once the input has content", () => {
    const { input, submit: button } = render();
    type(input, "diagram of the auth flow");
    expect(button.disabled).toBe(false);
  });

  it("routes a classifiable prompt via onClassified and clears the input", () => {
    const { input, form, onClassified, onFallbackToAssistant } = render();
    type(input, "diagram of the auth flow");
    submit(form);
    expect(onClassified).toHaveBeenCalledTimes(1);
    expect(onClassified).toHaveBeenCalledWith("diagrams", "diagram of the auth flow");
    expect(onFallbackToAssistant).not.toHaveBeenCalled();
    expect(input.value).toBe("");
  });

  it("routes a convert prompt via onClassified", () => {
    const { input, form, onClassified } = render();
    type(input, "convert this pdf to markdown");
    submit(form);
    expect(onClassified).toHaveBeenCalledWith("convert", "convert this pdf to markdown");
  });

  it("falls through to the Assistant when the classifier returns null", () => {
    const { input, form, onClassified, onFallbackToAssistant } = render();
    type(input, "what's the weather today");
    submit(form);
    expect(onClassified).not.toHaveBeenCalled();
    expect(onFallbackToAssistant).toHaveBeenCalledWith("what's the weather today");
    expect(input.value).toBe("");
  });

  it("does nothing when submitting whitespace-only input", () => {
    const { input, form, onClassified, onFallbackToAssistant } = render();
    type(input, "   ");
    submit(form);
    expect(onClassified).not.toHaveBeenCalled();
    expect(onFallbackToAssistant).not.toHaveBeenCalled();
  });

  it("has an accessible label for the submit button", () => {
    const { submit: button } = render();
    expect(button.getAttribute("aria-label")).toBe("Submit prompt");
  });
});
