import { describe, it, expect, vi } from 'vitest';
import paragraph from './paragraph';
import createMockEditor from './__tests__/createMockEditor';
import type { CommandItem } from '../../types';

const baseCmd: CommandItem = {
  name: 'paragraph',
  label: '段落',
  description: '段落',
  iconName: 'title',
  isTextMenu: true,
  isDisabled: () => false,
};

describe('paragraph block', () => {
  it('action: setParagraph と recordUsage と setBlockAttrs(class) を呼ぶ', () => {
    const recordUsage = vi.fn();
    const mock = createMockEditor();
    const cmd: CommandItem = { ...baseCmd, class: 'lead' };

    paragraph.createActions(cmd, { recordUsage }).action(mock.editor);

    expect(recordUsage).toHaveBeenCalledWith(cmd);
    expect(mock.expectChainCalled('setParagraph')).toBe(true);
    expect(mock.getChainCallArgs('setBlockAttrs')).toEqual([0, { class: 'lead' }]);
  });

  it('convert: setNodeSelection → removeList → removeBlockquote → setParagraph', () => {
    const mock = createMockEditor();

    paragraph.createActions(baseCmd, { recordUsage: () => {} }).convert(mock.editor, 7);

    const methods = mock.chainCalls.map((c) => c.method);
    expect(methods).toContain('setNodeSelection');
    expect(methods).toContain('removeList');
    expect(methods).toContain('removeBlockquote');
    expect(methods).toContain('setParagraph');
    expect(mock.getChainCallArgs('setNodeSelection')).toEqual([7]);
  });

  it('isDisabled: 同じ paragraph + 同 class が active → true', () => {
    const mock = createMockEditor();
    mock.isActive.mockImplementation(
      (type: string, attrs?: { class?: string }) => type === 'paragraph' && attrs?.class === 'lead'
    );
    const actions = paragraph.createActions({ ...baseCmd, class: 'lead' }, { recordUsage: () => {} });

    expect(actions.isDisabled(mock.editor, 0)).toBe(true);
  });

  it('isDisabled: class なしで paragraph(class:null) が active → true', () => {
    const mock = createMockEditor();
    mock.isActive.mockImplementation(
      (type: string, attrs?: { class?: string | null }) => type === 'paragraph' && attrs?.class === null
    );

    expect(paragraph.createActions(baseCmd, { recordUsage: () => {} }).isDisabled(mock.editor, 0)).toBe(true);
  });

  it('isDisabled: 別 class の paragraph が active → false', () => {
    const mock = createMockEditor();
    mock.isActive.mockImplementation((type: string, attrs?: { class?: string }) => {
      if (type === 'paragraph' && attrs?.class === 'lead') return false;
      if (type === 'paragraph') return true;
      return false;
    });
    const actions = paragraph.createActions({ ...baseCmd, class: 'lead' }, { recordUsage: () => {} });

    expect(actions.isDisabled(mock.editor, 0)).toBe(false);
  });

  it('isDisabled: paragraph でなく can.setParagraph が false → true', () => {
    const mock = createMockEditor();
    mock.isActive.mockReturnValue(false);
    mock.can.mockReturnValue({ setParagraph: () => false });

    expect(paragraph.createActions(baseCmd, { recordUsage: () => {} }).isDisabled(mock.editor, 0)).toBe(true);
  });

  it('isActive: class 一致なら true', () => {
    const mock = createMockEditor();
    mock.isActive.mockImplementation(
      (type: string, attrs?: { class?: string }) => type === 'paragraph' && attrs?.class === 'lead'
    );

    expect(
      paragraph.createActions({ ...baseCmd, class: 'lead' }, { recordUsage: () => {} }).isActive(mock.editor)
    ).toBe(true);
  });

  it('isActive: class なしで paragraph(class:null) が active → true', () => {
    const mock = createMockEditor();
    mock.isActive.mockImplementation(
      (type: string, attrs?: { class?: string | null }) => type === 'paragraph' && attrs?.class === null
    );

    expect(paragraph.createActions(baseCmd, { recordUsage: () => {} }).isActive(mock.editor)).toBe(true);
  });
});
