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

import {
  registerToolcraftRendererPipeline,
  createToolcraftRendererPipelineRuntimeOwner,
  type ToolcraftRendererPipelinePassContract,
  type ToolcraftRendererPipelineClient,
} from "../../rendering";
import { defineToolcraft } from "../../schema/define-toolcraft";
import { ToolcraftApp } from "./toolcraft-app";
import { ToolcraftPipelineEvidenceBridge } from "./toolcraft-pipeline-evidence";
import { useToolcraftPipeline } from "./use-toolcraft-pipeline";

beforeAll(() => {
  Object.defineProperty(window, "matchMedia", {
    value: () => ({
      addEventListener: () => undefined,
      addListener: () => undefined,
      matches: false,
      removeEventListener: () => undefined,
      removeListener: () => undefined,
    }),
    writable: true,
  });
});

afterEach(cleanup);

const schema = defineToolcraft({
  canvas: { enabled: true },
  panels: {
    controls: {
      sections: [
        {
          controls: {
            actions: {
              actions: [{ label: "Render", value: "render" }],
              target: "panel.actions",
              type: "panelActions",
            },
            power: {
              defaultValue: 10,
              label: "Power",
              target: "generation.power",
              type: "pipelinePower",
            },
          },
          title: "Renderer",
        },
      ],
      title: "Controls",
    },
  },
});

function createRendererPipelineRegistration(runtimeId = "app-shell-test-v1") {
  return registerToolcraftRendererPipeline<{
    preview: ToolcraftRendererPipelinePassContract<string>;
  }>()({
    interactionInvalidation: [],
    passes: [
      {
        cacheKey: ["generation.power"],
        id: "preview",
        inputs: ["generation.power"],
        invalidatedBy: ["generation.power"],
        kind: "composite",
        lifecycle: { cache: "memoized", resourceScope: "renderer" },
        output: "preview",
        quality: "full",
        runsOn: "main",
      },
    ],
    runtimeId,
  });
}

