import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { type ReactNode } from "react";
import React from "react";
import { ToastContext, type ToastContextValue } from "@/hooks/useToast";
import { useShare } from "./useShare";

const mockShow = vi.fn();
const ctxValue: ToastContextValue = { show: mockShow };

function wrapper({ children }: { children: ReactNode }) {
  return React.createElement(
    ToastContext.Provider,
    { value: ctxValue },
    children,
  );
}

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

describe("useShare", () => {
  describe("copyToClipboard", () => {
    it("copies text and shows success toast", async () => {
      Object.assign(navigator, {
        clipboard: { writeText: vi.fn().mockResolvedValue(undefined) },
      });

      const { result } = renderHook(() => useShare(), { wrapper });

      await act(async () => {
        await result.current.copyToClipboard("hello");
      });

      expect(navigator.clipboard.writeText).toHaveBeenCalledWith("hello");
      expect(mockShow).toHaveBeenCalledWith("已复制");
    });

    it("uses custom success message", async () => {
      Object.assign(navigator, {
        clipboard: { writeText: vi.fn().mockResolvedValue(undefined) },
      });

      const { result } = renderHook(() => useShare(), { wrapper });

      await act(async () => {
        await result.current.copyToClipboard("hello", "复制成功");
      });

      expect(mockShow).toHaveBeenCalledWith("复制成功");
    });

    it("shows error toast on clipboard failure", async () => {
      Object.assign(navigator, {
        clipboard: { writeText: vi.fn().mockRejectedValue(new Error()) },
      });

      const { result } = renderHook(() => useShare(), { wrapper });

      await act(async () => {
        await result.current.copyToClipboard("hello");
      });

      expect(mockShow).toHaveBeenCalledWith("复制失败，请手动复制");
    });
  });

  describe("share", () => {
    it("uses native share when available", async () => {
      const mockNativeShare = vi.fn().mockResolvedValue(undefined);
      Object.assign(navigator, {
        share: mockNativeShare,
        canShare: vi.fn().mockReturnValue(true),
      });

      const { result } = renderHook(() => useShare(), { wrapper });

      let shared = false;
      await act(async () => {
        shared = await result.current.share({ title: "Test", text: "test" });
      });

      expect(shared).toBe(true);
      expect(mockNativeShare).toHaveBeenCalled();
    });

    it("calls fallback when native share unavailable", async () => {
      Object.assign(navigator, {
        share: undefined,
        canShare: undefined,
      });

      const fallback = vi.fn();
      const { result } = renderHook(() => useShare(), { wrapper });

      let shared = false;
      await act(async () => {
        shared = await result.current.share({ title: "Test" }, fallback);
      });

      expect(shared).toBe(false);
      expect(fallback).toHaveBeenCalled();
    });

    it("handles AbortError gracefully", async () => {
      const abortError = new Error("User aborted");
      abortError.name = "AbortError";
      Object.assign(navigator, {
        share: vi.fn().mockRejectedValue(abortError),
        canShare: vi.fn().mockReturnValue(true),
      });

      const { result } = renderHook(() => useShare(), { wrapper });

      let shared = false;
      await act(async () => {
        shared = await result.current.share({ title: "Test" });
      });

      expect(shared).toBe(true);
    });
  });
});
