// @vitest-environment jsdom
// ---------------------------------------------------------------------------
// 0747 T-007: UpdateDropdown rows MUST expose an inline Update button with a
// title= tooltip generated by formatUpdateLocationTooltip. For all-readonly
// (plugin-bundled-only) skills, the click is blocked with an informative toast
// instead of invoking the update endpoint.
// ---------------------------------------------------------------------------
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;

const toastMock = vi.fn();
vi.mock("../ToastProvider", () => ({
  useToast: () => ({ toast: toastMock, dismiss: vi.fn(), clear: vi.fn() }),
}));

// 0766 F-002: stub StudioContext so UpdateDropdown's onSkillUpdated dep
// doesn't require the provider tree.
vi.mock("../../StudioContext", () => ({
  useStudio: () => ({ onSkillUpdated: vi.fn() }),
}));

const postSkillUpdateMock = vi.fn();
vi.mock("../../api", () => ({
  api: { postSkillUpdate: postSkillUpdateMock },
}));

async function mountDropdown(updates: unknown[]) {
  const React = await import("react");
  const { createRoot } = await import("react-dom/client");
  const { act } = await import("react");
  const UpdateDropdown = (await import("../UpdateDropdown")).default;
  const container = document.createElement("div");
  document.body.appendChild(container);
  const root = createRoot(container);
  await act(async () => {
    root.render(
      React.createElement(UpdateDropdown, {
        updates,
        isRefreshing: false,
        onRefresh: () => {},
        onSelectSkill: () => {},
        onViewAll: () => {},
        onClose: () => {},
      }),
    );
  });
  return {
    container,
    act: (fn: () => void | Promise<void>) => act(async () => { await fn(); }),
    unmount() {
      act(() => root.unmount());
      container.remove();
    },
  };
}

beforeEach(() => {
  toastMock.mockReset();
  postSkillUpdateMock.mockReset();
});

afterEach(() => {
  vi.clearAllMocks();
});

describe("UpdateDropdown inline Update button (0747 T-007)", () => {
  it("TC-001: shows tooltip with location count and triggers postSkillUpdate on click", async () => {
    postSkillUpdateMock.mockResolvedValue({ ok: true, status: 200, body: "", version: "1.1.0" });
    const updates = [
      {
        name: "owner/repo/foo",
        installed: "1.0.0",
        latest: "1.1.0",
        updateAvailable: true,
        installLocations: [
          {
            scope: "personal",
            agent: "claude-code",
            agentLabel: "Claude Code",
            dir: "/h/.claude/skills/foo",
            symlinked: false,
            readonly: false,
          },
          {
            scope: "personal",
            agent: "codex",
            agentLabel: "Codex CLI",
            dir: "/h/.codex/skills/foo",
            symlinked: false,
            readonly: false,
          },
        ],
        localPlugin: "foo",
        localSkill: "foo",
      },
    ];
    const h = await mountDropdown(updates);
    try {
      const btn = h.container.querySelector(
        "[data-testid='update-dropdown-row-update']",
      ) as HTMLButtonElement;
      expect(btn).toBeTruthy();
      expect(btn.title).toBe(
        "Updates 2 locations: personal (Claude Code, Codex CLI)",
      );
      await h.act(async () => {
        btn.click();
        await Promise.resolve();
      });
      expect(postSkillUpdateMock).toHaveBeenCalledWith("foo", "foo");
    } finally {
      h.unmount();
    }
  });

  it("TC-002: when all locations are readonly (plugin-bundled), click is blocked + informative toast shown", async () => {
    const updates = [
      {
        name: "owner/repo/foo",
        installed: "1.0.0",
        latest: "1.1.0",
        updateAvailable: true,
        installLocations: [
          {
            scope: "plugin",
            agent: "claude-code",
            agentLabel: "Claude Code",
            dir: "/h/.claude/plugins/cache/.../foo",
            pluginSlug: "mobile",
            symlinked: false,
            readonly: true,
          },
        ],
      },
    ];
    const h = await mountDropdown(updates);
    try {
      const btn = h.container.querySelector(
        "[data-testid='update-dropdown-row-update']",
      ) as HTMLButtonElement;
      expect(btn).toBeTruthy();
      await h.act(async () => {
        btn.click();
        await Promise.resolve();
      });
      expect(postSkillUpdateMock).not.toHaveBeenCalled();
      expect(toastMock).toHaveBeenCalledTimes(1);
      const msg = (toastMock.mock.calls[0][0] as { message: string }).message;
      expect(msg).toContain("plugin");
      expect(msg).toContain("mobile");
    } finally {
      h.unmount();
    }
  });

  it("TC-003: pinned suffix appears in tooltip", async () => {
    const updates = [
      {
        name: "owner/repo/foo",
        installed: "1.0.0",
        latest: "1.1.0",
        updateAvailable: true,
        pinned: true,
        pinnedVersion: "1.0.0",
        installLocations: [
          {
            scope: "personal",
            agent: "claude-code",
            agentLabel: "Claude Code",
            dir: "/h/.claude/skills/foo",
            symlinked: false,
            readonly: false,
          },
        ],
      },
    ];
    const h = await mountDropdown(updates);
    try {
      const btn = h.container.querySelector(
        "[data-testid='update-dropdown-row-update']",
      ) as HTMLButtonElement;
      expect(btn.title).toContain("pinned (skipped)");
    } finally {
      h.unmount();
    }
  });

  it("TC-004: backwards-compat — installLocations missing → tooltip falls back to generic, click still works", async () => {
    postSkillUpdateMock.mockResolvedValue({ ok: true, status: 200, body: "", version: "1.1.0" });
    const updates = [
      {
        name: "owner/repo/foo",
        installed: "1.0.0",
        latest: "1.1.0",
        updateAvailable: true,
        // no installLocations, no localPlugin/localSkill
      },
    ];
    const h = await mountDropdown(updates);
    try {
      const btn = h.container.querySelector(
        "[data-testid='update-dropdown-row-update']",
      ) as HTMLButtonElement;
      expect(btn.title).toBe("No tracked install — click to view details");
      // Click should still work — falls back to canonical-name parsing
      await h.act(async () => {
        btn.click();
        await Promise.resolve();
      });
      expect(postSkillUpdateMock).toHaveBeenCalledTimes(1);
    } finally {
      h.unmount();
    }
  });
});
