import { describe, expect, it, vi } from 'vitest';
import { Extension } from '@ablogcms/block-editor/tiptap/core';
import type { Tiptap } from '@ablogcms/block-editor/tiptap';
import type { BlockDefinition } from '@ablogcms/block-editor/types';
import { getBlockDefinitionRegistry, getBlockExtensionRegistry } from '@ablogcms/plugins/core';
import { getBlockDefinitions, getBlockExtensions } from './block-definitions';

const dummyTiptap = {} as Tiptap;

const dummyBlockDefinition = (label: string): BlockDefinition => ({
  label,
  description: label,
  icon: 'title',
  isTextMenu: false,
  aliases: [],
  createActions: () => ({ action: () => {}, convert: () => {}, isDisabled: () => false, isActive: () => false }),
});

// 拡張基盤フェーズ3-C: registerBlockDefinition / registerExtension のレジストリ統合配線。
// unit-editor 側の unit-definitions.ts と同じ形式で、レジストリに登録された
// ファクトリを tiptap で評価する。ファクトリの評価は呼び出し側（ブロックエディターのチャンク内）
// のタイミングで行われる想定のため、ここでは tiptap を直接受け取るだけの関数として実装する。
describe('getBlockExtensions', () => {
  it('レジストリが空なら空配列を返す（回帰なし）', () => {
    expect(getBlockExtensions(dummyTiptap)).toEqual([]);
  });

  it('登録済みファクトリを tiptap で評価し、結果を配列にまとめる', () => {
    const extension = Extension.create({ name: 'callout' });
    const unsubscribe = getBlockExtensionRegistry().add('callout', () => extension);

    expect(getBlockExtensions(dummyTiptap)).toEqual([extension]);

    unsubscribe();
  });

  it('ファクトリが配列を返す場合はフラットにまとめる', () => {
    const extensionA = Extension.create({ name: 'calloutA' });
    const extensionB = Extension.create({ name: 'calloutB' });
    const unsubscribe = getBlockExtensionRegistry().add('callout', () => [extensionA, extensionB]);

    expect(getBlockExtensions(dummyTiptap)).toEqual([extensionA, extensionB]);

    unsubscribe();
  });

  it('ファクトリが throw した場合はそのエントリだけスキップし console.error でログする（他プラグインは継続）', () => {
    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
    const okExtension = Extension.create({ name: 'ok' });
    const unsubscribeBroken = getBlockExtensionRegistry().add('broken', () => {
      throw new Error('boom');
    });
    const unsubscribeOk = getBlockExtensionRegistry().add('ok', () => okExtension);

    expect(getBlockExtensions(dummyTiptap)).toEqual([okExtension]);
    expect(errorSpy).toHaveBeenCalledTimes(1);
    expect(errorSpy.mock.calls[0][0]).toContain('broken');

    unsubscribeBroken();
    unsubscribeOk();
    errorSpy.mockRestore();
  });
});

describe('getBlockDefinitions', () => {
  it('レジストリが空なら空オブジェクトを返す（回帰なし）', () => {
    expect(getBlockDefinitions(dummyTiptap)).toEqual({});
  });

  it('登録済みファクトリを tiptap で評価し、type をキーにした Record にまとめる', () => {
    const def = dummyBlockDefinition('コールアウト');
    const unsubscribe = getBlockDefinitionRegistry().add('callout', () => def);

    expect(getBlockDefinitions(dummyTiptap)).toEqual({ callout: def });

    unsubscribe();
  });

  it('ファクトリが throw した場合はそのエントリだけスキップし console.error でログする（他プラグインは継続）', () => {
    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
    const okDef = dummyBlockDefinition('OK');
    const unsubscribeBroken = getBlockDefinitionRegistry().add('broken', () => {
      throw new Error('boom');
    });
    const unsubscribeOk = getBlockDefinitionRegistry().add('ok', () => okDef);

    expect(getBlockDefinitions(dummyTiptap)).toEqual({ ok: okDef });
    expect(errorSpy).toHaveBeenCalledTimes(1);
    expect(errorSpy.mock.calls[0][0]).toContain('broken');

    unsubscribeBroken();
    unsubscribeOk();
    errorSpy.mockRestore();
  });
});
