import { describe, expect, test } from "bun:test";
import { Window } from "happy-dom";
import {
  createContextProvider,
  historySignal,
  mountPortal,
  transition,
  createTimeline,
  urlSignal,
} from "../src/index.ts";

describe("advanced reactive primitives", () => {
  test("supports bounded undo and redo", () => {
    const value = historySignal(0, { limit: 2 });
    value.set(1);
    value.set(2);
    value.set(3);
    expect(value.undo()).toBe(true);
    expect(value.get()).toBe(2);
    expect(value.undo()).toBe(true);
    expect(value.get()).toBe(1);
    expect(value.undo()).toBe(false);
    expect(value.redo()).toBe(true);
    expect(value.get()).toBe(2);
  });
  test("synchronizes URL state through an explicit adapter", () => {
    let changed = "";
    const value = urlSignal("all", {
      url: new URL("https://example.test/?filter=open"),
      parameter: "filter",
      parse: (input) => input ?? "all",
      replace: (url) => {
        changed = url.href;
      },
    });
    expect(value.get()).toBe("open");
    value.set("closed");
    expect(changed).toBe("https://example.test/?filter=closed");
  });
  test("provides scoped context and cleans up portals and transitions", async () => {
    const context = createContextProvider("default");
    expect(context.provide("tenant", () => context.use())).toBe("tenant");
    expect(context.use()).toBe("default");
    const window = new Window();
    const target = window.document.createElement("div");
    const cleanup = mountPortal("hello", target as unknown as Element);
    expect(target.textContent).toBe("hello");
    cleanup();
    expect(target.textContent).toBe("");
    await transition(() => target.setAttribute("ready", ""), {
      target: target as unknown as Element,
      className: "enter",
    });
    expect(target.classList.contains("enter")).toBe(false);
  });
});

test("animation timelines sequence bounded forward and reverse steps", async () => {
  let clock = 0;
  const values: number[] = [];
  const timeline = createTimeline(
    [{ durationMs: 10, update: (progress) => values.push(progress) }],
    {
      now: () => clock,
      frame(callback) {
        clock += 5;
        queueMicrotask(callback);
      },
    },
  );
  await timeline.play();
  expect(values.at(-1)).toBe(1);
  values.length = 0;
  clock = 0;
  await timeline.play({ reverse: true });
  expect(values[0]).toBe(1);
  expect(values.at(-1)).toBe(0);
  expect(timeline.running).toBeFalse();
});
