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

const baseCmd: CommandItem = {
  name: 'heading1',
  label: '見出し1',
  description: '見出し',
  iconName: 'format_h1',
  isTextMenu: true,
  isDisabled: () => false,
};

describe('createHeadingActions', () => {
  describe.each<HeadingLevel>([1, 2, 3, 4, 5, 6])('level %i', (level) => {
    it('action は setHeading({ level }) を chain で呼び、recordUsage に cmd を渡す', () => {
      const recordUsage = vi.fn();
      const mock = createMockEditor();
      const actions = createHeadingActions(level)(baseCmd, { recordUsage });

      actions.action(mock.editor);

      expect(recordUsage).toHaveBeenCalledWith(baseCmd);
      expect(mock.expectChainCalled('setHeading')).toBe(true);
      expect(mock.getChainCallArgs('setHeading')).toEqual([{ level }]);
    });

    it('action は setBlockAttrs に cmd.class を渡す', () => {
      const mock = createMockEditor();
      const cmd: CommandItem = { ...baseCmd, class: 'h-lg' };
      const actions = createHeadingActions(level)(cmd, { recordUsage: () => {} });

      actions.action(mock.editor);

      expect(mock.getChainCallArgs('setBlockAttrs')).toEqual([0, { class: 'h-lg' }]);
    });

    it('convert は removeList → removeBlockquote → setHeading の順に chain する', () => {
      const mock = createMockEditor();
      const actions = createHeadingActions(level)(baseCmd, { recordUsage: () => {} });

      actions.convert(mock.editor, 5);

      const methods = mock.chainCalls.map((c) => c.method);
      const setHeadingIdx = methods.indexOf('setHeading');
      expect(methods.indexOf('removeList')).toBeLessThan(setHeadingIdx);
      expect(methods.indexOf('removeBlockquote')).toBeLessThan(setHeadingIdx);
      expect(mock.getChainCallArgs('setHeading')).toEqual([{ level }]);
      expect(mock.getChainCallArgs('setNodeSelection')).toEqual([5]);
    });

    it('isDisabled: cmd.class なしで同じ heading が active → true', () => {
      const mock = createMockEditor();
      mock.isActive.mockImplementation((type: string, attrs?: { level?: number; class?: string | null }) => {
        return type === 'heading' && attrs?.level === level && (attrs?.class === null || attrs?.class === '');
      });
      const actions = createHeadingActions(level)(baseCmd, { recordUsage: () => {} });

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

    it('isDisabled: cmd.class ありで同 class の heading が active → true', () => {
      const mock = createMockEditor();
      mock.isActive.mockImplementation((type: string, attrs?: { level?: number; class?: string }) => {
        return type === 'heading' && attrs?.level === level && attrs?.class === 'h-lg';
      });
      const cmd: CommandItem = { ...baseCmd, class: 'h-lg' };
      const actions = createHeadingActions(level)(cmd, { recordUsage: () => {} });

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

    it('isDisabled: 同 heading だが異なる class が active → false', () => {
      const mock = createMockEditor();
      mock.isActive.mockImplementation((type: string, attrs?: { level?: number; class?: string }) => {
        if (type === 'heading' && attrs?.level === level && attrs?.class === 'h-lg') return false;
        if (type === 'heading' && attrs?.level === level) return true;
        return false;
      });
      const cmd: CommandItem = { ...baseCmd, class: 'h-lg' };
      const actions = createHeadingActions(level)(cmd, { recordUsage: () => {} });

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

    it('isDisabled: heading が active でなく can.setHeading が false → true', () => {
      const mock = createMockEditor();
      mock.isActive.mockReturnValue(false);
      mock.can.mockReturnValue({ setHeading: () => false });
      const actions = createHeadingActions(level)(baseCmd, { recordUsage: () => {} });

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

    it('isActive: cmd.class なしで class:null の heading が active → true', () => {
      const mock = createMockEditor();
      mock.isActive.mockImplementation((type: string, attrs?: { level?: number; class?: string | null }) => {
        return type === 'heading' && attrs?.level === level && attrs?.class === null;
      });
      const actions = createHeadingActions(level)(baseCmd, { recordUsage: () => {} });

      expect(actions.isActive(mock.editor)).toBe(true);
    });

    it('isActive: cmd.class ありで class 一致 → true', () => {
      const mock = createMockEditor();
      mock.isActive.mockImplementation((type: string, attrs?: { level?: number; class?: string }) => {
        return type === 'heading' && attrs?.level === level && attrs?.class === 'h-lg';
      });
      const cmd: CommandItem = { ...baseCmd, class: 'h-lg' };
      const actions = createHeadingActions(level)(cmd, { recordUsage: () => {} });

      expect(actions.isActive(mock.editor)).toBe(true);
    });
  });
});
