import { waitFor } from '@testing-library/dom';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
import initDiffsApp from '~/diffs';
import { createStore } from '~/mr_notes/stores';
import {
  getDiffCodePart,
  getLineNumberFromCodeElement,
  getCodeElementFromLineNumber,
} from './diffs_interopability_api';

const TEST_PROJECT_PATH = 'gitlab-org/gitlab-test';
const TEST_BASE_URL = `/${TEST_PROJECT_PATH}/-/merge_requests/1/`;
const TEST_DIFF_FILE = 'files/js/commit.coffee';
const EXPECT_INLINE = [
  ['head', 1],
  ['head', 2],
  ['head', 3],
  ['base', 4],
  ['head', 4],
  null,
  ['base', 6],
  ['head', 6],
  null,
];
const EXPECT_PARALLEL_LEFT_SIDE = [
  ['base', 1],
  ['base', 2],
  ['base', 3],
  ['base', 4],
  null,
  ['base', 6],
  null,
];
const EXPECT_PARALLEL_RIGHT_SIDE = [
  ['head', 1],
  ['head', 2],
  ['head', 3],
  ['head', 4],
  null,
  ['head', 6],
  null,
];

const startDiffsApp = () => {
  const el = document.createElement('div');
  el.id = 'js-diffs-app';
  document.body.appendChild(el);
  Object.assign(el.dataset, {
    endpoint: TEST_BASE_URL,
    endpointMetadata: `${TEST_BASE_URL}diffs_metadata.json`,
    endpointBatch: `${TEST_BASE_URL}diffs_batch.json`,
    projectPath: TEST_PROJECT_PATH,
    helpPagePath: '/help',
    currentUserData: 'null',
    changesEmptyStateIllustration: '',
    isFluidLayout: 'false',
    dismissEndpoint: '',
    showSuggestPopover: 'false',
    showWhitespaceDefault: 'true',
    viewDiffsFileByFile: 'false',
    defaultSuggestionCommitMessage: 'Lorem ipsum',
  });

  const store = createStore();

  const vm = initDiffsApp(store);

  store.dispatch('setActiveTab', 'diffs');

  return vm;
};

describe('diffs third party interoperability', () => {
  let vm;

  afterEach(() => {
    vm.$destroy();
    document.body.innerHTML = '';
  });

  const tryOrErrorMessage = (fn) => (...args) => {
    try {
      return fn(...args);
    } catch (e) {
      return e.message;
    }
  };

  const findDiffFile = () => document.querySelector(`.diff-file[data-path="${TEST_DIFF_FILE}"]`);
  const hasLines = (sel = 'tr.line_holder') => findDiffFile().querySelectorAll(sel).length > 0;
  const findLineElements = (sel = 'tr.line_holder') =>
    Array.from(findDiffFile().querySelectorAll(sel));

  const findCodeElements = (lines, sel = 'td.line_content') => {
    return lines.map((x) => x.querySelector(`${sel} span.line`));
  };

  const getCodeElementsInteropModel = (codeElements) =>
    codeElements.map(
      (x) =>
        x && [
          tryOrErrorMessage(getDiffCodePart)(x),
          tryOrErrorMessage(getLineNumberFromCodeElement)(x),
        ],
    );

  describe.each`
    desc                          | view          | rowSelector               | codeSelector                          | expectation
    ${'inline view'}              | ${'inline'}   | ${'.diff-tr.line_holder'} | ${'.diff-td.line_content'}            | ${EXPECT_INLINE}
    ${'parallel view left side'}  | ${'parallel'} | ${'.diff-tr.line_holder'} | ${'.diff-td.line_content.left-side'}  | ${EXPECT_PARALLEL_LEFT_SIDE}
    ${'parallel view right side'} | ${'parallel'} | ${'.diff-tr.line_holder'} | ${'.diff-td.line_content.right-side'} | ${EXPECT_PARALLEL_RIGHT_SIDE}
  `('$desc', ({ view, rowSelector, codeSelector, expectation }) => {
    beforeEach(async () => {
      setWindowLocation(`${TEST_HOST}/${TEST_BASE_URL}/diffs?view=${view}`);

      vm = startDiffsApp();

      await waitFor(() => expect(hasLines(rowSelector)).toBe(true));
    });

    it('should match diff model', () => {
      const lines = findLineElements(rowSelector);
      const codes = findCodeElements(lines, codeSelector);

      expect(getCodeElementsInteropModel(codes)).toEqual(expectation);
    });

    it.each`
      lineNumber | part      | expectedText
      ${4}       | ${'base'} | ${'new CommitFile(this)'}
      ${4}       | ${'head'} | ${'new CommitFile(@)'}
      ${2}       | ${'base'} | ${'constructor: ->'}
      ${2}       | ${'head'} | ${'constructor: ->'}
    `(
      'should find code element lineNumber=$lineNumber part=$part',
      ({ lineNumber, part, expectedText }) => {
        const codeElement = getCodeElementFromLineNumber(findDiffFile(), lineNumber, part);

        expect(codeElement.textContent.trim()).toBe(expectedText);
      },
    );
  });
});