import { describe, expect, it } from 'vitest';

import {
  createInputTableRowClickHandler,
  getInputTableCellInteractionAttrs,
  isInputTableCellEditable,
  isInputTableInteractiveTarget,
  toggleInputTableRowSelection,
} from '../helpers/rowSelection';

interface TestRow {
  id?: string;
}

const row1: TestRow = { id: 'row-1' };
const row2: TestRow = { id: 'row-2' };

describe('toggleInputTableRowSelection', () => {
  it('adds an unselected row without replacing other selections', () => {
    expect(toggleInputTableRowSelection(['row-1'], [row1], row2, (row) => row.id as string)).toEqual({
      selectedRowKeys: ['row-1', 'row-2'],
      selectedRows: [row1, row2],
    });
  });

  it('removes only the clicked row when it is already selected', () => {
    expect(toggleInputTableRowSelection(['row-1', 'row-2'], [row1, row2], row1, (row) => row.id as string)).toEqual({
      selectedRowKeys: ['row-2'],
      selectedRows: [row2],
    });
  });

  it('does not mutate the existing selection arrays', () => {
    const selectedRowKeys = ['row-1'];
    const selectedRows = [row1];

    toggleInputTableRowSelection(selectedRowKeys, selectedRows, row2, (row) => row.id as string);

    expect(selectedRowKeys).toEqual(['row-1']);
    expect(selectedRows).toEqual([row1]);
  });
});

describe('createInputTableRowClickHandler', () => {
  function createHarness(options: { enabled?: boolean; readonly?: boolean } = {}) {
    let selection = { selectedRowKeys: ['row-1'], selectedRows: [row1] };
    let updateCount = 0;
    const customRow = createInputTableRowClickHandler<TestRow, string>({
      getRowKey: (row) => row.id,
      getSelectedRowKeys: () => selection.selectedRowKeys,
      getSelectedRows: () => selection.selectedRows,
      isEnabled: () => options.enabled !== false,
      isRowReadonly: () => options.readonly === true,
      updateSelection: (nextSelection) => {
        selection = nextSelection;
        updateCount++;
      },
    });

    return {
      click: (row: TestRow, target: EventTarget | null) => customRow(row).onClick({ target }),
      getSelection: () => selection,
      getUpdateCount: () => updateCount,
    };
  }

  it('publishes a toggled selection for an ordinary row click', () => {
    const harness = createHarness();
    const target = document.createElement('td');

    harness.click(row2, target);

    expect(harness.getSelection()).toEqual({
      selectedRowKeys: ['row-1', 'row-2'],
      selectedRows: [row1, row2],
    });
    expect(harness.getUpdateCount()).toBe(1);
  });

  it('ignores clicks from interactive row content', () => {
    const harness = createHarness();
    const control = document.createElement('div');
    const target = document.createElement('input');
    control.dataset.inputTableInteractive = '';
    control.append(target);

    harness.click(row2, target);

    expect(harness.getSelection()).toEqual({ selectedRowKeys: ['row-1'], selectedRows: [row1] });
    expect(harness.getUpdateCount()).toBe(0);
  });

  it.each([
    ['batch selection is disabled', { enabled: false }],
    ['the clicked row is read-only', { readonly: true }],
  ])('ignores the click when %s', (_label, options) => {
    const harness = createHarness(options);

    harness.click(row2, document.createElement('td'));

    expect(harness.getUpdateCount()).toBe(0);
  });

  it('ignores rows without a key', () => {
    const harness = createHarness();

    harness.click({}, document.createElement('td'));

    expect(harness.getUpdateCount()).toBe(0);
  });
});

describe('isInputTableInteractiveTarget', () => {
  it.each(['a', 'button'])('recognizes row action %s elements', (tagName) => {
    expect(isInputTableInteractiveTarget(document.createElement(tagName))).toBe(true);
  });

  it('recognizes nested content inside a field marked editable', () => {
    const control = document.createElement('div');
    const target = document.createElement('span');
    control.dataset.inputTableInteractive = '';
    control.append(target);

    expect(isInputTableInteractiveTarget(target)).toBe(true);
  });

  it.each(['input', 'select', 'textarea'])('allows row selection from unmarked %s content', (tagName) => {
    expect(isInputTableInteractiveTarget(document.createElement(tagName))).toBe(false);
  });

  it('recognizes the complete Ant Design selection cell', () => {
    const selectionCell = document.createElement('td');
    const target = document.createElement('span');
    selectionCell.className = 'ant-table-selection-column';
    selectionCell.append(target);

    expect(isInputTableInteractiveTarget(target)).toBe(true);
  });

  it('allows clicks on ordinary table-cell content', () => {
    const cell = document.createElement('td');
    const target = document.createElement('span');
    cell.append(target);

    expect(isInputTableInteractiveTarget(target)).toBe(false);
    expect(isInputTableInteractiveTarget(null)).toBe(false);
  });
});

describe('isInputTableCellEditable', () => {
  it('marks an enabled generated field as editable', () => {
    expect(isInputTableCellEditable({})).toBe(true);
  });

  it.each([
    ['disabled', { disabled: true }],
    ['readonly', { readonly: true }],
    ['detailed', { detailed: true }],
  ])('treats a %s generated field as row-clickable content', (_label, controlProps) => {
    expect(isInputTableCellEditable(controlProps)).toBe(false);
  });
});

describe('getInputTableCellInteractionAttrs', () => {
  it('marks an enabled field as interactive', () => {
    expect(getInputTableCellInteractionAttrs({})).toEqual({ 'data-input-table-interactive': '' });
  });

  it('makes a disabled field pass pointer events through to the table row', () => {
    expect(getInputTableCellInteractionAttrs({ disabled: true })).toEqual({ 'data-input-table-click-through': '' });
  });

  it('keeps a disabled detail field interactive', () => {
    expect(getInputTableCellInteractionAttrs({ disabled: true, disabledButClickable: true })).toEqual({ 'data-input-table-interactive': '' });
  });

  it.each([
    ['readonly', { readonly: true }],
    ['detailed', { detailed: true }],
  ])('leaves a %s field unmarked so its click bubbles normally', (_label, controlProps) => {
    expect(getInputTableCellInteractionAttrs(controlProps)).toEqual({});
  });
});
