import { act, cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";

import { defineToolcraft } from "../../schema/define-toolcraft";
import type { ToolcraftExternalStore } from "../../state/toolcraft-external-store";
import { ToolcraftRoot } from "./toolcraft-root";
import { useToolcraftStore } from "./toolcraft-store-context";
import {
  useToolcraftEvaluatedValue,
  useToolcraftEvaluatedValues,
  useToolcraftValue,
} from "./use-toolcraft";

const valuesSchema = defineToolcraft({
  canvas: { enabled: true },
  panels: {
    controls: {
      sections: [
        {
          controls: {
            opacity: {
              defaultValue: 0,
              target: "shape.opacity",
              type: "slider",
            },
            scale: {
              defaultValue: 10,
              target: "shape.scale",
              type: "slider",
            },
          },
        },
      ],
      title: "Controls",
    },
    timeline: { mode: "keyframes" },
  },
});

const initialState = {
  timeline: {
    currentTimeSeconds: 0,
    keyframeGroups: [
      {
        controlId: "shape.opacity",
        keyframes: [
          {
            controlId: "shape.opacity",
            controlLabel: "Opacity",
            id: "shape.opacity::0",
            timeSeconds: 0,
            value: 0,
            valueLabel: "0",
          },
          {
            controlId: "shape.opacity",
            controlLabel: "Opacity",
            id: "shape.opacity::4",
            timeSeconds: 4,
            value: 100,
            valueLabel: "100",
          },
        ],
        label: "Opacity",
      },
    ],
  },
};

function CaptureStore({
  capture,
}: {
  capture: (store: ToolcraftExternalStore) => void;
}) {
  capture(useToolcraftStore());
  return null;
}

afterEach(cleanup);

describe("Toolcraft value selector hooks", () => {
  it("useToolcraftValue rerenders only when its target changes", () => {
    let store: ToolcraftExternalStore | undefined;
    let renderCount = 0;

    function Probe() {
      const value = useToolcraftValue("shape.opacity");

      renderCount += 1;

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

    render(
      <ToolcraftRoot initialState={initialState} schema={valuesSchema}>
        <CaptureStore capture={(value) => { store = value; }} />
        <Probe />
      </ToolcraftRoot>,
    );

    act(() => {
      store?.dispatch({
        target: "shape.scale",
        type: "controls.setValue",
        value: 20,
      });
      store?.dispatchTransient({
        offset: { x: 12, y: -6 },
        type: "canvas.setViewport",
        zoom: 125,
      });
    });

    expect(renderCount).toBe(1);

    act(() => {
      store?.dispatch({
        target: "shape.opacity",
        type: "controls.setValue",
        value: 25,
      });
    });

    expect(screen.getByTestId("value").textContent).toBe("25");
    expect(renderCount).toBe(2);
  });

  it("evaluates one target from only its value, matching keyframes, and implicit playback", () => {
    let store: ToolcraftExternalStore | undefined;
    let implicitRenderCount = 0;
    let explicitRenderCount = 0;

    function ImplicitProbe() {
      const value = useToolcraftEvaluatedValue("shape.opacity");

      implicitRenderCount += 1;

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

    function ExplicitProbe() {
      const value = useToolcraftEvaluatedValue("shape.opacity", 0);

      explicitRenderCount += 1;

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

    render(
      <ToolcraftRoot initialState={initialState} schema={valuesSchema}>
        <CaptureStore capture={(value) => { store = value; }} />
        <ImplicitProbe />
        <ExplicitProbe />
      </ToolcraftRoot>,
    );

    act(() => {
      store?.dispatch({
        target: "shape.scale",
        type: "controls.setValue",
        value: 20,
      });
      store?.dispatchTransient({
        offset: { x: 12, y: -6 },
        type: "canvas.setViewport",
        zoom: 125,
      });
    });

    expect(implicitRenderCount).toBe(1);
    expect(explicitRenderCount).toBe(1);

    act(() => {
      store?.dispatchTransient({
        currentTimeSeconds: 4,
        type: "timeline.setCurrentTime",
      });
    });

    expect(screen.getByTestId("implicit-value").textContent).toBe("100");
    expect(screen.getByTestId("explicit-value").textContent).toBe("0");
    expect(implicitRenderCount).toBe(2);
    expect(explicitRenderCount).toBe(1);

    act(() => {
      store?.dispatch({
        controlId: "shape.scale",
        controlLabel: "Scale",
        type: "timeline.upsertControlKeyframe",
        value: 20,
        valueLabel: "20",
      });
    });

    expect(implicitRenderCount).toBe(2);
    expect(explicitRenderCount).toBe(1);

    act(() => {
      store?.dispatch({
        controlId: "shape.opacity",
        controlLabel: "Opacity",
        timeSeconds: 4,
        type: "timeline.upsertControlKeyframe",
        value: 80,
        valueLabel: "80",
      });
    });

    expect(screen.getByTestId("implicit-value").textContent).toBe("80");
    expect(screen.getByTestId("explicit-value").textContent).toBe("0");
    expect(implicitRenderCount).toBe(3);
    expect(explicitRenderCount).toBe(2);
  });

  it("evaluates all values without subscribing to viewport, panels, or explicit-time playback", () => {
    let store: ToolcraftExternalStore | undefined;
    const implicitResults: Array<Record<string, unknown>> = [];
    const explicitResults: Array<Record<string, unknown>> = [];

    function ImplicitProbe() {
      const values = useToolcraftEvaluatedValues();

      implicitResults.push(values);

      return <output data-testid="implicit-values">{String(values["shape.opacity"])}</output>;
    }

    function ExplicitProbe() {
      const values = useToolcraftEvaluatedValues(0);

      explicitResults.push(values);

      return <output data-testid="explicit-values">{String(values["shape.opacity"])}</output>;
    }

    render(
      <ToolcraftRoot initialState={initialState} schema={valuesSchema}>
        <CaptureStore capture={(value) => { store = value; }} />
        <ImplicitProbe />
        <ExplicitProbe />
      </ToolcraftRoot>,
    );

    const initialImplicitResult = implicitResults[0];
    const initialExplicitResult = explicitResults[0];

    act(() => {
      store?.dispatchTransient({
        offset: { x: 18, y: 9 },
        type: "canvas.setViewport",
        zoom: 140,
      });
      store?.dispatch({
        offset: { x: 20, y: 10 },
        panelId: "controls",
        type: "panels.setOffset",
      });
    });

    expect(implicitResults).toHaveLength(1);
    expect(explicitResults).toHaveLength(1);
    expect(implicitResults[0]).toBe(initialImplicitResult);
    expect(explicitResults[0]).toBe(initialExplicitResult);

    act(() => {
      store?.dispatchTransient({
        currentTimeSeconds: 4,
        type: "timeline.setCurrentTime",
      });
    });

    expect(screen.getByTestId("implicit-values").textContent).toBe("100");
    expect(screen.getByTestId("explicit-values").textContent).toBe("0");
    expect(implicitResults).toHaveLength(2);
    expect(explicitResults).toHaveLength(1);

    act(() => {
      store?.dispatch({
        target: "shape.scale",
        type: "controls.setValue",
        value: 30,
      });
    });

    expect(implicitResults).toHaveLength(3);
    expect(explicitResults).toHaveLength(2);
  });
});
