import { describe, it, expect, beforeEach, vi } from 'vitest';
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import * as React from 'react';
import { Editor } from '@tiptap/core';
import type { JSONContent } from '@tiptap/core';
import type { SuggestionProps } from '@tiptap/suggestion';

import { RichTextViewer } from '../RichTextViewer.js';
import { RichTextEditor } from '../RichTextEditor.js';
import { Toolbar } from '../Toolbar.js';
import { buildExtensions } from '../extensions.js';
import { defaultSlashItems, filterSlashItems, type SlashItem } from '../slashMenu.js';
import { filterUsers, type MentionUser } from '../mention.js';
import { SlashMenuList } from '../SlashMenuList.js';
import { MentionList } from '../MentionList.js';

beforeEach(() => cleanup());

/* ── 9.B.4 — slash filter (pure) ─────────────────────────────────────── */
describe('filterSlashItems', () => {
  it('returns all items for an empty query', () => {
    expect(filterSlashItems('')).toHaveLength(defaultSlashItems.length);
  });
  it('matches by title (case-insensitive)', () => {
    const r = filterSlashItems('HEAD');
    expect(r.length).toBeGreaterThan(0);
    expect(r.every(i => /head/i.test(i.title))).toBe(true);
  });
  it('matches by alias', () => {
    const r = filterSlashItems('ul');
    expect(r.some(i => i.title === 'Bullet list')).toBe(true);
  });
});

/* ── 9.B.5 — mention people search (pure) ────────────────────────────── */
describe('filterUsers', () => {
  const people: MentionUser[] = [
    { id: '1', label: 'Ada Lovelace' },
    { id: '2', label: 'Alan Turing' },
    { id: '3', label: 'Grace Hopper' },
  ];
  it('filters case-insensitively by label', () => {
    expect(filterUsers('ada', people).map(u => u.id)).toEqual(['1']);
  });
  it('returns all (capped) for an empty query', () => {
    expect(filterUsers('', people, 2)).toHaveLength(2);
  });
});

/* ── 9.B.2 — viewer renders HTML from JSON ───────────────────────────── */
describe('RichTextViewer', () => {
  const doc: JSONContent = {
    type: 'doc',
    content: [
      { type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'Title' }] },
      {
        type: 'paragraph',
        content: [{ type: 'text', marks: [{ type: 'bold' }], text: 'bold' }],
      },
      {
        type: 'bulletList',
        content: [
          {
            type: 'listItem',
            content: [{ type: 'paragraph', content: [{ type: 'text', text: 'one' }] }],
          },
        ],
      },
    ],
  };
  it('serialises headings, marks and lists', () => {
    const { container } = render(<RichTextViewer doc={doc} />);
    expect(container.querySelector('h1')?.textContent).toBe('Title');
    expect(container.querySelector('strong')?.textContent).toBe('bold');
    expect(container.querySelector('ul li')?.textContent).toBe('one');
  });
});

/* ── 9.B.3 — toolbar commands ────────────────────────────────────────── */
describe('Toolbar', () => {
  it('toggles bold on the bound editor', () => {
    const editor = new Editor({
      element: document.createElement('div'),
      extensions: buildExtensions(),
      content: '<p>hello</p>',
    });
    editor.commands.selectAll();
    render(<Toolbar editor={editor} />);
    fireEvent.click(screen.getByLabelText('Bold'));
    expect(editor.getHTML()).toContain('<strong>');
    editor.destroy();
  });

  it('renders nothing without an editor', () => {
    const { container } = render(<Toolbar editor={null} />);
    expect(container.firstChild).toBeNull();
  });
});

/* ── 9.B.2 — editor mounts (SSR-safe) ────────────────────────────────── */
describe('RichTextEditor', () => {
  it('mounts a toolbar + editable textbox', async () => {
    render(<RichTextEditor content="<p>hi</p>" />);
    expect(await screen.findByRole('textbox')).toBeTruthy();
    expect(screen.getByRole('toolbar', { name: 'Formatting' })).toBeTruthy();
  });
});

/* ── 9.B.4 / 9.B.5 — suggestion list components ──────────────────────── */
function slashProps(
  items: SlashItem[],
  command: (i: SlashItem) => void
): SuggestionProps<SlashItem> {
  return { items, command } as unknown as SuggestionProps<SlashItem>;
}

describe('SlashMenuList', () => {
  it('renders an option per item and invokes command on click', () => {
    const command = vi.fn();
    render(<SlashMenuList {...slashProps(defaultSlashItems, command)} />);
    const options = screen.getAllByRole('option');
    expect(options).toHaveLength(defaultSlashItems.length);
    fireEvent.click(screen.getByText('Heading 1'));
    expect(command).toHaveBeenCalledWith(defaultSlashItems[0]);
  });

  it('shows an empty state when there are no items', () => {
    render(<SlashMenuList {...slashProps([], vi.fn())} />);
    expect(screen.getByText('No matches')).toBeTruthy();
  });
});

describe('MentionList', () => {
  it('renders people and emits {id,label} on click', () => {
    const command = vi.fn();
    const people: MentionUser[] = [{ id: '7', label: 'Ada' }];
    render(
      <MentionList {...({ items: people, command } as unknown as SuggestionProps<MentionUser>)} />
    );
    fireEvent.click(screen.getByText('Ada'));
    expect(command).toHaveBeenCalledWith({ id: '7', label: 'Ada' });
  });
});
