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

import { initDatabaseColumn } from '../database/actions.js';
import {
  activeNoteInEdgeless,
  changeEdgelessNoteBackground,
  copyByKeyboard,
  createShapeElement,
  cutByKeyboard,
  dragBetweenCoords,
  enterPlaygroundRoom,
  focusRichText,
  getAllNoteIds,
  getClipboardHTML,
  getClipboardSnapshot,
  getClipboardText,
  getEdgelessSelectedRectModel,
  getInlineSelectionIndex,
  getInlineSelectionText,
  getPageSnapshot,
  getRichTextBoundingBox,
  initDatabaseDynamicRowWithData,
  initEmptyDatabaseWithParagraphState,
  initEmptyEdgelessState,
  initEmptyParagraphState,
  initThreeParagraphs,
  pasteByKeyboard,
  pasteContent,
  pressArrowLeft,
  pressArrowRight,
  pressEnter,
  pressEscape,
  pressShiftTab,
  pressSpace,
  pressTab,
  selectAllByKeyboard,
  selectNoteInEdgeless,
  setInlineRangeInSelectedRichText,
  SHORT_KEY,
  switchEditorMode,
  toViewCoord,
  triggerComponentToolbarAction,
  type,
  undoByKeyboard,
  waitForInlineEditorStateUpdated,
  waitNextFrame,
} from '../utils/actions/index.js';
import {
  assertBlockTypes,
  assertEdgelessNoteBackground,
  assertEdgelessSelectedModelRect,
  assertExists,
  assertRichTextModelType,
  assertRichTexts,
  assertStoreMatchJSX,
  assertText,
} from '../utils/asserts.js';
import { scoped, test } from '../utils/playwright.js';

test('paste a non-nested list to a non-nested list', async ({ page }) => {
  await enterPlaygroundRoom(page);
  await initEmptyParagraphState(page);
  await focusRichText(page);

  const clipData = {
    'text/plain': `
- a
`,
  };
  await type(page, '-');
  await pressSpace(page);
  await type(page, '123');
  await page.keyboard.press('Control+ArrowLeft');

  // paste on start
  await waitNextFrame(page);
  await pasteContent(page, clipData);
  await pressArrowLeft(page);
  await assertRichTexts(page, ['a123']);

  // paste in middle
  await pressArrowRight(page, 2);
  await pasteContent(page, clipData);
  await pressArrowRight(page);
  await assertRichTexts(page, ['a1a23']);

  // paste on end
  await pressArrowRight(page);
  await pasteContent(page, clipData);
  await waitNextFrame(page);
  await assertRichTexts(page, ['a1a23a']);

  await assertBlockTypes(page, ['bulleted']);
});

test('copy a nested list by clicking button, the clipboard data should be complete', async ({
  page,
}, testInfo) => {
  await enterPlaygroundRoom(page);
  await initEmptyParagraphState(page);
  await focusRichText(page);

  const clipData = {
    'text/plain': `
- aaa
  - bbb
    - ccc
`,
  };
  await pasteContent(page, clipData);

  const rootListBound = await page.locator('affine-list').first().boundingBox();
  assertExists(rootListBound);

  // use drag element to test.
  await dragBetweenCoords(
    page,
    { x: rootListBound.x + 1, y: rootListBound.y - 1 },
    { x: rootListBound.x + 1, y: rootListBound.y + rootListBound.height - 1 }
  );
  await copyByKeyboard(page);

  const text = await getClipboardText(page);
  const html = await getClipboardHTML(page);
  const snapshot = await getClipboardSnapshot(page);
  expect(text).toMatchSnapshot(`${testInfo.title}-clipboard.md`);
  expect(JSON.stringify(snapshot.snapshot.content, null, 2)).toMatchSnapshot(
    `${testInfo.title}-clipboard.json`
  );
  expect(html).toMatchSnapshot(`${testInfo.title}-clipboard.html`);
});

