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 {
  getToolcraftCanvasSizeTargetDimension,
  toolcraftRuntimeOwnedTargets,
  toolcraftTimelinePanelExtendedTarget,
  toolcraftTimelinePanelVisibleTarget,
} from "../../../schema/runtime-targets";
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>(),
}));
const settingsTransfer = vi.hoisted(() => ({
  download: vi.fn(),
  import: vi.fn(),
}));

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("../../app-shell/settings-transfer", () => ({
  downloadToolcraftSettings: settingsTransfer.download,
  importToolcraftSettings: settingsTransfer.import,
}));

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, args.id);
      return (
        <>
          <CommitProbe id={args.id} />
          {actual.renderBasicControl(args)}
        </>
      );
    },
  };
});

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

  return {
    ...actual,
    renderFileDropControl: (
      args: Parameters<typeof actual.renderFileDropControl>[0],
    ): React.ReactNode => {
      increment(counters.executions, args.id);
      return (
        <>
          <CommitProbe id={args.id} />
          {actual.renderFileDropControl(args)}
        </>
      );
    },
  };
});

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

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

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

function renderPanel(schema: ReturnType<typeof defineToolcraft>): ToolcraftExternalStore {
  let store: ToolcraftExternalStore | undefined;

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

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

  resetCounters();
  return store;
}

const canvasWidthTarget = toolcraftRuntimeOwnedTargets.find(
  (target) => getToolcraftCanvasSizeTargetDimension(target) === "width",
);

if (!canvasWidthTarget) {
  throw new Error("Expected a runtime canvas width target.");
}

afterEach(() => {
  cleanup();
  resetCounters();
  settingsTransfer.download.mockReset();
  settingsTransfer.import.mockReset();
  window.localStorage.clear();
});

describe("ControlsPanel dependency isolation", () => {
  it("updates canvas size and timeline runtime-target controls independently", () => {
    const schema = defineToolcraft({
      canvas: {
        enabled: true,
        size: { height: 180, unit: "px", width: 320 },
        sizing: { mode: "editable-output" },
      },
      panels: {
        controls: {
          sections: [
            {
              controls: {
                timelineVisible: {
                  defaultValue: true,
                  label: "Timeline visible",
                  target: toolcraftTimelinePanelVisibleTarget,
                  type: "switch",
                },
              },
              title: "Panel",
            },
          ],
          title: "Controls",
        },
        timeline: { mode: "playback" },
      },
    });
    const store = renderPanel(schema);

    act(() => {
      store.dispatch({
        size: { height: 180, unit: "px", width: 640 },
        type: "canvas.setSize",
      });
    });

    expect(count("canvasWidth")).toEqual({ commits: 1, executions: 1 });
    expect(count("timelineVisible")).toEqual({ commits: 0, executions: 0 });
    expect(count("timelineExtended")).toEqual({ commits: 0, executions: 0 });
    resetCounters();

    act(() => {
      store.dispatch({ hidden: true, panelId: "timeline", type: "panels.setHidden" });
    });

    expect(count("timelineVisible")).toEqual({ commits: 1, executions: 1 });
    expect(count("canvasWidth")).toEqual({ commits: 0, executions: 0 });
    expect(count("timelineExtended")).toEqual({ commits: 0, executions: 0 });
    resetCounters();

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

    expect(count("timelineExtended")).toEqual({ commits: 1, executions: 1 });
    expect(count("canvasWidth")).toEqual({ commits: 0, executions: 0 });
    expect(count("timelineVisible")).toEqual({ commits: 0, executions: 0 });
    expect(store.getState().canvas.size.width).toBe(640);
    expect(canvasWidthTarget).toBe(
      schema.panels.controls?.sections[0]?.controls.canvasWidth?.target,
    );
  });

  it("updates a media renderer for media assets and canvas size only", () => {
    const schema = defineToolcraft({
      canvas: { enabled: true },
      panels: {
        controls: {
          sections: [
            {
              controls: {
                source: {
                  label: "Source",
                  target: "source.media",
                  type: "fileDrop",
                },
                amount: {
                  defaultValue: 1,
                  label: "Amount",
                  max: 10,
                  min: 0,
                  target: "effect.amount",
                  type: "slider",
                },
              },
              title: "Source",
            },
          ],
          title: "Controls",
        },
      },
    });
    const sourceTarget = schema.panels.controls?.sections.find(
      (section) => section.controls.source !== undefined,
    )?.controls.source?.target;
    const amountTarget = schema.panels.controls?.sections.find(
      (section) => section.controls.amount !== undefined,
    )?.controls.amount?.target;
    const store = renderPanel(schema);

    if (!sourceTarget || !amountTarget) {
      throw new Error("Expected source and amount targets.");
    }

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

    expect(count("amount")).toEqual({ commits: 1, executions: 1 });
    expect(count("source")).toEqual({ commits: 0, executions: 0 });
    resetCounters();

    act(() => {
      store.dispatch({
        asset: {
          dataUrl: "data:image/png;base64,test",
          fileName: "source.png",
          mimeType: "image/png",
          position: { x: 0, y: 0 },
          size: { height: 100, unit: "px", width: 100 },
          sourceTarget,
        },
        type: "media.import",
      });
    });

    expect(count("source")).toEqual({ commits: 1, executions: 1 });
    expect(count("amount")).toEqual({ commits: 0, executions: 0 });
    resetCounters();

    act(() => {
      store.dispatch({
        size: { height: 360, unit: "px", width: 640 },
        type: "canvas.setSize",
      });
    });

    expect(count("source")).toEqual({ commits: 1, executions: 1 });
    expect(count("amount")).toEqual({ commits: 0, executions: 0 });
  });

  it("reads the current live state when settings transfer is invoked", () => {
    const schema = defineToolcraft({
      canvas: { enabled: false },
      panels: {
        controls: {
          sections: [
            {
              controls: {
                prompt: {
                  defaultValue: "Initial",
                  label: "Prompt",
                  target: "generation.prompt",
                  type: "text",
                },
              },
              title: "Prompt",
            },
          ],
          title: "Controls",
        },
      },
      settingsTransfer: true,
    });
    const promptTarget = schema.panels.controls?.sections.find(
      (section) => section.title === "Prompt",
    )?.controls.prompt?.target;
    const store = renderPanel(schema);

    if (!promptTarget) {
      throw new Error("Expected prompt target.");
    }

    act(() => {
      store.dispatch({
        target: promptTarget,
        type: "controls.setValue",
        value: "Current",
      });
    });
    fireEvent.click(screen.getByRole("button", { name: "Export Settings" }));

    expect(settingsTransfer.download).toHaveBeenCalledTimes(1);
    expect(settingsTransfer.download.mock.calls[0]?.[0].values[promptTarget]).toBe(
      "Current",
    );
  });
});
