import {
  render,
  cleanup,
  screen,
  act,
} from "@testing-library/react";
import { createRef } from "react";
import assert from "node:assert";
import {
  describe,
  test,
  mock,
  afterEach,
  before,
} from "node:test";
import type { IMapRef, TCluster } from "./config/types/index";
import { getNextClustersByPointClick } from "./lib/placemark";
import { Map } from "./index";

class IntersectionObserverMock {

  readonly root: Element | Document | null = null;

  readonly rootMargin: string = "0px";

  readonly thresholds: ReadonlyArray<number> = [ 0 ];

  constructor(
    _callback: (...args: unknown[]) => void,
    _options?: unknown
  ) {}

  disconnect(): void {

    return;

  }

  observe(_target: Element): void {

    return;

  }

  takeRecords(): IntersectionObserverEntry[] {
    return [];
  }

  unobserve(_target: Element): void {

    return;

  }

}

describe("Map", () => {

  before(() => {

    if (!globalThis.IntersectionObserver) {
      globalThis.IntersectionObserver = IntersectionObserverMock as unknown as typeof IntersectionObserver;
    }

  });

  afterEach(() => {
    cleanup();
  });

  test("renders map container with id and attrs", () => {
    render(<Map id={"my-map"} extraAttrs={{ "data-testid": "map-root" }} />);
    const node = screen.getByTestId("map-root");

    assert.notStrictEqual(node, null);
    assert.strictEqual(node.getAttribute("id"), "my-map");
  });

  test("calls onMount and onUnmount callbacks", async () => {
    const onMount = mock.fn();
    const onUnmount = mock.fn();

    const { unmount } = render(<Map onMount={onMount} onUnmount={onUnmount} />);
    await act(async () => {});

    assert.strictEqual(onMount.mock.calls.length, 1);

    unmount();

    assert.strictEqual(onUnmount.mock.calls.length, 1);
  });

  test("exposes imperative ref api", async () => {
    const ref = createRef<IMapRef | null>();
    render(<Map ref={ref} />);
    await act(async () => {});

    assert.notStrictEqual(ref.current, null);
    assert.ok(ref.current?.el instanceof HTMLElement);
    assert.strictEqual(typeof ref.current?.getData, "function");
    assert.strictEqual(typeof ref.current?.stateActions.setClusters, "function");
    assert.strictEqual(typeof ref.current?.stateActions.setIsDisabled, "function");
  });

  test("updates clusters via ref.stateActions.setClusters", async () => {
    const ref = createRef<IMapRef | null>();
    render(<Map ref={ref} />);
    await act(async () => {});

    const clusters: TCluster[] = [
      {
        id: "cluster-1",
        points: [
          {
            id: "point-1",
            coords: [ 55.951244, 37.518423 ],
            state: {
              isActive: true,
              isDisabled: false,
            },
          },
        ],
      },
    ];

    await act(async () => {
      ref.current?.stateActions.setClusters(clusters);
    });

    const data = ref.current?.getData();
    assert.strictEqual(data?.clusters[0]?.id, "cluster-1");
    assert.strictEqual(data?.clusters[0]?.points[0]?.id, "point-1");
    assert.strictEqual(data?.clusters[0]?.points[0]?.state?.isActive, true);
  });

  test("switches active point between two markers in single-active mode", async () => {
    const ref = createRef<IMapRef | null>();

    render(
      <Map
        ref={ref}
        maxActivePoints={1}
        points={[
          {
            id: "point-1",
            coords: [ 55.751244, 37.618423 ],
            state: {
              isActive: false,
              isDisabled: false,
            },
            meta: {
              balloon: {
                title: "Point 1 balloon",
                description: "Point 1",
              },
            },
          },
          {
            id: "point-2",
            coords: [ 55.751444, 37.618923 ],
            state: {
              isActive: false,
              isDisabled: false,
            },
            meta: {
              balloon: {
                title: "Point 2 balloon",
                description: "Point 2",
              },
            },
          },
        ]}
      />
    );

    await act(async () => {});

    await act(async () => {
      ref.current?.stateActions.setClusters((prevClusters: TCluster[]) => {
        return getNextClustersByPointClick({
          clusters: prevClusters,
          pointId: "point-1",
          maxActivePoints: 1,
        }).nextClusters;
      }, false);
    });

    let data = ref.current?.getData();
    let currentPoints = data?.clusters?.[0]?.points ?? [];
    assert.strictEqual(currentPoints.find((point) => point.id === "point-1")?.state?.isActive, true);
    assert.strictEqual(currentPoints.find((point) => point.id === "point-2")?.state?.isActive, false);

    await act(async () => {
      ref.current?.stateActions.setClusters((prevClusters: TCluster[]) => {
        return getNextClustersByPointClick({
          clusters: prevClusters,
          pointId: "point-2",
          maxActivePoints: 1,
        }).nextClusters;
      }, false);
    });

    data = ref.current?.getData();
    currentPoints = data?.clusters?.[0]?.points ?? [];
    assert.strictEqual(currentPoints.find((point) => point.id === "point-1")?.state?.isActive, false);
    assert.strictEqual(currentPoints.find((point) => point.id === "point-2")?.state?.isActive, true);
  });

});
