import React from "react";
import { render } from "@testing-library/react-native";
import { Text, View } from "react-native";
import { withAsyncRenderHOC } from "../withAsyncRender";

describe("withAsyncRenderHOC", () => {
  // Test component that requires onAsyncRender
  const TestComponent = ({
    onAsyncRender,
    text,
  }: {
    onAsyncRender: () => void;
    text?: string;
  }) => {
    React.useEffect(() => {
      onAsyncRender();
    }, [onAsyncRender]);

    return (
      <View>
        <Text>{text || "Test"}</Text>
      </View>
    );
  };

  it("should wrap component and provide onAsyncRender callback", () => {
    const WrappedComponent = withAsyncRenderHOC(TestComponent);

    const wrapper = render(<WrappedComponent text="Hello" />);

    expect(wrapper.toJSON()).not.toBeNull();
    expect(wrapper.getByText("Hello")).toBeDefined();
  });

  it("should call emitAsyncElementRegistrate on mount", () => {
    const mockRegistrate = jest.fn();
    const WrappedComponent = withAsyncRenderHOC(TestComponent);

    render(<WrappedComponent emitAsyncElementRegistrate={mockRegistrate} />);

    expect(mockRegistrate).toHaveBeenCalledTimes(1);
  });

  it("should call emitAsyncElementLayout when onAsyncRender is called", () => {
    const mockLayout = jest.fn();

    const ComponentWithAsyncCall = ({
      onAsyncRender,
    }: {
      onAsyncRender: () => void;
    }) => {
      return (
        <View onLayout={onAsyncRender}>
          <Text>Async Component</Text>
        </View>
      );
    };

    const WrappedComponent = withAsyncRenderHOC(ComponentWithAsyncCall);

    const wrapper = render(
      <WrappedComponent emitAsyncElementLayout={mockLayout} />
    );

    // Trigger onLayout
    const view = wrapper.UNSAFE_getByType(View);
    view.props.onLayout();

    expect(mockLayout).toHaveBeenCalled();
  });

  it("should return unsubscribe function from emitAsyncElementRegistrate", () => {
    const mockUnsubscribe = jest.fn();
    const mockRegistrate = jest.fn(() => mockUnsubscribe);
    const WrappedComponent = withAsyncRenderHOC(TestComponent);

    const { unmount } = render(
      <WrappedComponent emitAsyncElementRegistrate={mockRegistrate} />
    );

    expect(mockRegistrate).toHaveBeenCalledTimes(1);

    // Unmount should trigger cleanup
    unmount();

    expect(mockUnsubscribe).toHaveBeenCalled();
  });

  it("should work without emitAsyncElementRegistrate prop", () => {
    const WrappedComponent = withAsyncRenderHOC(TestComponent);

    const wrapper = render(<WrappedComponent text="No Registration" />);

    expect(wrapper.toJSON()).not.toBeNull();
    expect(wrapper.getByText("No Registration")).toBeDefined();
  });

  it("should work without emitAsyncElementLayout prop", () => {
    const ComponentWithLayout = ({
      onAsyncRender,
    }: {
      onAsyncRender: () => void;
    }) => {
      return (
        <View onLayout={onAsyncRender}>
          <Text>Test Layout</Text>
        </View>
      );
    };

    const WrappedComponent = withAsyncRenderHOC(ComponentWithLayout);

    const wrapper = render(<WrappedComponent />);

    // Should not throw when calling onLayout without emitAsyncElementLayout
    const view = wrapper.UNSAFE_getByType(View);
    expect(() => view.props.onLayout()).not.toThrow();
  });

  it("should pass through all other props to wrapped component", () => {
    const ComponentWithProps = ({
      customProp,
      anotherProp,
    }: {
      onAsyncRender: () => void;
      customProp: string;
      anotherProp: number;
    }) => {
      return (
        <View>
          <Text>{customProp}</Text>
          <Text>{anotherProp}</Text>
        </View>
      );
    };

    const WrappedComponent = withAsyncRenderHOC(ComponentWithProps);

    const wrapper = render(
      <WrappedComponent customProp="custom value" anotherProp={42} />
    );

    expect(wrapper.getByText("custom value")).toBeDefined();
    expect(wrapper.getByText("42")).toBeDefined();
  });

  it("should call emitAsyncElementLayout multiple times if onAsyncRender is called multiple times", () => {
    const mockLayout = jest.fn();

    const ComponentWithMultipleCalls = ({
      onAsyncRender,
    }: {
      onAsyncRender: () => void;
    }) => {
      return (
        <View>
          <View onLayout={onAsyncRender} testID="view1">
            <Text>First</Text>
          </View>
          <View onLayout={onAsyncRender} testID="view2">
            <Text>Second</Text>
          </View>
        </View>
      );
    };

    const WrappedComponent = withAsyncRenderHOC(ComponentWithMultipleCalls);

    const wrapper = render(
      <WrappedComponent emitAsyncElementLayout={mockLayout} />
    );

    // Trigger both onLayout callbacks
    const view1 = wrapper.getByTestId("view1");
    const view2 = wrapper.getByTestId("view2");

    view1.props.onLayout();
    view2.props.onLayout();

    expect(mockLayout).toHaveBeenCalledTimes(2);
  });

  it("should handle components that use onAsyncRender in effects", () => {
    const mockLayout = jest.fn();

    const ComponentWithEffect = ({
      onAsyncRender,
    }: {
      onAsyncRender: () => void;
    }) => {
      React.useEffect(() => {
        // Simulate async operation completing
        const timer = setTimeout(() => {
          onAsyncRender();
        }, 0);

        return () => clearTimeout(timer);
      }, [onAsyncRender]);

      return (
        <View>
          <Text>Effect Component</Text>
        </View>
      );
    };

    const WrappedComponent = withAsyncRenderHOC(ComponentWithEffect);

    render(<WrappedComponent emitAsyncElementLayout={mockLayout} />);

    // The effect should have been triggered
    return new Promise((resolve) => {
      setTimeout(() => {
        expect(mockLayout).toHaveBeenCalled();
        resolve(true);
      }, 10);
    });
  });
});
