import { describe, it, expect } from 'vitest';
import createBlockMenu, { type RawBlockMenu } from './createBlockMenu';
import type { BlockTypeMaster } from '../types';

const masters: Record<string, BlockTypeMaster> = {
  paragraph: {
    label: '段落',
    icon: 'title',
    description: '段落',
    isTextMenu: true,
    aliases: ['p', 'paragraph'],
  },
  heading2: {
    label: '見出し2',
    icon: 'format_h2',
    description: '見出し2',
    isTextMenu: true,
    aliases: ['h2'],
  },
  image: {
    label: '画像',
    icon: 'photo',
    description: 'メディア画像',
    isTextMenu: false,
    aliases: ['img'],
  },
};

describe('createBlockMenu', () => {
  it('空配列を渡したら空配列を返す', () => {
    expect(createBlockMenu([], masters)).toEqual([]);
  });

  it('マスターにある type を CommandItem に展開する', () => {
    const raw: RawBlockMenu = [{ type: 'paragraph', label: '', class: '', group: 'テキスト' }];

    const result = createBlockMenu(raw, masters);

    expect(result).toHaveLength(1);
    expect(result[0].title).toBe('テキスト');
    expect(result[0].commands).toHaveLength(1);

    const command = result[0].commands[0];
    expect(command.name).toBe('paragraph');
    expect(command.label).toBe('段落'); // master.label
    expect(command.iconName).toBe('title'); // master.icon
    expect(command.description).toBe('段落');
    expect(command.aliases).toEqual(['p', 'paragraph']);
    expect(command.isTextMenu).toBe(true);
    expect(command.class).toBeUndefined();
  });

  it('admin の label が非空なら master.label より優先する', () => {
    const raw: RawBlockMenu = [{ type: 'paragraph', label: '本文', class: '', group: 'テキスト' }];

    const result = createBlockMenu(raw, masters);
    expect(result[0].commands[0].label).toBe('本文');
  });

  it('admin の class が非空ならそのまま付与、空なら undefined', () => {
    const raw: RawBlockMenu = [
      { type: 'paragraph', label: '', class: 'lead', group: 'テキスト' },
      { type: 'heading2', label: '', class: '', group: 'テキスト' },
    ];

    const result = createBlockMenu(raw, masters);
    expect(result[0].commands[0].class).toBe('lead');
    expect(result[0].commands[1].class).toBeUndefined();
  });

  it('group を指定すれば対応するグループに振り分けられる', () => {
    const raw: RawBlockMenu = [
      { type: 'paragraph', label: '', class: '', group: 'テキスト' },
      { type: 'image', label: '', class: '', group: 'メディア' },
    ];

    const result = createBlockMenu(raw, masters);
    expect(result.map((g) => g.title)).toEqual(['テキスト', 'メディア']);
    expect(result[0].commands.map((c) => c.name)).toEqual(['paragraph']);
    expect(result[1].commands.map((c) => c.name)).toEqual(['image']);
  });

  it('空 group は直前の entry の group を引き継ぐ', () => {
    const raw: RawBlockMenu = [
      { type: 'paragraph', label: '', class: '', group: 'テキスト' },
      { type: 'heading2', label: '', class: '', group: '' },
      { type: 'image', label: '', class: '', group: 'メディア' },
    ];

    const result = createBlockMenu(raw, masters);
    expect(result.map((g) => g.title)).toEqual(['テキスト', 'メディア']);
    expect(result[0].commands.map((c) => c.name)).toEqual(['paragraph', 'heading2']);
    expect(result[1].commands.map((c) => c.name)).toEqual(['image']);
  });

  it('最初の entry が空 group ならフォールバックの「グループ」に入る', () => {
    const raw: RawBlockMenu = [
      { type: 'paragraph', label: '', class: '', group: '' },
      { type: 'heading2', label: '', class: '', group: 'テキスト' },
    ];

    const result = createBlockMenu(raw, masters);
    expect(result.map((g) => g.title)).toEqual(['グループ', 'テキスト']);
    expect(result[0].commands.map((c) => c.name)).toEqual(['paragraph']);
    expect(result[1].commands.map((c) => c.name)).toEqual(['heading2']);
  });

  it('マスターに無い type は黙って除外される', () => {
    const raw: RawBlockMenu = [
      { type: 'paragraph', label: '', class: '', group: 'テキスト' },
      { type: 'unknownBlock', label: '謎', class: '', group: 'テキスト' },
    ];

    const result = createBlockMenu(raw, masters);
    expect(result[0].commands.map((c) => c.name)).toEqual(['paragraph']);
  });

  it('複数 type を同じ group に束ねられる', () => {
    const raw: RawBlockMenu = [
      { type: 'paragraph', label: '', class: '', group: 'テキスト' },
      { type: 'heading2', label: '', class: '', group: 'テキスト' },
    ];

    const result = createBlockMenu(raw, masters);
    expect(result).toHaveLength(1);
    expect(result[0].title).toBe('テキスト');
    expect(result[0].commands.map((c) => c.name)).toEqual(['paragraph', 'heading2']);
  });

  it('グループの出力順は raw の配列順を保つ', () => {
    const raw: RawBlockMenu = [
      { type: 'image', label: '', class: '', group: 'メディア' },
      { type: 'paragraph', label: '', class: '', group: 'テキスト' },
      { type: 'heading2', label: '', class: '', group: 'テキスト' },
    ];

    const result = createBlockMenu(raw, masters);
    expect(result.map((g) => g.title)).toEqual(['メディア', 'テキスト']);
  });

  it('連番のグループ name (group-1, group-2, ...) を付与する', () => {
    const raw: RawBlockMenu = [
      { type: 'paragraph', label: '', class: '', group: 'A' },
      { type: 'heading2', label: '', class: '', group: 'B' },
    ];

    const result = createBlockMenu(raw, masters);
    expect(result[0].name).toBe('group-1');
    expect(result[1].name).toBe('group-2');
  });

  describe('identifier の生成', () => {
    it('entry.class が空文字なら identifier = type', () => {
      const raw: RawBlockMenu = [{ type: 'paragraph', label: '', class: '', group: 'テキスト' }];

      const result = createBlockMenu(raw, masters);

      const command = result[0].commands[0];
      expect(command.name).toBe('paragraph');
      expect(command.identifier).toBe('paragraph');
      expect(command.class).toBeUndefined();
    });

    it('entry.class が指定されたら identifier = type.class', () => {
      const raw: RawBlockMenu = [{ type: 'paragraph', label: 'リード', class: 'lead', group: 'テキスト' }];

      const result = createBlockMenu(raw, masters);

      const command = result[0].commands[0];
      expect(command.name).toBe('paragraph');
      expect(command.identifier).toBe('paragraph.lead');
      expect(command.class).toBe('lead');
      expect(command.label).toBe('リード');
    });
  });

  describe('同 type の class 違いを複数並べる', () => {
    it('class が異なる同 type は別エントリとして並列に出力される', () => {
      const raw: RawBlockMenu = [
        { type: 'paragraph', label: '', class: '', group: 'テキスト' },
        { type: 'paragraph', label: 'リード', class: 'lead', group: 'テキスト' },
        { type: 'paragraph', label: '注釈', class: 'note', group: 'テキスト' },
      ];

      const result = createBlockMenu(raw, masters);

      expect(result[0].commands).toHaveLength(3);
      expect(result[0].commands.map((c) => c.identifier)).toEqual(['paragraph', 'paragraph.lead', 'paragraph.note']);
      expect(result[0].commands.map((c) => c.label)).toEqual(['段落', 'リード', '注釈']);
    });
  });

  describe('identifier の重複は後勝ち', () => {
    it('同 identifier を 2 回書いたら最後の entry の内容で上書きされる', () => {
      const raw: RawBlockMenu = [
        { type: 'paragraph', label: '段落', class: '', group: 'A' },
        { type: 'paragraph', label: '本文', class: '', group: 'B' },
      ];

      const result = createBlockMenu(raw, masters);

      // 後勝ちで 1 件、group も後勝ち
      expect(result).toHaveLength(1);
      expect(result[0].title).toBe('B');
      expect(result[0].commands).toHaveLength(1);
      expect(result[0].commands[0].label).toBe('本文');
    });

    it('class 違いがあれば identifier が違うので両方残る', () => {
      const raw: RawBlockMenu = [
        { type: 'paragraph', label: '段落', class: '', group: 'テキスト' },
        { type: 'paragraph', label: 'リード', class: 'lead', group: 'テキスト' },
      ];

      const result = createBlockMenu(raw, masters);

      expect(result[0].commands).toHaveLength(2);
      expect(result[0].commands.map((c) => c.identifier)).toEqual(['paragraph', 'paragraph.lead']);
    });
  });
});