test('paste a nested list to a nested list', async ({ page }) => {
  await enterPlaygroundRoom(page);
  await initEmptyParagraphState(page);
  await focusRichText(page);

  const clipData = {
    'text/plain': `
- aaa
  - bbb
    - ccc
`,
  };
  await pasteContent(page, clipData);
  await focusRichText(page, 1);

  // paste on start
  await page.keyboard.press('Control+ArrowLeft');

  /**
   * - aaa
   *   - |bbb
   *     - ccc
   */
  await pasteContent(page, clipData);
  /**
   * - aaa
   *   - aaa
   *     - bbb
   *        - ccc|bbb
   *          -ccc
   */

  await assertRichTexts(page, ['aaa', 'aaa', 'bbb', 'cccbbb', 'ccc']);
  expect(await getInlineSelectionText(page)).toEqual('cccbbb');
  expect(await getInlineSelectionIndex(page)).toEqual(3);

  // paste in middle
  await undoByKeyboard(page);
  await pressArrowRight(page);

  /**
   * - aaa
   *   - b|bb
   *     - ccc
   */
  await pasteContent(page, clipData);
  /**
   * - aaa
   *   - baaa
   *     - bbb
   *       - ccc|bb
   *        - ccc
   */

  await assertRichTexts(page, ['aaa', 'baaa', 'bbb', 'cccbb', 'ccc']);
  expect(await getInlineSelectionText(page)).toEqual('cccbb');
  expect(await getInlineSelectionIndex(page)).toEqual(3);

  // paste on end
  await undoByKeyboard(page);
  await page.keyboard.press('Control+ArrowRight');

  /**
   * - aaa
   *   - bbb|
   *     - ccc
   */
  await pasteContent(page, clipData);
  /**
   * - aaa
   *   - bbbaaa
   *     - bbb
   *       - ccc|
   *         - ccc
   */

  await assertRichTexts(page, ['aaa', 'bbbaaa', 'bbb', 'ccc', 'ccc']);
  expect(await getInlineSelectionText(page)).toEqual('ccc');
  expect(await getInlineSelectionIndex(page)).toEqual(3);
});

test('paste nested lists to a nested list', async ({ page }) => {
  await enterPlaygroundRoom(page);
  await initEmptyParagraphState(page);
  await focusRichText(page);

  const clipData = {
    'text/plain': `
- aaa
  - bbb
    - ccc
`,
  };
  await pasteContent(page, clipData);
  await focusRichText(page, 1);

  const clipData2 = {
    'text/plain': `
- 111
  - 222
- 111
  - 222
`,
  };

  // paste on start
  await page.keyboard.press('Control+ArrowLeft');

  /**
   * - aaa
   *   - |bbb
   *     - ccc
   */
  await pasteContent(page, clipData2);
  /**
   * - aaa
   *   - 111
   *     - 222
   *   - 111
   *     - 222|bbb
   *       - ccc
   */

  await assertRichTexts(page, ['aaa', '111', '222', '111', '222bbb', 'ccc']);
  expect(await getInlineSelectionText(page)).toEqual('222bbb');
  expect(await getInlineSelectionIndex(page)).toEqual(3);

  // paste in middle
  await undoByKeyboard(page);
  await pressArrowRight(page);

  /**
   * - aaa
   *   - b|bb
   *     - ccc
   */
  await pasteContent(page, clipData2);
  /**
   * - aaa
   *   - b111
   *     - 222
   *   - 111
   *     - 222|bb
   *     - ccc
   */

  await assertRichTexts(page, ['aaa', 'b111', '222', '111', '222bb', 'ccc']);
  expect(await getInlineSelectionText(page)).toEqual('222bb');
  expect(await getInlineSelectionIndex(page)).toEqual(3);

  // paste on end
  await undoByKeyboard(page);
  await page.keyboard.press('Control+ArrowRight');

  /**
   * - aaa
   *   - bbb|
   *     - ccc
   */
  await pasteContent(page, clipData2);
  /**
   * - aaa
   *   - bbb111
   *     - 222
   *   - 111
   *     - 222|
   *     - ccc
   */

  await assertRichTexts(page, ['aaa', 'bbb111', '222', '111', '222', 'ccc']);
  expect(await getInlineSelectionText(page)).toEqual('222');
  expect(await getInlineSelectionIndex(page)).toEqual(3);
});

