import { render, waitFor } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { useEditorExtensionsContext } from '@ablogcms/block-editor/editor-extensions';
import BlockEditorExtensionsProvider from './block-editor-extensions-provider';

const dummyExtension = { name: 'dummy' };
const dummyBlockDefinition = { label: 'ダミー' };

vi.mock('@ablogcms/block-editor/tiptap', () => ({
  tiptap: {},
}));

const getBlockExtensionsMock = vi.fn(() => [dummyExtension]);
const getBlockDefinitionsMock = vi.fn(() => ({ callout: dummyBlockDefinition }));

vi.mock('../block-definitions', () => ({
  getBlockExtensions: () => getBlockExtensionsMock(),
  getBlockDefinitions: () => getBlockDefinitionsMock(),
}));

function ContextConsumer() {
  const value = useEditorExtensionsContext();
  return <span data-testid="value">{value === null ? 'null' : JSON.stringify(value)}</span>;
}

describe('BlockEditorExtensionsProvider', () => {
  it('解決前は null を子孫の Context に渡す（BlockEditor を早期マウントさせないため）', () => {
    const { getByTestId } = render(
      <BlockEditorExtensionsProvider>
        <ContextConsumer />
      </BlockEditorExtensionsProvider>
    );

    expect(getByTestId('value').textContent).toBe('null');
  });

  it('tiptap + registerExtension/registerBlockDefinition の解決結果を子孫の Context 経由で渡す', async () => {
    const { getByTestId } = render(
      <BlockEditorExtensionsProvider>
        <ContextConsumer />
      </BlockEditorExtensionsProvider>
    );

    await waitFor(() => {
      expect(getByTestId('value').textContent).toBe(
        JSON.stringify({ extensions: [dummyExtension], blockDefinitions: { callout: dummyBlockDefinition } })
      );
    });
  });

  it('children を render-prop（関数）として渡すと、解決済みの value を引数で受け取れる', async () => {
    const { getByTestId } = render(
      <BlockEditorExtensionsProvider>
        {(value) => (
          <span data-testid="render-prop-value">{value === null ? 'null' : value.blockDefinitions.callout.label}</span>
        )}
      </BlockEditorExtensionsProvider>
    );

    expect(getByTestId('render-prop-value').textContent).toBe('null');

    await waitFor(() => {
      expect(getByTestId('render-prop-value').textContent).toBe('ダミー');
    });
  });

  it('拡張の解決（ファクトリ評価）が失敗しても console.error でログされ、例外が伝播しない', async () => {
    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
    getBlockExtensionsMock.mockImplementationOnce(() => {
      throw new Error('boom');
    });

    expect(() =>
      render(
        <BlockEditorExtensionsProvider>
          <ContextConsumer />
        </BlockEditorExtensionsProvider>
      )
    ).not.toThrow();

    await waitFor(() => {
      expect(errorSpy).toHaveBeenCalled();
    });

    errorSpy.mockRestore();
  });
});
