import { expect } from '@playwright/test';

import { dragBetweenCoords } from '../utils/actions/drag.js';
import { shiftClick } from '../utils/actions/edgeless.js';
import {
  pressArrowDown,
  pressArrowDownWithShiftKey,
  pressArrowLeft,
  pressArrowRight,
  pressArrowUp,
  pressArrowUpWithShiftKey,
  pressBackspace,
  pressEnter,
  pressEscape,
  type,
} from '../utils/actions/keyboard.js';
import {
  enterPlaygroundRoom,
  getBoundingBox,
  initDatabaseDynamicRowWithData,
  initDatabaseRowWithData,
  initEmptyDatabaseState,
  initKanbanViewState,
  waitNextFrame,
} from '../utils/actions/misc.js';
import { test } from '../utils/playwright.js';
import {
  assertCellsSelection,
  assertDatabaseTitleColumnText,
  assertKanbanCardHeaderText,
  assertKanbanCardSelected,
  assertKanbanCellSelected,
  assertRowsSelection,
  clickKanbanCardHeader,
  focusKanbanCardHeader,
  getDatabaseBodyCell,
  getKanbanCard,
  initDatabaseColumn,
  switchColumnType,
} from './actions.js';

test.describe('focus', () => {
  test('should support move focus by arrow key', async ({ page }) => {
    await enterPlaygroundRoom(page);
    await initEmptyDatabaseState(page);

    await initDatabaseColumn(page);
    await initDatabaseDynamicRowWithData(page, '123', true);
    await pressEscape(page);
    await waitNextFrame(page, 100);
    await pressEscape(page);
    await assertRowsSelection(page, [0, 0]);
  });

  test('should support multi row selection', async ({ page }) => {
    await enterPlaygroundRoom(page);
    await initEmptyDatabaseState(page);

    await initDatabaseColumn(page);
    await initDatabaseDynamicRowWithData(page, '', true);
    await pressEscape(page);
    await switchColumnType(page, 'Number');
    await initDatabaseDynamicRowWithData(page, '123', true);

    const selectColumn = getDatabaseBodyCell(page, {
      rowIndex: 1,
      columnIndex: 1,
    });

    const endBox = await getBoundingBox(selectColumn);
    const endX = endBox.x + endBox.width / 2;

    await dragBetweenCoords(
      page,
      { x: endX, y: endBox.y },
      { x: endX, y: endBox.y + endBox.height }
    );
    await pressEscape(page);
    await assertRowsSelection(page, [0, 1]);
  });

  test('should support row selection with dynamic height', async ({ page }) => {
    await enterPlaygroundRoom(page);
    await initEmptyDatabaseState(page);

    await initDatabaseColumn(page);
    await initDatabaseDynamicRowWithData(page, '123123', true);
    await type(page, '456456');
    await pressEnter(page);
    await type(page, 'abcabc');
    await pressEnter(page);
    await type(page, 'defdef');
    await pressEnter(page);
    await pressEscape(page);

    await pressEscape(page);
    await assertRowsSelection(page, [0, 0]);
  });
});

