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

import { defineToolcraft } from "../../../schema/define-toolcraft";
import type { ToolcraftExternalStore } from "../../../state/toolcraft-external-store";
import { ToolcraftRoot } from "../../app-shell/toolcraft-root";
import { useToolcraftStore } from "../../app-shell/toolcraft-store-context";
import { ControlsPanel } from "../controls-panel";

const counters = vi.hoisted(() => ({
  commits: new Map<string, number>(),
  executions: new Map<string, number>(),
}));

function increment(counter: Map<string, number>, id: string): void {
  counter.set(id, (counter.get(id) ?? 0) + 1);
}

function CommitProbe({ id }: { id: string }): null {
  React.useLayoutEffect(() => {
    increment(counters.commits, id);
  });

  return null;
}

vi.mock("@repo/ui", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@repo/ui")>();

  return {
    ...actual,
    PanelSection: (props: React.ComponentProps<typeof actual.PanelSection>) => {
      const id = `section:${String(props.title)}`;

      increment(counters.executions, id);
      return (
        <>
          <CommitProbe id={id} />
          <actual.PanelSection {...props} />
        </>
      );
    },
  };
});

vi.mock("../renderers/controls-panel-basic-renderers", async (importOriginal) => {
  const actual = await importOriginal<
    typeof import("../renderers/controls-panel-basic-renderers")
  >();

  return {
    ...actual,
    renderBasicControl: (
      args: Parameters<typeof actual.renderBasicControl>[0],
    ): React.ReactNode => {
      increment(counters.executions, `control:${args.id}`);

      return (
        <>
          <CommitProbe id={`control:${args.id}`} />
          {actual.renderBasicControl(args)}
        </>
      );
    },
  };
});

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

function resetCounters(): void {
  counters.commits.clear();
  counters.executions.clear();
}

function count(kind: "commits" | "executions", id: string): number {
  return counters[kind].get(id) ?? 0;
}

function renderPanel(
  schema: ReturnType<typeof defineToolcraft>,
  props: Pick<React.ComponentProps<typeof ControlsPanel>, "onPanelAction"> = {},
): ToolcraftExternalStore {
  let store: ToolcraftExternalStore | undefined;

  render(
    <ToolcraftRoot schema={schema}>
      <CaptureStore capture={(value) => { store = value; }} />
      <ControlsPanel {...props} framed={false} />
    </ToolcraftRoot>,
  );

  if (!store) {
    throw new Error("Expected Toolcraft store to be captured.");
  }

  resetCounters();
  return store;
}

function createTwoSliderSchema(): ReturnType<typeof defineToolcraft> {
  return defineToolcraft({
    canvas: { enabled: true },
    panels: {
      controls: {
        sections: [
          {
            controls: {
              alpha: {
                defaultValue: 10,
                label: "Alpha",
                max: 100,
                min: 0,
                target: "shape.alpha",
                type: "slider",
              },
              beta: {
                defaultValue: 20,
                label: "Beta",
                max: 100,
                min: 0,
                target: "shape.beta",
                type: "slider",
              },
            },
            title: "Transform",
          },
        ],
        title: "Controls",
      },
    },
  });
}

afterEach(() => {
  cleanup();
  resetCounters();
  window.localStorage.clear();
});

