import { describe, it, expect } from 'vitest';
import { RichTextBlock } from '@shared/kernel/rich-text';
import { adjacentChip, readBlock, renderInto } from './rich-text-dom';

const labelFor = (key: string) => ({ naam: 'Naam', datum: 'Datum' })[key] ?? key;

function roundTrip(block: RichTextBlock): RichTextBlock {
  const root = document.createElement('div');
  renderInto(root, block, labelFor);
  return readBlock(root);
}

describe('rich-text DOM boundary', () => {
  it('round-trips text, marks, placeholders, line breaks and multiple paragraphs', () => {
    const block: RichTextBlock = {
      paragraphs: [
        {
          nodes: [
            { type: 'text', text: 'Beste ' },
            { type: 'placeholder', key: 'naam' },
            { type: 'text', text: ' vet', marks: ['bold'] },
            { type: 'lineBreak' },
            { type: 'text', text: 'nieuwe regel' },
          ],
        },
        { nodes: [{ type: 'text', text: 'Op ' }, { type: 'placeholder', key: 'datum' }] },
      ],
    };
    expect(roundTrip(block)).toEqual(block);
  });

  it('round-trips an empty paragraph (filler <br> is not a line break)', () => {
    const empty: RichTextBlock = { paragraphs: [{ nodes: [] }] };
    expect(roundTrip(empty)).toEqual(empty);
  });

  it('renders a placeholder as a non-editable chip carrying its key and label', () => {
    const root = document.createElement('div');
    renderInto(root, { paragraphs: [{ nodes: [{ type: 'placeholder', key: 'naam' }] }] }, labelFor);
    const chip = root.querySelector('.rte-chip') as HTMLElement;
    expect(chip.getAttribute('contenteditable')).toBe('false');
    expect(chip.dataset['phKey']).toBe('naam');
    expect(chip.textContent).toBe('Naam');
  });

  it('reads combined marks in canonical order regardless of nesting', () => {
    const root = document.createElement('div');
    root.innerHTML = '<p><em><strong>x</strong></em></p>'; // italic wrapping bold
    expect(readBlock(root)).toEqual({ paragraphs: [{ nodes: [{ type: 'text', text: 'x', marks: ['bold', 'italic'] }] }] });
  });

  it('round-trips bullet and numbered lists mixed with paragraphs', () => {
    const block: RichTextBlock = {
      paragraphs: [
        { nodes: [{ type: 'text', text: 'Intro' }] },
        { nodes: [{ type: 'text', text: 'een' }], list: 'bullet' },
        { nodes: [{ type: 'text', text: 'twee' }], list: 'bullet' },
        { nodes: [{ type: 'text', text: 'eerst' }], list: 'number' },
        { nodes: [{ type: 'text', text: 'dan' }], list: 'number' },
        { nodes: [{ type: 'text', text: 'Slot' }] },
      ],
    };
    expect(roundTrip(block)).toEqual(block);
  });

  it('groups consecutive same-kind list lines into one <ul>/<ol>', () => {
    const root = document.createElement('div');
    renderInto(
      root,
      {
        paragraphs: [
          { nodes: [{ type: 'text', text: 'a' }], list: 'bullet' },
          { nodes: [{ type: 'text', text: 'b' }], list: 'bullet' },
          { nodes: [{ type: 'text', text: 'c' }], list: 'number' },
        ],
      },
      labelFor,
    );
    expect(root.querySelectorAll('ul').length).toBe(1);
    expect(root.querySelectorAll('ul > li').length).toBe(2);
    expect(root.querySelectorAll('ol > li').length).toBe(1);
  });

  it('adjacentChip finds a chip next to a collapsed caret so Backspace/Delete can remove it', () => {
    // <p>aa{chip}bb</p>
    const p = document.createElement('p');
    const before = document.createTextNode('aa');
    const chip = document.createElement('span');
    chip.dataset['phKey'] = 'naam';
    const after = document.createTextNode('bb');
    p.append(before, chip, after);

    // Backspace with caret just after the chip (start of "bb") → the chip.
    expect(adjacentChip(after, 0, -1)).toBe(chip);
    // Delete with caret just before the chip (end of "aa") → the chip.
    expect(adjacentChip(before, before.length, 1)).toBe(chip);
    // Element-level caret between chip and "bb" (offset 2), Backspace → the chip.
    expect(adjacentChip(p, 2, -1)).toBe(chip);
    // Caret mid-text → nothing to remove.
    expect(adjacentChip(after, 1, -1)).toBeNull();
    // At the text edge but the sibling is not a chip → null.
    expect(adjacentChip(before, 0, -1)).toBeNull();
  });

  it('marks auto-resolvable vs manual chips with data-auto for styling', () => {
    const root = document.createElement('div');
    renderInto(
      root,
      { paragraphs: [{ nodes: [{ type: 'placeholder', key: 'naam' }, { type: 'placeholder', key: 'reden' }] }] },
      labelFor,
      (key) => key === 'naam',
    );
    const chips = root.querySelectorAll('.rte-chip');
    expect((chips[0] as HTMLElement).dataset['auto']).toBe('true');
    expect((chips[1] as HTMLElement).dataset['auto']).toBe('false');
  });
});