test.describe('row-level selection', () => {
  test('should support title selection', async ({ page }) => {
    await enterPlaygroundRoom(page);
    await initEmptyDatabaseState(page);

    await initDatabaseColumn(page);
    await initDatabaseRowWithData(page, 'title');
    await pressEscape(page);
    await waitNextFrame(page, 100);
    await assertCellsSelection(page, {
      start: [0, 0],
    });

    await pressEscape(page);
    await assertRowsSelection(page, [0, 0]);
  });

  test('should support pressing esc to trigger row selection', async ({
    page,
  }) => {
    await enterPlaygroundRoom(page);
    await initEmptyDatabaseState(page);

    await initDatabaseColumn(page);
    await initDatabaseDynamicRowWithData(page, '123', true);
    await pressEscape(page);
    await waitNextFrame(page, 100);
    await pressEscape(page);
    await assertRowsSelection(page, [0, 0]);
  });

  test('should support multi row selection', async ({ page }) => {
    await enterPlaygroundRoom(page);
    await initEmptyDatabaseState(page);

    await initDatabaseColumn(page);
    await initDatabaseDynamicRowWithData(page, '', true);
    await pressEscape(page);
    await switchColumnType(page, 'Number');
    await initDatabaseDynamicRowWithData(page, '123', true);

    const selectColumn = getDatabaseBodyCell(page, {
      rowIndex: 1,
      columnIndex: 1,
    });

    const endBox = await getBoundingBox(selectColumn);
    const endX = endBox.x + endBox.width / 2;

    await dragBetweenCoords(
      page,
      { x: endX, y: endBox.y },
      { x: endX, y: endBox.y + endBox.height }
    );
    await pressEscape(page);
    await assertRowsSelection(page, [0, 1]);
  });

  test('should support row selection with dynamic height', async ({ page }) => {
    await enterPlaygroundRoom(page);
    await initEmptyDatabaseState(page);

    await initDatabaseColumn(page);
    await initDatabaseDynamicRowWithData(page, '123123', true);
    await type(page, '456456');
    await pressEnter(page);
    await type(page, 'abcabc');
    await pressEnter(page);
    await type(page, 'defdef');
    await pressEnter(page);
    await pressEscape(page);

    await pressEscape(page);
    await assertRowsSelection(page, [0, 0]);
  });

  test('move row selection with (up | down)', async ({ page }) => {
    await enterPlaygroundRoom(page);
    await initEmptyDatabaseState(page);

    await initDatabaseColumn(page);

    // add two rows
    await initDatabaseDynamicRowWithData(page, '123123', true);
    await pressEscape(page);

    await initDatabaseDynamicRowWithData(page, '123123', true);
    await pressEscape(page);

    await pressEscape(page); // switch to row selection

    await assertRowsSelection(page, [1, 1]);

    await pressArrowUp(page);
    await assertRowsSelection(page, [0, 0]);

    // should not allow under selection
    await pressArrowUp(page);
    await assertRowsSelection(page, [0, 0]);

    await pressArrowDown(page);
    await assertRowsSelection(page, [1, 1]);

    // should not allow over selection
    await pressArrowDown(page);
    await assertRowsSelection(page, [1, 1]);
  });

  test('increment decrement row selection with shift+(up | down)', async ({
    page,
  }) => {
    await enterPlaygroundRoom(page);
    await initEmptyDatabaseState(page);

    await initDatabaseColumn(page);

    // add two rows
    await initDatabaseDynamicRowWithData(page, '123123', true);
    await pressEscape(page);

    await initDatabaseDynamicRowWithData(page, '123123', true);
    await pressEscape(page);

    await pressEscape(page); // switch to row selection

    await pressArrowUpWithShiftKey(page);
    await assertRowsSelection(page, [0, 1]);

    await pressArrowDownWithShiftKey(page);
    await assertRowsSelection(page, [1, 1]); // should decrement back

    await pressArrowUp(page); // go to first row

    await pressArrowDownWithShiftKey(page);
    await assertRowsSelection(page, [0, 1]);

    await pressArrowUpWithShiftKey(page);
    await assertRowsSelection(page, [0, 0]);
  });
});

