import { afterAll, afterEach, beforeAll, beforeEach, expect, test } from "bun:test";
import { Window } from "happy-dom";
import { act, useState } from "react";
import type { Root } from "react-dom/client";
import { Counter } from "./primitives";

const dom = new Window({ url: "http://localhost:3000/design-system" });
const originalGlobals = new Map<string, PropertyDescriptor | undefined>();
let createRoot: typeof import("react-dom/client").createRoot;
let root: Root;
let container: HTMLDivElement;

beforeAll(async () => {
  for (const key of ["window", "document", "navigator", "HTMLElement", "HTMLInputElement", "Element", "Node", "Event", "MouseEvent", "IS_REACT_ACT_ENVIRONMENT"]) {
    originalGlobals.set(key, Object.getOwnPropertyDescriptor(globalThis, key));
    Object.defineProperty(globalThis, key, {
      configurable: true,
      writable: true,
      value: key === "window" ? dom : key === "IS_REACT_ACT_ENVIRONMENT" ? true : (dom as unknown as Record<string, unknown>)[key],
    });
  }
  ({ createRoot } = await import("react-dom/client"));
});

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

afterEach(async () => {
  await act(async () => root.unmount());
  container.remove();
});

afterAll(() => {
  dom.happyDOM.abort();
  for (const [key, descriptor] of originalGlobals) {
    if (descriptor) Object.defineProperty(globalThis, key, descriptor);
    else Reflect.deleteProperty(globalThis, key);
  }
});


const input = () => container.querySelector<HTMLInputElement>("input")!;
const control = (name: string) => container.querySelector<HTMLButtonElement>(`button[aria-label="${name}"]`)!;
async function enter(text: string) {
  await act(async () => {
    Object.getOwnPropertyDescriptor(dom.HTMLInputElement.prototype, "value")!.set!.call(input(), text);
    input().dispatchEvent(new dom.Event("input", { bubbles: true }) as unknown as Event);
  });
}
async function key(key: string) {
  await act(async () => input().dispatchEvent(new dom.KeyboardEvent("keydown", { key, bubbles: true }) as unknown as KeyboardEvent));
}

test("totals save explicitly, support above-target values and retain step controls", async () => {
  const updates: number[] = [];
  function Demo() {
    const [value, setValue] = useState(8);
    return <Counter label="pages" value={value} target={8} onChange={next => { updates.push(next); setValue(next); }} />;
  }
  await act(async () => root.render(<Demo />));
  await enter("30");
  expect(updates).toEqual([]);
  await key("Enter");
  expect(updates).toEqual([30]);
  expect(input().value).toBe("30");
  expect(control("Save total pages")).toBeNull();
  await act(async () => control("Increase pages").click());
  expect(input().value).toBe("31");
  await enter("40");
  await act(async () => control("Save total pages").click());
  expect(updates).toEqual([30, 31, 40]);
});

test("invalid totals never submit and Escape restores the saved value", async () => {
  const updates: number[] = [];
  await act(async () => root.render(<Counter label="pages" value={3} target={8} onChange={next => updates.push(next)} />));
  for (const invalid of ["", "-1", "1.5", "abc", "1000000001"]) {
    await enter(invalid);
    expect(input().getAttribute("aria-invalid")).toBe("true");
    expect(control("Save total pages").disabled).toBe(true);
    await key("Enter");
  }
  expect(updates).toEqual([]);
  await key("Escape");
  expect(input().value).toBe("3");
  expect(container.querySelector('[role="alert"]')).toBeNull();
});

test("server refresh replaces a draft and API bounds disable increment and decrement", async () => {
  const render = (value: number, disabled = false) => root.render(<Counter label="pages" value={value} target={8} disabled={disabled} onChange={() => {}} />);
  await act(async () => render(0));
  expect(control("Decrease pages").disabled).toBe(true);
  await enter("12");
  await act(async () => render(1_000_000_000));
  expect(input().value).toBe("1000000000");
  expect(control("Increase pages").disabled).toBe(true);
  await act(async () => render(2, true));
  expect(input().disabled).toBe(true);
  expect(control("Decrease pages").disabled).toBe(true);
});