describe("Toolcraft pipeline context", () => {
  it("exposes pull-only immutable evidence without subscribing or serializing", async () => {
    const registration = createRendererPipelineRegistration("pull-evidence-v1");
    const owner = createToolcraftRendererPipelineRuntimeOwner(registration);
    const subscribe = vi.fn(owner.client.subscribe);
    const client: ToolcraftRendererPipelineClient = Object.freeze({
      ...owner.client,
      subscribe,
    });
    const symbol = Symbol.for("toolcraft.renderer-pipeline-evidence.snapshot");

    render(<ToolcraftPipelineEvidenceBridge client={client} />);

    const evidence = document.querySelector(
      '[data-toolcraft-pipeline-evidence="pull-evidence-v1"]',
    ) as HTMLOutputElement & Record<symbol, unknown>;
    expect(subscribe).not.toHaveBeenCalled();
    expect(evidence.getAttribute("data-toolcraft-pipeline-snapshot")).toBeNull();
    expect(evidence.propertyIsEnumerable(symbol)).toBe(false);
    const getSnapshot = evidence[symbol];
    expect(getSnapshot).toBeTypeOf("function");

    await owner.client.runPass(
      registration.getPass("preview"),
      { "generation.power": 10 },
      () => "preview",
    );
    const snapshot = (getSnapshot as () => ReturnType<typeof client.getSnapshot>)();
    expect(snapshot.passes.preview.executions).toBe(1);
    expect(Object.isFrozen(snapshot)).toBe(true);
    await owner.dispose();
  });

  it("owns one StrictMode-safe runtime for output, custom controls, and actions", async () => {
    const registration = createRendererPipelineRegistration();
    const observedRuntimes: ToolcraftRendererPipelineClient[] = [];
    let actionRuntime: ToolcraftRendererPipelineClient | null | undefined;

    function PipelineConsumer(): React.JSX.Element {
      const runtime = useToolcraftPipeline();
      if (runtime) {
        observedRuntimes.push(runtime);
      }
      return <span>Pipeline consumer</span>;
    }

    const view = render(
      <React.StrictMode>
        <ToolcraftApp
          canvasContent={<PipelineConsumer />}
          controlRenderers={{ pipelinePower: PipelineConsumer }}
          onPanelAction={({ rendererPipeline }) => {
            actionRuntime = rendererPipeline;
            return rendererPipeline?.runPass(
              registration.getPass("preview"),
              { "generation.power": 10 },
              () => "preview-model",
            );
          }}
          rendererPipelineRegistration={registration}
          schema={schema}
        />
      </React.StrictMode>,
    );
    view.rerender(
      <React.StrictMode>
        <ToolcraftApp
          canvasContent={<PipelineConsumer />}
          controlRenderers={{ pipelinePower: PipelineConsumer }}
          onPanelAction={({ rendererPipeline }) => {
            actionRuntime = rendererPipeline;
            return rendererPipeline?.runPass(
              registration.getPass("preview"),
              { "generation.power": 10 },
              () => "preview-model",
            );
          }}
          rendererPipelineRegistration={registration}
          schema={schema}
        />
      </React.StrictMode>,
    );
    fireEvent.click(screen.getByRole("button", { name: "Render" }));
    await act(() => Promise.resolve());

    const runtime = observedRuntimes[0];
    expect(runtime).toBeDefined();
    expect(observedRuntimes.every((candidate) => candidate === runtime)).toBe(true);
    expect(runtime).not.toHaveProperty("dispose");
    expect(actionRuntime).toBe(runtime);
    expect(runtime?.getSnapshot().disposed).toBe(false);
    expect(Object.isFrozen(runtime?.getSnapshot())).toBe(true);
    const evidence = document.querySelector(
      '[data-toolcraft-pipeline-evidence="app-shell-test-v1"]',
    );
    expect(evidence).toBeInstanceOf(HTMLOutputElement);
    const getSnapshot = (
      evidence as HTMLOutputElement & Record<symbol, unknown>
    )[Symbol.for("toolcraft.renderer-pipeline-evidence.snapshot")];
    const snapshot = (getSnapshot as () => {
      disposed: boolean;
      passes: { preview: { executions: number } };
    })();
    expect(snapshot).toMatchObject({
      disposed: false,
      passes: { preview: { executions: 1 } },
    });

    view.unmount();
    await act(() => Promise.resolve());
    expect(runtime?.getSnapshot().disposed).toBe(true);
  });

  it("is absent when an app does not register a pipeline", () => {
    let observedRuntime: unknown = "not-rendered";

    function PipelineProbe(): React.JSX.Element {
      observedRuntime = useToolcraftPipeline();
      return <span>Pipeline probe</span>;
    }

    render(<ToolcraftApp canvasContent={<PipelineProbe />} schema={schema} />);

    expect(observedRuntime).toBeNull();
    expect(
      document.querySelector("[data-toolcraft-pipeline-evidence]"),
    ).toBeNull();
  });

  it("isolates one immutable evidence bridge per provider lifecycle", async () => {
    const first = createRendererPipelineRegistration("first-pipeline-v1");
    const second = createRendererPipelineRegistration("second-pipeline-v1");
    const view = render(
      <>
        <ToolcraftApp
          canvasContent={<span>First</span>}
          rendererPipelineRegistration={first}
          schema={schema}
        />
        <ToolcraftApp
          canvasContent={<span>Second</span>}
          rendererPipelineRegistration={second}
          schema={schema}
        />
      </>,
    );
    await act(() => Promise.resolve());

    const evidence = document.querySelectorAll(
      "[data-toolcraft-pipeline-evidence]",
    );
    expect(evidence).toHaveLength(2);
    expect(
      [...evidence].map((element) =>
        element.getAttribute("data-toolcraft-pipeline-evidence"),
      ),
    ).toEqual(["first-pipeline-v1", "second-pipeline-v1"]);
    for (const element of evidence) {
      const getSnapshot = (element as HTMLElement & Record<symbol, unknown>)[
        Symbol.for("toolcraft.renderer-pipeline-evidence.snapshot")
      ];
      const snapshot = (getSnapshot as () => { runtimeId: string })();
      expect(snapshot.runtimeId).toBe(
        element.getAttribute("data-toolcraft-pipeline-evidence"),
      );
    }

    view.unmount();
    expect(
      document.querySelector("[data-toolcraft-pipeline-evidence]"),
    ).toBeNull();
  });
});