test.describe('cell-level selection', () => {
  test('should support multi cell selection', async ({ page }) => {
    await enterPlaygroundRoom(page);
    await initEmptyDatabaseState(page);

    await initDatabaseColumn(page);
    await initDatabaseDynamicRowWithData(page, '', true);
    await pressEscape(page);
    await switchColumnType(page, 'Number');
    await initDatabaseDynamicRowWithData(page, '123', true);

    const startCell = getDatabaseBodyCell(page, {
      rowIndex: 0,
      columnIndex: 0,
    });
    const endCell = getDatabaseBodyCell(page, {
      rowIndex: 1,
      columnIndex: 1,
    });

    const startBox = await getBoundingBox(startCell);
    const endBox = await getBoundingBox(endCell);

    const startX = startBox.x + startBox.width / 2;
    const startY = startBox.y + startBox.height / 2;
    const endX = endBox.x + endBox.width / 2;
    const endY = endBox.y + endBox.height / 2;

    await dragBetweenCoords(
      page,
      { x: startX, y: startY },
      { x: endX, y: endY }
    );

    await assertCellsSelection(page, {
      start: [0, 0],
      end: [1, 1],
    });
  });

  test("should support backspace key to delete cell's content", async ({
    page,
  }) => {
    await enterPlaygroundRoom(page);
    await initEmptyDatabaseState(page);

    await initDatabaseColumn(page);
    await initDatabaseRowWithData(page, 'row1');
    await initDatabaseDynamicRowWithData(page, 'abc', false);
    await pressEscape(page);
    await initDatabaseRowWithData(page, 'row2');
    await initDatabaseDynamicRowWithData(page, '123', false);
    await pressEscape(page);

    const startCell = getDatabaseBodyCell(page, {
      rowIndex: 0,
      columnIndex: 0,
    });
    const endCell = getDatabaseBodyCell(page, {
      rowIndex: 1,
      columnIndex: 1,
    });

    const startBox = await getBoundingBox(startCell);
    const endBox = await getBoundingBox(endCell);

    const startX = startBox.x + startBox.width / 2;
    const startY = startBox.y + startBox.height / 2;
    const endX = endBox.x + endBox.width / 2;
    const endY = endBox.y + endBox.height / 2;

    await dragBetweenCoords(
      page,
      { x: startX, y: startY },
      { x: endX, y: endY }
    );

    await pressBackspace(page);
    await assertDatabaseTitleColumnText(page, '', 0);
    await assertDatabaseTitleColumnText(page, '', 1);
    const selectCell1 = getDatabaseBodyCell(page, {
      rowIndex: 0,
      columnIndex: 1,
    });
    expect(await selectCell1.innerText()).toBe('');
    const selectCell2 = getDatabaseBodyCell(page, {
      rowIndex: 1,
      columnIndex: 1,
    });
    expect(await selectCell2.innerText()).toBe('');
  });
});