test('paste non-nested lists to a nested list', async ({ page }) => {
  await enterPlaygroundRoom(page);
  await initEmptyParagraphState(page);
  await focusRichText(page);

  const clipData = {
    'text/plain': `
- aaa
  - bbb
`,
  };
  await pasteContent(page, clipData);
  await focusRichText(page, 0);

  const clipData2 = {
    'text/plain': `
- 123
- 456
`,
  };

  // paste on start
  await page.keyboard.press('Control+ArrowLeft');

  /**
   * - |aaa
   *   - bbb
   */
  await pasteContent(page, clipData2);
  /**
   * - 123
   * - 456|aaa
   *   - bbb
   */

  await assertRichTexts(page, ['123', '456aaa', 'bbb']);
  expect(await getInlineSelectionText(page)).toEqual('456aaa');
  expect(await getInlineSelectionIndex(page)).toEqual(3);
});

test(scoped`cut should work for multi-block selection`, async ({ page }) => {
  await enterPlaygroundRoom(page);
  await initEmptyParagraphState(page);
  await focusRichText(page);

  await type(page, 'a');
  await pressEnter(page);
  await type(page, 'b');
  await pressEnter(page);
  await type(page, 'c');
  await selectAllByKeyboard(page);
  await selectAllByKeyboard(page);
  await selectAllByKeyboard(page);
  await cutByKeyboard(page);
  await page.locator('.affine-page-viewport').click();
  await waitNextFrame(page);
  await assertText(page, '');
});

test(
  scoped`pasting into empty list should not convert the list into paragraph`,
  async ({ page }) => {
    await enterPlaygroundRoom(page);
    await initEmptyParagraphState(page);
    await focusRichText(page);
    await type(page, 'test');
    await setInlineRangeInSelectedRichText(page, 0, 4);
    await copyByKeyboard(page);
    await type(page, '- ');
    await page.keyboard.press(`${SHORT_KEY}+v`);
    await assertRichTexts(page, ['test']);
    await assertRichTextModelType(page, 'bulleted');
  }
);

test('cut will delete all content, and copy will reappear content', async ({
  page,
}, testInfo) => {
  await enterPlaygroundRoom(page);
  await initEmptyParagraphState(page);
  await focusRichText(page);
  await type(page, '-');
  await pressSpace(page);
  await type(page, '1');
  await pressEnter(page);
  await pressTab(page);
  await type(page, '2');
  await pressEnter(page);
  await type(page, '3');
  await pressEnter(page);
  await pressShiftTab(page);
  await type(page, '4');

  const box123 = await getRichTextBoundingBox(page, '1');
  const inside123 = { x: box123.left + 1, y: box123.top + 1 };

  const box789 = await getRichTextBoundingBox(page, '6');
  const inside789 = { x: box789.right - 1, y: box789.bottom - 1 };
  // from top to bottom
  await dragBetweenCoords(page, inside123, inside789);

  await cutByKeyboard(page);
  await waitNextFrame(page);
  expect(await getPageSnapshot(page, true)).toMatchSnapshot(
    `${testInfo.title}_after-cut.json`
  );
  await waitNextFrame(page);
  await focusRichText(page);

  await pasteByKeyboard(page);
  await waitNextFrame(page);
  expect(await getPageSnapshot(page, true)).toMatchSnapshot(
    `${testInfo.title}_after-paste.json`
  );
});

