import {
  cleanup,
  createEvent,
  fireEvent,
  render,
  screen,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { createRef } from "react";
import assert from "node:assert";
import {
  describe, test, mock, afterEach,
} from "node:test";
import { Input } from "./index";
import type { IInputRef } from "./index";

describe("Input", () => {

  afterEach(() => {
    cleanup();
  });
  /* COMMON */

  test("renders input by default without crashing", () => {
    render(<Input name={"test"} />);
    const element = screen.getByRole("textbox");
    assert.notStrictEqual(element, null);
    assert.strictEqual(element.tagName, "INPUT");
  });

  test("renders textarea when type is 'textarea'", () => {
    render(<Input name={"test"} type={"textarea"} />);
    const element = screen.getByRole("textbox");
    assert.notStrictEqual(element, null);
    assert.strictEqual(element.tagName, "TEXTAREA");
  });

  test("renders with initial value", () => {
    render(<Input value={"test-value"} />);
    assert.strictEqual(screen.getByRole("textbox").getAttribute("value"), "test-value");
  });

  test("has 'data-js-input-required' attribute when isRequired", () => {
    render(<Input isRequired={true} />);
    const input = screen.getByRole("textbox");
    assert.notStrictEqual(input.getAttribute("data-js-input-required"), null);
  });

  test("is focused then autoFocus is true", () => {
    render(<Input autoFocus={true} />);
    assert.strictEqual(document.activeElement, screen.getByRole("textbox"));
  });

  /* HANDLERS */

  test("changes value on user input", async () => {
    const user = userEvent.setup();
    render(<Input />);

    const input = screen.getByRole("textbox") as HTMLInputElement;
    await user.type(input, "foo-boo");

    assert.strictEqual(input.value, "foo-boo");
  });

  test("calls onChange with stateActions when value is changed", async () => {
    const user = userEvent.setup();
    const onChange = mock.fn();

    render(<Input onChange={onChange} />);
    await user.type(screen.getByRole("textbox"), "val");

    assert.strictEqual(onChange.mock.calls.length, 3);
    const args = onChange.mock.calls[0]?.arguments;
    assert.ok(args && typeof args[0] === "object");
    assert.ok("stateActions" in args[0]);
  });

  test("calls onFocus and onBlur with stateActions", async () => {
    const user = userEvent.setup();
    const onFocus = mock.fn();
    const onBlur = mock.fn();

    render(<Input onFocus={onFocus} onBlur={onBlur} />);
    const input = screen.getByRole("textbox");
    await user.click(input);
    await user.tab();

    assert.strictEqual(onFocus.mock.calls.length, 1);
    let args = onFocus.mock.calls[0]?.arguments;
    assert.ok(args && typeof args[0] === "object");
    assert.ok("stateActions" in args[0]);

    assert.strictEqual(onBlur.mock.calls.length, 1);
    args = onBlur.mock.calls[0]?.arguments;
    assert.ok(args && typeof args[0] === "object");
    assert.ok("stateActions" in args[0]);
  });

  test("does not allow typing when disabled", async () => {
    const user = userEvent.setup();
    render(<Input isDisabled={true} />);

    const input = screen.getByRole("textbox") as HTMLInputElement;
    await user.type(input, "value");

    assert.strictEqual(input.value, "");
  });

  test("does not change value when is readonly", async () => {
    const user = userEvent.setup();
    render(<Input isReadOnly={true} value={"foo-"} />);

    const input = screen.getByRole("textbox") as HTMLInputElement;
    await user.type(input, "value");

    assert.strictEqual(input.value, "foo-");
  });

  test("calls onReset and resets value on reset", async () => {
    const user = userEvent.setup();
    const onReset = mock.fn((args) => {
      args.stateActions.setValue("");
    });

    render(<Input name={"test"} onReset={onReset} />);

    const input = screen.getByRole("textbox") as HTMLInputElement;
    await user.type(input, "boo");
    assert.strictEqual(input.value, "boo");
    fireEvent(input, createEvent("reset", input, { bubbles: true }));

    assert.strictEqual(onReset.mock.calls.length, 1);
    const args = onReset.mock.calls[0]?.arguments;
    assert.ok(args && typeof args[0] === "object");
    assert.ok("stateActions" in args[0]);
    assert.strictEqual(typeof args[0].stateActions.setValue, "function");
    assert.strictEqual(input.value, "");
  });

  /* CHECKBOX */

  test("supports checkbox behavior", async () => {
    const user = userEvent.setup();
    render(<Input type={"checkbox"} />);

    const checkbox = screen.getByRole("checkbox") as HTMLInputElement;
    assert.strictEqual(checkbox.checked, false);

    await user.click(checkbox);
    assert.strictEqual(checkbox.checked, true);
  });

  /* REF */

  test("exposes imperative ref API", async () => {
    const ref = createRef<IInputRef>();
    render(<Input ref={ref} value={"hello"} />);

    assert.ok(ref.current?.el instanceof HTMLElement);
    assert.deepStrictEqual(ref.current?.getData(), {
      value: "hello",
      isChecked: false,
    });
  });
});
