// @vitest-environment happy-dom
// Pure presentation primitive — no React Query, no async work — but
// we still set IS_REACT_ACT_ENVIRONMENT to match the project convention
// (ClaimDrawerHeader.test.tsx, KeyboardCheatsheet.test.tsx, etc.).
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;

import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { beforeEach, describe, expect, it } from "vitest";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "./select";

/**
 * Render the Radix Select open with two items so the popover content
 * is in the DOM. Radix's SelectPortal puts the popover in
 * `document.body`, so tests query there.
 *
 * Returns an `unmount` that synchronously tears down the React tree.
 * Radix Select portals its content into `document.body`; between
 * tests we wipe `document.body` in `beforeEach` to be safe — happy-dom
 * + concurrent React can briefly leak portal nodes past unmount in
 * the test environment, which makes the next test see stale
 * `[role="option"]` elements.
 */
function renderOpenSelect(): {
  container: HTMLDivElement;
  unmount: () => void;
} {
  const container = document.createElement("div");
  document.body.appendChild(container);
  const root: Root = createRoot(container);
  act(() => {
    root.render(
      <Select defaultValue="a" open>
        <SelectTrigger>
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="a">Option A</SelectItem>
          <SelectItem value="b">Option B</SelectItem>
        </SelectContent>
      </Select>
    );
  });
  return {
    container,
    unmount: () => {
      act(() => root.unmount());
      container.remove();
    },
  };
}

/**
 * Wait for the popover to be portaled into `document.body`. Radix
 * mounts the SelectContent on a microtask after the trigger's `open`
 * flips, so a single tick is normally enough; we poll defensively.
 */
async function waitForPopover(timeoutMs = 1000): Promise<HTMLElement> {
  const body = document.body;
  const start = Date.now();
  while (!body.querySelector('[role="option"]')) {
    if (Date.now() - start > timeoutMs) {
      throw new Error(
        `waitForPopover: no options rendered within ${timeoutMs}ms (body=${body.innerHTML.slice(0, 200)})`
      );
    }
    await act(async () => {
      await new Promise((r) => setTimeout(r, 0));
    });
  }
  return body;
}

describe("SelectItem", () => {
  // Wipe any leaked portal nodes between tests — happy-dom + Radix's
  // Portal sometimes leaves a stray SelectContent behind after the
  // React tree unmounts, which would let the next test see stale
  // options in document.body and over-count assertions.
  beforeEach(() => {
    document.body.innerHTML = "";
  });

  it("test_selectitem_classname_includes_highlighted_outline_tokens", async () => {
    // The hairline-outline tokens added for a11y are part of the
    // static className string on every SelectItem. Tailwind compiles
    // the `data-[highlighted]:outline-*` variants into CSS that
    // activates only when the data attribute is present, but the
    // className tokens themselves are always in the rendered class
    // string. This test asserts they're present without needing to
    // trigger keyboard navigation (which is finicky in happy-dom).
    const { unmount } = renderOpenSelect();

    const body = await waitForPopover();

    const item = body.querySelector(
      '[role="option"]'
    ) as HTMLElement | null;
    expect(item).not.toBeNull();
    const cls = item?.className ?? "";

    // Three new Tailwind tokens — outline on, 1px wide, accent color.
    expect(cls).toContain("data-[highlighted]:outline");
    expect(cls).toContain("data-[highlighted]:outline-1");
    expect(cls).toContain("data-[highlighted]:outline-accent");

    unmount();
  });

  it("test_selectitem_classname_preserves_existing_background", async () => {
    // The new outline classes must layer ON TOP OF the existing
    // highlighted background, not replace it — `bg-muted` plus a
    // hairline outline reads cleaner than either alone.
    const { unmount } = renderOpenSelect();

    const body = await waitForPopover();
    const item = body.querySelector(
      '[role="option"]'
    ) as HTMLElement | null;
    expect(item).not.toBeNull();
    const cls = item?.className ?? "";

    // Original highlight background is preserved.
    expect(cls).toContain("data-[highlighted]:bg-muted");
    // And the new outline classes coexist with it.
    expect(cls).toContain("data-[highlighted]:outline");

    unmount();
  });

  it("test_renders_all_provided_items", async () => {
    // Sanity check: the popover actually mounted both options. If
    // this ever fails it's a regression in SelectContent itself, not
    // in our outline work, but it's worth pinning down because the
    // other two tests both query the same `[role="option"]` element.
    const { unmount } = renderOpenSelect();

    const body = await waitForPopover();
    const options = body.querySelectorAll('[role="option"]');
    expect(options.length).toBe(2);
    expect(body.textContent).toContain("Option A");
    expect(body.textContent).toContain("Option B");

    unmount();
  });
});