import React, { Dispatch, useEffect, useState } from "react";
import {
  act,
  create,
  ReactTestRenderer,
  ReactTestRendererJSON,
} from "react-test-renderer";
import { FreezeWithCallback } from "../index";

const SimpleContent = () => <div />;

interface InnerProps {
  value: number;
}
// eslint-disable-next-line unused-imports/no-unused-vars
const Inner = ({ value }: InnerProps) => <div />;

interface TestSubscriber {
  renderCount: number;
  subscription: Dispatch<number>;
}

function createSubscriberComponent(): [React.FC, TestSubscriber] {
  const testState: TestSubscriber = {
    renderCount: 0,
    subscription: () => {},
  };

  const Subscriber: React.FC = () => {
    const [value, setValue] = useState(0);

    useEffect(() => {
      testState.subscription = setValue;
    }, []);

    testState.renderCount += 1;

    return <Inner value={value} />;
  };

  return [Subscriber, testState];
}

interface ContainerProps {
  freeze: boolean;
  children: React.ReactNode;
}

const Container = ({ freeze, children }: ContainerProps) => (
  <FreezeWithCallback freeze={freeze}>{children}</FreezeWithCallback>
);

function setupTest(initialFreeze: boolean = false) {
  let testRenderer: ReactTestRenderer | undefined;
  const [Subscriber, subscriberState] = createSubscriberComponent();

  act(() => {
    testRenderer = create(
      <Container freeze={initialFreeze}>
        <Subscriber />
      </Container>
    );
  });

  return {
    testRenderer,
    testInstance: testRenderer?.root,
    subscriberState,
    updateFreeze: (freeze: boolean) => {
      act(() =>
        testRenderer?.update(
          <Container freeze={freeze}>
            <Subscriber />
          </Container>
        )
      );
    },
  };
}

describe("FreezeWithCallback", () => {
  test("Renders stuff not frozen", () => {
    const testRenderer = create(
      <Container freeze={false}>
        <SimpleContent />
      </Container>
    );

    expect(testRenderer.root.findByType(SimpleContent)).toBeTruthy();
  });

  test("Does not render stuff when frozen", () => {
    const testRenderer = create(
      <Container freeze>
        <SimpleContent />
      </Container>
    );

    expect(testRenderer.root.findAllByType(SimpleContent)).toHaveLength(0);
  });

  test("Stuff is gone after freeze", () => {
    const testRenderer = create(
      <Container freeze={false}>
        <SimpleContent />
      </Container>
    );

    expect(testRenderer.root.findByType(SimpleContent)).toBeTruthy();

    act(() =>
      testRenderer.update(
        <Container freeze>
          <SimpleContent />
        </Container>
      )
    );

    expect(testRenderer.toJSON()).toBe(null);
  });

  test("Updates work when not frozen", () => {
    const { testInstance, subscriberState } = setupTest();

    expect(testInstance?.findByType(Inner).props.value).toEqual(0);
    act(() => subscriberState.subscription(1));
    expect(testInstance?.findByType(Inner).props.value).toEqual(1);
    expect(subscriberState.renderCount).toBe(2);
  });

  test("Updates does not propagate when frozen", () => {
    const { testInstance, subscriberState, updateFreeze } = setupTest();

    expect(testInstance?.findByType(Inner).props.value).toEqual(0);
    updateFreeze(true);
    act(() => subscriberState.subscription(1));
    expect(testInstance?.findByType(Inner).props.value).toEqual(0);
    expect(subscriberState.renderCount).toBe(1);
  });

  test("State persists after defrost", () => {
    const { testInstance, subscriberState, updateFreeze, testRenderer } =
      setupTest();

    expect(testInstance?.findByType(Inner).props.value).toEqual(0);
    act(() => subscriberState.subscription(1));
    expect(testInstance?.findByType(Inner).props.value).toEqual(1);

    updateFreeze(true);
    expect(testRenderer?.toJSON()).toBe(null);

    updateFreeze(false);
    expect((testRenderer?.toJSON() as ReactTestRendererJSON).type).toBe("div");
    expect(testInstance?.findByType(Inner).props.value).toEqual(1);
  });

  test("Update propagate after defrost", () => {
    const { testInstance, subscriberState, updateFreeze } = setupTest();

    updateFreeze(true);
    act(() => subscriberState.subscription(1));
    act(() => subscriberState.subscription(2));
    act(() => subscriberState.subscription(3));
    expect(testInstance?.findByType(Inner).props.value).toEqual(0);

    updateFreeze(false);
    expect(testInstance?.findByType(Inner).props.value).toEqual(3);
    expect(subscriberState.renderCount).toBe(2);
  });
});
