import { describe, test, expect, vi } from 'vitest';
import React from 'react';
import { createRoot, Root } from 'react-dom/client';
import { act } from 'react-dom/test-utils';
import { PageProvider, usePages } from './PageContext';

/**
 * D6 regression coverage: `switchPage` and `deletePage` used to run their
 * side effects (actions.deserialize via loadState, setActivePageId,
 * activePageIdRef mutation) INSIDE a setPages/setHeaderPage/setFooterPage
 * functional updater. React (in <React.StrictMode>, dev builds) invokes
 * updater functions passed to setState twice to help surface exactly this
 * kind of impurity — so the old code deserialized twice per page switch.
 * These tests mount under StrictMode and assert the side effect fires once.
 */
const deserializeMock = vi.fn();
vi.mock('@craftjs/core', () => ({
  useEditor: () => ({
    query: { serialize: () => '{}' },
    actions: { deserialize: deserializeMock },
  }),
}));

let container: HTMLDivElement;
let root: Root;

function render(ui: React.ReactElement) {
  container = document.createElement('div');
  document.body.appendChild(container);
  act(() => {
    root = createRoot(container);
    root.render(ui);
  });
}

function unmount() {
  act(() => {
    root.unmount();
  });
  container.remove();
}

/** `loadState` defers `actions.deserialize` via a real setTimeout(fn, 0). */
async function flushTimers() {
  await act(async () => {
    await new Promise((resolve) => setTimeout(resolve, 10));
  });
}

describe('PageContext pure updaters (D6)', () => {
  test('switchPage deserializes exactly once under StrictMode double-invocation', async () => {
    deserializeMock.mockClear();
    let ctx: ReturnType<typeof usePages> | null = null;
    const Consumer: React.FC = () => {
      ctx = usePages();
      return null;
    };

    render(
      <React.StrictMode>
        <PageProvider>
          <Consumer />
        </PageProvider>
      </React.StrictMode>,
    );

    act(() => {
      ctx!.addPage('About', 'about');
    });
    await flushTimers();
    deserializeMock.mockClear();

    act(() => {
      ctx!.switchPage('home');
    });
    await flushTimers();

    expect(deserializeMock).toHaveBeenCalledTimes(1);
    expect(ctx!.activePageId).toBe('home');

    unmount();
  });

  test('deletePage: deserialize fires exactly once when deleting the active page', async () => {
    deserializeMock.mockClear();
    let ctx: ReturnType<typeof usePages> | null = null;
    const Consumer: React.FC = () => {
      ctx = usePages();
      return null;
    };

    render(
      <React.StrictMode>
        <PageProvider>
          <Consumer />
        </PageProvider>
      </React.StrictMode>,
    );

    act(() => {
      ctx!.addPage('About', 'about');
    });
    await flushTimers();

    const aboutId = ctx!.pages[1].id;
    act(() => {
      ctx!.switchPage(aboutId);
    });
    await flushTimers();
    deserializeMock.mockClear();

    act(() => {
      ctx!.deletePage(aboutId);
    });
    await flushTimers();

    expect(deserializeMock).toHaveBeenCalledTimes(1);
    expect(ctx!.pages.length).toBe(1);
    expect(ctx!.activePageId).toBe('home');

    unmount();
  });

  test("deletePage: can't delete the last remaining page", async () => {
    let ctx: ReturnType<typeof usePages> | null = null;
    const Consumer: React.FC = () => {
      ctx = usePages();
      return null;
    };

    render(
      <React.StrictMode>
        <PageProvider>
          <Consumer />
        </PageProvider>
      </React.StrictMode>,
    );

    act(() => {
      ctx!.deletePage(ctx!.pages[0].id);
    });

    expect(ctx!.pages.length).toBe(1);

    unmount();
  });
});