test.describe('kanban view selection', () => {
  test("should support move cell's focus by arrow key(up&down) within a card", async ({
    page,
  }) => {
    await enterPlaygroundRoom(page);
    await initKanbanViewState(page, {
      rows: ['row1'],
      columns: [
        {
          type: 'number',
          value: [1],
        },
        {
          type: 'rich-text',
          value: ['text'],
        },
      ],
    });

    await focusKanbanCardHeader(page);
    await assertKanbanCellSelected(page, {
      // group by `number` column, the first(groupIndex: 0) group is `Ungroups`
      groupIndex: 1,
      cardIndex: 0,
      cellIndex: 0,
    });

    await pressArrowDown(page, 3);
    await assertKanbanCellSelected(page, {
      groupIndex: 1,
      cardIndex: 0,
      cellIndex: 0,
    });

    await pressArrowUp(page);
    await assertKanbanCellSelected(page, {
      groupIndex: 1,
      cardIndex: 0,
      cellIndex: 2,
    });
  });

  test("should support move cell's focus by arrow key(up&down) within multi cards", async ({
    page,
  }) => {
    await enterPlaygroundRoom(page);
    await initKanbanViewState(page, {
      rows: ['row1', 'row2'],
      columns: [
        {
          type: 'number',
          value: [1, 2],
        },
        {
          type: 'rich-text',
          value: ['text'],
        },
      ],
    });

    await focusKanbanCardHeader(page);
    await pressArrowUp(page);
    await assertKanbanCellSelected(page, {
      groupIndex: 1,
      cardIndex: 1,
      cellIndex: 2,
    });

    await pressArrowDown(page);
    await assertKanbanCellSelected(page, {
      groupIndex: 1,
      cardIndex: 0,
      cellIndex: 0,
    });
  });

  test("should support move cell's focus by arrow key(left&right)", async ({
    page,
  }) => {
    await enterPlaygroundRoom(page);
    await initKanbanViewState(page, {
      rows: ['row1', 'row2', 'row3'],
      columns: [
        {
          type: 'number',
          value: [undefined, 1, 10],
        },
      ],
    });

    await focusKanbanCardHeader(page);

    await pressArrowRight(page, 3);
    await assertKanbanCellSelected(page, {
      groupIndex: 0,
      cardIndex: 0,
      cellIndex: 0,
    });

    await pressArrowLeft(page);
    await assertKanbanCellSelected(page, {
      groupIndex: 2,
      cardIndex: 0,
      cellIndex: 0,
    });
  });

  test("should support move card's focus by arrow key(up&down)", async ({
    page,
  }) => {
    await enterPlaygroundRoom(page);
    await initKanbanViewState(page, {
      rows: ['row1', 'row2', 'row3'],
      columns: [
        {
          type: 'number',
          value: [undefined, undefined, undefined],
        },
      ],
    });

    await focusKanbanCardHeader(page);
    await pressEscape(page);
    await pressEscape(page);
    await assertKanbanCardSelected(page, {
      groupIndex: 0,
      cardIndex: 0,
    });

    await pressArrowDown(page, 3);
    await assertKanbanCardSelected(page, {
      groupIndex: 0,
      cardIndex: 0,
    });

    await pressArrowUp(page);
    await assertKanbanCardSelected(page, {
      groupIndex: 0,
      cardIndex: 2,
    });
  });

  test("should support move card's focus by arrow key(left&right)", async ({
    page,
  }) => {
    await enterPlaygroundRoom(page);
    await initKanbanViewState(page, {
      rows: ['row1', 'row2', 'row3'],
      columns: [
        {
          type: 'number',
          value: [undefined, 1, 10],
        },
      ],
    });

    await focusKanbanCardHeader(page);
    await pressEscape(page);
    await pressEscape(page);

    await pressArrowRight(page, 3);
    await assertKanbanCardSelected(page, {
      groupIndex: 0,
      cardIndex: 0,
    });

    await pressArrowLeft(page);
    await assertKanbanCardSelected(page, {
      groupIndex: 2,
      cardIndex: 0,
    });
  });

  test('should support multi card selection', async ({ page }) => {
    await enterPlaygroundRoom(page);
    await initKanbanViewState(page, {
      rows: ['row1', 'row2'],
      columns: [
        {
          type: 'number',
          value: [undefined, 1],
        },
      ],
    });

    await focusKanbanCardHeader(page);
    await pressEscape(page);
    await pressEscape(page);

    const card = getKanbanCard(page, {
      groupIndex: 1,
      cardIndex: 0,
    });
    const box = await getBoundingBox(card);
    await shiftClick(page, {
      x: box.x + box.width / 2,
      y: box.y + box.height / 2,
    });

    await assertKanbanCardSelected(page, {
      groupIndex: 0,
      cardIndex: 0,
    });
    await assertKanbanCardSelected(page, {
      groupIndex: 1,
      cardIndex: 0,
    });
  });

  test("should support move cursor in card's title by arrow key(left&right)", async ({
    page,
  }) => {
    await enterPlaygroundRoom(page);
    await initKanbanViewState(page, {
      rows: ['row1'],
      columns: [
        {
          type: 'rich-text',
          value: ['text'],
        },
      ],
    });

    await clickKanbanCardHeader(page);
    await type(page, 'abc');
    await pressArrowLeft(page, 2);
    await pressArrowRight(page);
    await pressBackspace(page);
    await pressEscape(page);

    await assertKanbanCardHeaderText(page, 'row1ac');
  });
});
