import { act, cleanup, render, screen } from "@testing-library/react";
import * as React from "react";
import { renderToString } from "react-dom/server";
import { afterEach, describe, expect, it, vi } from "vitest";

import { defineToolcraft } from "../../schema/define-toolcraft";
import { createToolcraftState } from "../../state/create-template-state";
import { createToolcraftExternalStore } from "../../state/toolcraft-external-store";
import type { ToolcraftExternalStore } from "../../state/toolcraft-external-store";
import { ToolcraftStoreContext } from "./toolcraft-store-context";
import { useToolcraftSelector } from "./use-toolcraft";

function createSelectorStore() {
  const schema = defineToolcraft({
    canvas: { enabled: true },
    panels: {
      controls: {
        sections: [
          {
            controls: {
              first: {
                defaultValue: 1,
                target: "value.first",
                type: "slider",
              },
              second: {
                defaultValue: 2,
                target: "value.second",
                type: "slider",
              },
            },
          },
        ],
        title: "Controls",
      },
    },
  });

  return createToolcraftExternalStore(createToolcraftState(schema));
}

function StoreProvider({
  children,
  store,
}: {
  children: React.ReactNode;
  store: ToolcraftExternalStore;
}) {
  return (
    <ToolcraftStoreContext.Provider value={store}>
      {children}
    </ToolcraftStoreContext.Provider>
  );
}

afterEach(cleanup);

describe("useToolcraftSelector", () => {
  it("keeps the committed selector active when a different selector suspends and is interrupted", async () => {
    const store = createSelectorStore();
    const suspendedRender = new Promise<never>(() => undefined);
    const attemptedTargets: string[] = [];

    function Probe({ shouldSuspend, target }: { shouldSuspend: boolean; target: string }) {
      const value = useToolcraftSelector((state) => state.values[target]);

      attemptedTargets.push(target);

      if (shouldSuspend) {
        throw suspendedRender;
      }

      return <output data-testid="selector-value">{String(value)}</output>;
    }

    const view = render(
      <StoreProvider store={store}>
        <React.Suspense fallback={<span>Pending</span>}>
          <Probe shouldSuspend={false} target="value.first" />
        </React.Suspense>
      </StoreProvider>,
    );

    await act(async () => {
      React.startTransition(() => {
        view.rerender(
          <StoreProvider store={store}>
            <React.Suspense fallback={<span>Pending</span>}>
              <Probe shouldSuspend target="value.second" />
            </React.Suspense>
          </StoreProvider>,
        );
      });
    });

    expect(attemptedTargets).toContain("value.second");
    expect(screen.getByTestId("selector-value").textContent).toBe("1");

    act(() => {
      store.dispatch({
        target: "value.first",
        type: "controls.setValue",
        value: 11,
      });
    });

    expect(screen.getByTestId("selector-value").textContent).toBe("11");
  });

  it("keeps one subscription across parent, playback, and selector-prop renders", () => {
    const store = createSelectorStore();
    const subscriptionStarts = vi.fn();
    const subscriptionStops = vi.fn();
    const subscribe = store.subscribe;
    const subscribeSelector = store.subscribeSelector;

    store.subscribe = ((listener) => {
      subscriptionStarts();
      const unsubscribe = subscribe(listener);

      return () => {
        subscriptionStops();
        unsubscribe();
      };
    }) as ToolcraftExternalStore["subscribe"];
    store.subscribeSelector = ((selector, listener, equality) => {
      subscriptionStarts();
      const unsubscribe = subscribeSelector(selector, listener, equality);

      return () => {
        subscriptionStops();
        unsubscribe();
      };
    }) as ToolcraftExternalStore["subscribeSelector"];

    function Probe({ label, target }: { label: string; target: "playback" | "value.second" }) {
      const value = useToolcraftSelector((state) =>
        target === "playback"
          ? state.timeline.currentTimeSeconds
          : state.values[target],
      );

      return <output data-testid="selector-value">{label}:{String(value)}</output>;
    }

    const view = render(
      <StoreProvider store={store}>
        <Probe label="first" target="playback" />
      </StoreProvider>,
    );

    view.rerender(
      <StoreProvider store={store}>
        <Probe label="parent" target="playback" />
      </StoreProvider>,
    );
    act(() => {
      store.dispatchTransient({
        currentTimeSeconds: 1,
        type: "timeline.setCurrentTime",
      });
    });

    expect(screen.getByTestId("selector-value").textContent).toBe("parent:1");

    view.rerender(
      <StoreProvider store={store}>
        <Probe label="switched" target="value.second" />
      </StoreProvider>,
    );

    act(() => {
      store.dispatch({
        target: "value.second",
        type: "controls.setValue",
        value: 20,
      });
    });

    expect(screen.getByTestId("selector-value").textContent).toBe("switched:20");
    expect(subscriptionStarts).toHaveBeenCalledTimes(1);
    expect(subscriptionStops).not.toHaveBeenCalled();

    view.unmount();

    expect(subscriptionStops).toHaveBeenCalledTimes(1);
  });

  it("uses custom equality without rerendering for an equivalent selection", () => {
    const store = createSelectorStore();
    let renderCount = 0;

    function Probe() {
      const offset = useToolcraftSelector(
        (state) => ({ ...state.canvas.offset }),
        (previous, next) => previous.x === next.x && previous.y === next.y,
      );

      renderCount += 1;

      return <output>{offset.x},{offset.y}</output>;
    }

    render(
      <StoreProvider store={store}>
        <Probe />
      </StoreProvider>,
    );

    act(() => {
      store.dispatchTransient({
        offset: { x: 0, y: 0 },
        type: "canvas.setViewport",
        zoom: 150,
      });
    });

    expect(renderCount).toBe(1);
  });

  it("uses a stable server snapshot", () => {
    const store = createSelectorStore();

    function Probe() {
      return (
        <output>
          {String(useToolcraftSelector((state) => state.values["value.first"]))}
        </output>
      );
    }

    expect(
      renderToString(
        <StoreProvider store={store}>
          <Probe />
        </StoreProvider>,
      ),
    ).toContain("1");
  });

});