test(scoped`should copy and paste of database work`, async ({ page }) => {
  await enterPlaygroundRoom(page);
  await initEmptyDatabaseWithParagraphState(page);

  // init database columns and rows
  await initDatabaseColumn(page);
  await initDatabaseDynamicRowWithData(page, 'abc', true);
  await pressEscape(page);
  await focusRichText(page, 1);
  await selectAllByKeyboard(page);
  await selectAllByKeyboard(page);
  await copyByKeyboard(page);
  await pressEnter(page);
  await pasteByKeyboard(page);
  await page.waitForTimeout(100);

  await assertStoreMatchJSX(
    page,
    /*xml*/ `
<affine:page>
  <affine:note
    prop:background="--affine-note-background-white"
    prop:displayMode="both"
    prop:edgeless={
      Object {
        "style": Object {
          "borderRadius": 8,
          "borderSize": 4,
          "borderStyle": "none",
          "shadowType": "--affine-note-shadow-box",
        },
      }
    }
    prop:hidden={false}
    prop:index="a0"
    prop:lockedBySelf={false}
  >
    <affine:database
      prop:columns="Array [2]"
      prop:title="Database 1"
      prop:views="Array [1]"
    >
      <affine:paragraph
        prop:collapsed={false}
        prop:type="text"
      />
    </affine:database>
    <affine:database
      prop:columns="Array [2]"
      prop:title="Database 1"
      prop:views="Array [1]"
    >
      <affine:paragraph
        prop:collapsed={false}
        prop:type="text"
      />
    </affine:database>
    <affine:paragraph
      prop:collapsed={false}
      prop:type="text"
    />
  </affine:note>
</affine:page>`
  );

  await undoByKeyboard(page);
  await assertStoreMatchJSX(
    page,
    /*xml*/ `
<affine:page>
  <affine:note
    prop:background="--affine-note-background-white"
    prop:displayMode="both"
    prop:edgeless={
      Object {
        "style": Object {
          "borderRadius": 8,
          "borderSize": 4,
          "borderStyle": "none",
          "shadowType": "--affine-note-shadow-box",
        },
      }
    }
    prop:hidden={false}
    prop:index="a0"
    prop:lockedBySelf={false}
  >
    <affine:database
      prop:columns="Array [2]"
      prop:title="Database 1"
      prop:views="Array [1]"
    >
      <affine:paragraph
        prop:collapsed={false}
        prop:type="text"
      />
    </affine:database>
    <affine:paragraph
      prop:collapsed={false}
      prop:type="text"
    />
  </affine:note>
</affine:page>`
  );
});

test(`copy canvas element and text note in edgeless mode`, async ({ page }) => {
  await enterPlaygroundRoom(page);
  await initEmptyEdgelessState(page);
  await switchEditorMode(page);
  await initThreeParagraphs(page);
  await createShapeElement(page, [0, 0], [100, 100]);
  await selectAllByKeyboard(page);
  const bound = await getEdgelessSelectedRectModel(page);
  await copyByKeyboard(page);
  const coord = await toViewCoord(page, [
    bound[0] + bound[2] / 2,
    bound[1] + bound[3] / 2 + 200,
  ]);
  await page.mouse.move(coord[0], coord[1]);
  await page.waitForTimeout(300);
  await pasteByKeyboard(page, false);
  bound[1] = bound[1] + 200;
  await assertEdgelessSelectedModelRect(page, bound);
});

test(scoped`copy when text note active in edgeless`, async ({ page }) => {
  await enterPlaygroundRoom(page);
  const ids = await initEmptyEdgelessState(page);
  await focusRichText(page);
  await type(page, '1234');

  await switchEditorMode(page);

  await activeNoteInEdgeless(page, ids.noteId);
  await waitForInlineEditorStateUpdated(page);
  await setInlineRangeInSelectedRichText(page, 0, 4);
  await copyByKeyboard(page);
  await pressArrowRight(page);
  await type(page, '555');
  await pasteByKeyboard(page, false);
  await assertText(page, '12345551234');
});

test(scoped`paste note block with background`, async ({ page }) => {
  await enterPlaygroundRoom(page);
  const ids = await initEmptyEdgelessState(page);
  await focusRichText(page);
  await type(page, '1234');

  await switchEditorMode(page);
  await selectNoteInEdgeless(page, ids.noteId);

  await triggerComponentToolbarAction(page, 'changeNoteColor');
  const color = '--affine-note-background-grey';
  await changeEdgelessNoteBackground(page, color);
  await assertEdgelessNoteBackground(page, ids.noteId, color);

  await copyByKeyboard(page);

  await page.mouse.move(0, 0);
  await pasteByKeyboard(page, false);
  const noteIds = await getAllNoteIds(page);
  for (const noteId of noteIds) {
    await assertEdgelessNoteBackground(page, noteId, color);
  }
});