describe("ControlsPanel section isolation", () => {
  it("distinguishes a control named Reset controls from an actual reset", () => {
    const schema = defineToolcraft({
      canvas: { enabled: false },
      panels: {
        controls: {
          sections: [
            {
              controls: {
                resetLabel: {
                  defaultValue: 10,
                  label: "Reset controls",
                  max: 100,
                  min: 0,
                  target: "shape.resetLabel",
                  type: "slider",
                },
              },
              title: "Transform",
            },
          ],
          title: "Controls",
        },
      },
    });
    const control = schema.panels.controls?.sections.find(
      (section) => section.controls.resetLabel !== undefined,
    )?.controls.resetLabel;
    const store = renderPanel(schema);

    if (!control || typeof control.label !== "string") {
      throw new Error("Expected the Reset controls control.");
    }
    const controlLabel = control.label;

    act(() => {
      store.dispatch({
        label: controlLabel,
        target: control.target,
        type: "controls.setValue",
        value: 30,
      });
    });

    expect(count("executions", "control:resetLabel")).toBe(1);
    expect(count("commits", "section:Transform")).toBe(0);
    resetCounters();

    act(() => {
      store.dispatch({ type: "controls.reset" });
    });

    expect(count("commits", "section:Transform")).toBe(1);
    expect(count("commits", "control:resetLabel")).toBe(1);
    resetCounters();

    act(() => {
      store.dispatch({
        label: controlLabel,
        target: control.target,
        type: "controls.setValue",
        value: 40,
      });
    });

    expect(count("executions", "control:resetLabel")).toBe(1);
    expect(count("commits", "section:Transform")).toBe(0);

    act(() => {
      store.dispatch({ type: "history.undo" });
    });
    resetCounters();

    act(() => {
      store.dispatch({ type: "history.undo" });
    });

    expect(store.getState().values[control.target]).toBe(30);
    expect(count("commits", "section:Transform")).toBe(1);
    resetCounters();

    act(() => {
      store.dispatch({ type: "history.redo" });
    });

    expect(store.getState().values[control.target]).toBe(10);
    expect(count("commits", "section:Transform")).toBe(1);
  });

  it("keeps the reset generation stable across a later ordinary edit", () => {
    const schema = createTwoSliderSchema();
    const alphaTarget = schema.panels.controls?.sections.find(
      (section) => section.controls.alpha !== undefined,
    )?.controls.alpha?.target;
    const store = renderPanel(schema);

    if (!alphaTarget) {
      throw new Error("Expected alpha target.");
    }

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

    expect(count("commits", "section:Transform")).toBeGreaterThan(0);
    resetCounters();

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

    expect(count("executions", "control:alpha")).toBe(1);
    expect(count("commits", "control:alpha")).toBe(1);
    expect(count("executions", "control:beta")).toBe(0);
    expect(count("commits", "control:beta")).toBe(0);
    expect(count("executions", "section:Transform")).toBe(0);
    expect(count("commits", "section:Transform")).toBe(0);

    act(() => {
      store.dispatch({ type: "history.undo" });
    });
    resetCounters();

    act(() => {
      store.dispatch({ type: "history.undo" });
    });

    expect(store.getState().values[alphaTarget]).toBe(30);
    expect(count("commits", "section:Transform")).toBeGreaterThan(0);
    resetCounters();

    act(() => {
      store.dispatch({ type: "history.redo" });
    });

    expect(store.getState().values[alphaTarget]).toBe(10);
    expect(count("commits", "section:Transform")).toBeGreaterThan(0);
  });

  it("keeps unaffected section shells cold for collapse and footer progress state", async () => {
    const schema = defineToolcraft({
      canvas: { enabled: false },
      panels: {
        controls: {
          sections: [
            {
              controls: {
                first: {
                  defaultValue: 1,
                  label: "First value",
                  max: 10,
                  min: 0,
                  target: "first.value",
                  type: "slider",
                },
              },
              title: "First",
            },
            {
              controls: {
                second: {
                  defaultValue: 2,
                  label: "Second value",
                  max: 10,
                  min: 0,
                  target: "second.value",
                  type: "slider",
                },
              },
              title: "Second",
            },
            {
              actionGroup: "primary",
              controls: {
                export: {
                  actions: [{ label: "Export", value: "export" }],
                  target: "actions.export",
                  type: "panelActions",
                },
              },
              title: "Actions",
            },
          ],
          title: "Controls",
        },
      },
    });
    let resolveAction: (() => void) | undefined;
    const pendingAction = new Promise<void>((resolve) => {
      resolveAction = resolve;
    });
    let reportActionProgress: ((progress: number) => void) | undefined;

    renderPanel(schema, {
      onPanelAction: ({ reportProgress }) => {
        reportActionProgress = reportProgress;
        return pendingAction;
      },
    });

    fireEvent.click(
      screen
        .getByText("First")
        .closest<HTMLElement>('[data-slot="control-section-header"]')!,
    );

    expect(count("executions", "section:First")).toBe(1);
    expect(count("commits", "section:First")).toBe(1);
    expect(count("executions", "section:Second")).toBe(0);
    expect(count("commits", "section:Second")).toBe(0);
    resetCounters();

    fireEvent.click(screen.getByRole("button", { name: "Export" }));

    expect(count("executions", "section:First")).toBe(0);
    expect(count("commits", "section:First")).toBe(0);
    expect(count("executions", "section:Second")).toBe(0);
    expect(count("commits", "section:Second")).toBe(0);
    resetCounters();

    act(() => {
      reportActionProgress?.(0.5);
    });

    expect(count("executions", "section:First")).toBe(0);
    expect(count("commits", "section:First")).toBe(0);
    expect(count("executions", "section:Second")).toBe(0);
    expect(count("commits", "section:Second")).toBe(0);

    await act(async () => {
      resolveAction?.();
      await pendingAction;
    });

    expect(count("executions", "section:First")).toBe(0);
    expect(count("commits", "section:Second")).toBe(0);
  });

  it("mounts a visibleWhen section without executing an unaffected section", () => {
    const schema = defineToolcraft({
      canvas: { enabled: false },
      panels: {
        controls: {
          sections: [
            {
              controls: {
                mode: {
                  defaultValue: false,
                  label: "Advanced mode",
                  target: "mode.advanced",
                  type: "switch",
                },
              },
              title: "Always",
            },
            {
              controls: {
                amount: {
                  defaultValue: 5,
                  label: "Amount",
                  max: 10,
                  min: 0,
                  target: "advanced.amount",
                  type: "slider",
                },
              },
              title: "Advanced",
              visibleWhen: { equals: true, target: "mode.advanced" },
            },
          ],
          title: "Controls",
        },
      },
    });
    const modeTarget = schema.panels.controls?.sections.find(
      (section) => section.controls.mode !== undefined,
    )?.controls.mode?.target;
    const store = renderPanel(schema);

    if (!modeTarget) {
      throw new Error("Expected mode target.");
    }

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

    expect(count("executions", "section:Advanced")).toBe(1);
    expect(count("commits", "section:Advanced")).toBe(1);
    expect(count("executions", "section:Always")).toBe(0);
    expect(count("commits", "section:Always")).toBe(0);
  });
});
