import { describe, it, expect, vi } from "vitest";
import {
  resolveAction,
  executeAction,
  interpolateString,
  actionBinding,
} from "./actions";

describe("interpolateString", () => {
  it("interpolates ${path} expressions", () => {
    const data = { user: { name: "Alice" }, count: 5 };

    expect(interpolateString("Hello ${/user/name}!", data)).toBe(
      "Hello Alice!",
    );
    expect(interpolateString("${/user/name} has ${/count} items", data)).toBe(
      "Alice has 5 items",
    );
  });

  it("returns string unchanged when no variables", () => {
    expect(interpolateString("No vars here", {})).toBe("No vars here");
  });

  it("replaces missing values with empty string", () => {
    expect(interpolateString("Hello ${/missing}!", {})).toBe("Hello !");
  });

  it("handles multiple occurrences of same variable", () => {
    const data = { name: "Bob" };
    expect(interpolateString("${/name} says ${/name}", data)).toBe(
      "Bob says Bob",
    );
  });
});

describe("resolveAction", () => {
  it("resolves literal params", () => {
    const resolved = resolveAction(
      {
        action: "navigate",
        params: { url: "/home", count: 5 },
      },
      {},
    );

    expect(resolved.action).toBe("navigate");
    expect(resolved.params.url).toBe("/home");
    expect(resolved.params.count).toBe(5);
  });

  it("resolves dynamic $state params", () => {
    const data = { userId: 123, settings: { theme: "dark" } };
    const resolved = resolveAction(
      {
        action: "updateUser",
        params: {
          id: { $state: "/userId" },
          theme: { $state: "/settings/theme" },
        },
      },
      data,
    );

    expect(resolved.params.id).toBe(123);
    expect(resolved.params.theme).toBe("dark");
  });

  it("interpolates confirmation messages", () => {
    const data = { user: { name: "Alice" } };
    const resolved = resolveAction(
      {
        action: "delete",
        confirm: {
          title: "Delete ${/user/name}",
          message: "Are you sure you want to delete ${/user/name}?",
        },
      },
      data,
    );

    expect(resolved.confirm?.title).toBe("Delete Alice");
    expect(resolved.confirm?.message).toBe(
      "Are you sure you want to delete Alice?",
    );
  });

  it("preserves onSuccess and onError handlers", () => {
    const resolved = resolveAction(
      {
        action: "save",
        onSuccess: { navigate: "/success" },
        onError: { set: { error: "$error.message" } },
      },
      {},
    );

    expect(resolved.onSuccess).toEqual({ navigate: "/success" });
    expect(resolved.onError).toEqual({ set: { error: "$error.message" } });
  });
});

describe("executeAction", () => {
  it("calls the handler with resolved params", async () => {
    const handler = vi.fn().mockResolvedValue(undefined);

    await executeAction({
      action: { action: "test", params: { value: 42 } },
      handler,
      setState: vi.fn(),
    });

    expect(handler).toHaveBeenCalledWith({ value: 42 });
  });

  it("handles onSuccess with navigate", async () => {
    const navigate = vi.fn();

    await executeAction({
      action: {
        action: "test",
        params: {},
        onSuccess: { navigate: "/success" },
      },
      handler: vi.fn().mockResolvedValue(undefined),
      setState: vi.fn(),
      navigate,
    });

    expect(navigate).toHaveBeenCalledWith("/success");
  });

  it("handles onSuccess with set", async () => {
    const setState = vi.fn();

    await executeAction({
      action: {
        action: "test",
        params: {},
        onSuccess: { set: { saved: true, message: "Done" } },
      },
      handler: vi.fn().mockResolvedValue(undefined),
      setState,
    });

    expect(setState).toHaveBeenCalledWith("saved", true);
    expect(setState).toHaveBeenCalledWith("message", "Done");
  });

  it("handles onSuccess with action", async () => {
    const executeActionFn = vi.fn();

    await executeAction({
      action: {
        action: "test",
        params: {},
        onSuccess: { action: "followUp" },
      },
      handler: vi.fn().mockResolvedValue(undefined),
      setState: vi.fn(),
      executeAction: executeActionFn,
    });

    expect(executeActionFn).toHaveBeenCalledWith("followUp");
  });

  it("handles onError with set", async () => {
    const setState = vi.fn();
    const error = new Error("Something went wrong");

    await executeAction({
      action: {
        action: "test",
        params: {},
        onError: { set: { error: "$error.message" } },
      },
      handler: vi.fn().mockRejectedValue(error),
      setState,
    });

    expect(setState).toHaveBeenCalledWith("error", "Something went wrong");
  });

  it("handles onError with action", async () => {
    const executeActionFn = vi.fn();
    const error = new Error("Failed");

    await executeAction({
      action: {
        action: "test",
        params: {},
        onError: { action: "handleError" },
      },
      handler: vi.fn().mockRejectedValue(error),
      setState: vi.fn(),
      executeAction: executeActionFn,
    });

    expect(executeActionFn).toHaveBeenCalledWith("handleError");
  });

  it("re-throws error when no onError handler", async () => {
    const error = new Error("Unhandled");

    await expect(
      executeAction({
        action: { action: "test", params: {} },
        handler: vi.fn().mockRejectedValue(error),
        setState: vi.fn(),
      }),
    ).rejects.toThrow("Unhandled");
  });
});

describe("actionBinding helper", () => {
  describe("simple", () => {
    it("creates binding with action only", () => {
      const a = actionBinding.simple("navigate");

      expect(a.action).toBe("navigate");
      expect(a.params).toBeUndefined();
    });

    it("creates binding with params", () => {
      const a = actionBinding.simple("navigate", { url: "/home" });

      expect(a.action).toBe("navigate");
      expect(a.params).toEqual({ url: "/home" });
    });
  });

  describe("withConfirm", () => {
    it("creates binding with confirmation", () => {
      const a = actionBinding.withConfirm("delete", {
        title: "Confirm",
        message: "Are you sure?",
      });

      expect(a.action).toBe("delete");
      expect(a.confirm).toEqual({
        title: "Confirm",
        message: "Are you sure?",
      });
    });

    it("creates binding with confirmation and params", () => {
      const a = actionBinding.withConfirm(
        "delete",
        { title: "Delete", message: "Delete item?" },
        { id: 123 },
      );

      expect(a.action).toBe("delete");
      expect(a.params).toEqual({ id: 123 });
      expect(a.confirm?.title).toBe("Delete");
    });
  });

  describe("withSuccess", () => {
    it("creates binding with navigate success handler", () => {
      const a = actionBinding.withSuccess("save", { navigate: "/success" });

      expect(a.action).toBe("save");
      expect(a.onSuccess).toEqual({ navigate: "/success" });
    });

    it("creates binding with set success handler", () => {
      const a = actionBinding.withSuccess("save", { set: { saved: true } });

      expect(a.onSuccess).toEqual({ set: { saved: true } });
    });

    it("creates binding with success handler and params", () => {
      const a = actionBinding.withSuccess(
        "save",
        { navigate: "/done" },
        { data: "test" },
      );

      expect(a.params).toEqual({ data: "test" });
      expect(a.onSuccess).toEqual({ navigate: "/done" });
    });
  });
});