test(scoped`copy and paste to selection block selection`, async ({ page }) => {
  test.info().annotations.push({
    type: 'issue',
    description: 'https://github.com/toeverything/blocksuite/issues/2265',
  });
  await enterPlaygroundRoom(page);
  await initEmptyParagraphState(page);
  await focusRichText(page);
  await type(page, '1234');

  await selectAllByKeyboard(page);
  await copyByKeyboard(page);
  await pressArrowRight(page);
  await pasteByKeyboard(page, false);
  await waitNextFrame(page);
  await assertRichTexts(page, ['12341234']);
});

test(
  scoped`should keep paragraph block's type when pasting at the start of empty paragraph block except type text`,
  async ({ page }, testInfo) => {
    test.info().annotations.push({
      type: 'issue',
      description: 'https://github.com/toeverything/blocksuite/issues/2336',
    });
    await enterPlaygroundRoom(page);
    await initEmptyParagraphState(page);
    await focusRichText(page);
    await focusRichText(page);
    await type(page, '>');
    await page.keyboard.press('Space', { delay: 50 });

    await page.evaluate(() => {
      const input = document.createElement('input');
      input.setAttribute('id', 'input-test');
      input.value = '123';
      document.body.querySelector('#app')?.append(input);
    });
    await page.focus('#input-test');
    await page.dblclick('#input-test');
    await copyByKeyboard(page);
    await focusRichText(page);
    await pasteByKeyboard(page);
    await waitNextFrame(page);

    expect(await getPageSnapshot(page, true)).toMatchSnapshot(
      `${testInfo.title}_after-paste-1.json`
    );

    await pressEnter(page);
    await waitNextFrame(page);
    await pressEnter(page);
    await waitNextFrame(page);
    await pasteByKeyboard(page, false);
    await waitNextFrame(page);

    expect(await getPageSnapshot(page, true)).toMatchSnapshot(
      `${testInfo.title}_after-paste-2.json`
    );
  }
);

test(scoped`paste from FeiShu list format`, async ({ page }) => {
  test.info().annotations.push({
    type: 'issue',
    description: 'https://github.com/toeverything/blocksuite/issues/2438',
  });
  await enterPlaygroundRoom(page);
  await initEmptyParagraphState(page);
  await focusRichText(page);

  // set up clipboard data using html
  const clipData = {
    'text/html': `<div><li><div><span>aaaa</span></div></li></div>`,
  };
  await waitNextFrame(page);
  await page.evaluate(
    ({ clipData }) => {
      const dT = new DataTransfer();
      const e = new ClipboardEvent('paste', { clipboardData: dT });
      Object.defineProperty(e, 'target', {
        writable: false,
        value: document,
      });
      e.clipboardData?.setData('text/html', clipData['text/html']);
      document.dispatchEvent(e);
    },
    { clipData }
  );
  await assertText(page, 'aaaa');
  await assertBlockTypes(page, ['bulleted']);
});

test(scoped`paste in list format`, async ({ page }) => {
  test.info().annotations.push({
    type: 'issue',
    description: 'https://github.com/toeverything/blocksuite/issues/2281',
  });
  await enterPlaygroundRoom(page);
  await initEmptyParagraphState(page);
  await focusRichText(page);

  await type(page, '- test');
  await focusRichText(page);

  const clipData = {
    'text/html': `<ul><li>111<ul><li>222</li></ul></li></ul>`,
  };
  await waitNextFrame(page);
  await page.evaluate(
    ({ clipData }) => {
      const dT = new DataTransfer();
      const e = new ClipboardEvent('paste', { clipboardData: dT });
      Object.defineProperty(e, 'target', {
        writable: false,
        value: document,
      });
      e.clipboardData?.setData('text/html', clipData['text/html']);
      document.dispatchEvent(e);
    },
    { clipData }
  );
  await assertRichTexts(page, ['test111', '222']);
});
