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

// ─── Stubs ──────────────────────────────────────────────────────────────────
// ChatInput and VoiceWaveform both pull in heavy dependencies (contexts,
// canvas APIs) that aren't relevant to what this composite adds. We stub
// them with minimal stand-ins so the test focuses on layout composition.

vi.mock("../ChatInput", () => ({
  ChatInput: (props: Record<string, unknown>) => (
    <button
      type="button"
      data-testid="chat-input-stub"
      data-placeholder={String(props.placeholder ?? "")}
      data-submitting={String(props.isSubmitting ?? "")}
      data-disabled={String(props.disabled ?? "")}
      data-enable-voice={String(props.enableVoiceInput ?? "")}
      onClick={() => (props.onSend as (text: string) => void)("hi")}
    >
      chat-input
    </button>
  ),
}));

vi.mock("../VoiceWaveform", () => ({
  VoiceWaveform: (props: { stream: MediaStream | null; active: boolean }) => (
    <div
      data-testid="voice-waveform-stub"
      data-has-stream={String(props.stream !== null)}
      data-active={String(props.active)}
    />
  ),
}));

import { AssistantInputBar } from "./AssistantInputBar";

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

describe("<AssistantInputBar />", () => {
  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(node: React.ReactNode) {
    root = createRoot(container);
    act(() => {
      root!.render(node);
    });
  }

  it("renders the waveform row, hairline divider, and wrapped ChatInput", () => {
    render(<AssistantInputBar onSend={() => {}} />);

    expect(container.querySelector('[data-testid="assistant-input-bar"]')).not.toBeNull();
    expect(container.querySelector('[data-testid="assistant-input-bar-waveform"]')).not.toBeNull();
    expect(container.querySelector('[data-testid="assistant-input-bar-divider"]')).not.toBeNull();
    expect(container.querySelector('[data-testid="voice-waveform-stub"]')).not.toBeNull();
    expect(container.querySelector('[data-testid="chat-input-stub"]')).not.toBeNull();
  });

  it("orders the waveform above the divider and the divider above the input", () => {
    render(<AssistantInputBar onSend={() => {}} />);
    const root = container.querySelector('[data-testid="assistant-input-bar"]');
    expect(root).not.toBeNull();
    const children = Array.from(root!.children);
    // row 0: waveform wrapper, row 1: divider, row 2: input wrapper
    expect(children[0]!.getAttribute("data-testid")).toBe("assistant-input-bar-waveform");
    expect(children[1]!.getAttribute("data-testid")).toBe("assistant-input-bar-divider");
    // The third child is the input wrapper div with ChatInput inside
    expect(children[2]!.querySelector('[data-testid="chat-input-stub"]')).not.toBeNull();
  });

  it("forwards onSend to the underlying ChatInput", () => {
    const onSend = vi.fn();
    render(<AssistantInputBar onSend={onSend} />);
    const stub = container.querySelector('[data-testid="chat-input-stub"]') as HTMLButtonElement;
    act(() => {
      stub.click();
    });
    expect(onSend).toHaveBeenCalledWith("hi");
  });

  it("forwards isSubmitting and disabled to ChatInput", () => {
    render(<AssistantInputBar onSend={() => {}} isSubmitting disabled />);
    const stub = container.querySelector('[data-testid="chat-input-stub"]') as HTMLElement;
    expect(stub.getAttribute("data-submitting")).toBe("true");
    expect(stub.getAttribute("data-disabled")).toBe("true");
  });

  it("passes micStream and micActive through to the waveform", () => {
    // A fake MediaStream stand-in is fine — the stub only checks identity.
    const stream = {} as unknown as MediaStream;
    render(<AssistantInputBar onSend={() => {}} micStream={stream} micActive />);
    const wave = container.querySelector('[data-testid="voice-waveform-stub"]') as HTMLElement;
    expect(wave.getAttribute("data-has-stream")).toBe("true");
    expect(wave.getAttribute("data-active")).toBe("true");
  });

  it("uses the spec-copy placeholder by default and accepts an override", () => {
    render(<AssistantInputBar onSend={() => {}} />);
    const stub = container.querySelector('[data-testid="chat-input-stub"]') as HTMLElement;
    expect(stub.getAttribute("data-placeholder")).toContain("hold space to talk");

    act(() => {
      root!.render(<AssistantInputBar onSend={() => {}} placeholder="Custom" />);
    });
    const stub2 = container.querySelector('[data-testid="chat-input-stub"]') as HTMLElement;
    expect(stub2.getAttribute("data-placeholder")).toBe("Custom");
  });

  it("enables voice input by default", () => {
    render(<AssistantInputBar onSend={() => {}} />);
    const stub = container.querySelector('[data-testid="chat-input-stub"]') as HTMLElement;
    expect(stub.getAttribute("data-enable-voice")).toBe("true");
  });
});
