import {
  findAllByText,
  fireEvent,
  getByLabelText,
  findByTestId,
  getByText,
  screen,
  findByText,
} from '@testing-library/dom';
import { editor as monacoEditor } from 'monaco-editor';

const isFolderRowOpen = (row) => row.matches('.folder.is-open');

const getLeftSidebar = () => screen.getByTestId('left-sidebar');

export const switchLeftSidebarTab = (name) => {
  const sidebar = getLeftSidebar();

  const button = getByLabelText(sidebar, name);

  button.click();
};

export const getStatusBar = () => document.querySelector('.ide-status-bar');

export const waitForMonacoEditor = () =>
  new Promise((resolve) => monacoEditor.onDidCreateEditor(resolve));

export const waitForEditorDispose = (instance) =>
  new Promise((resolve) => instance.onDidDispose(resolve));

export const waitForEditorModelChange = (instance) =>
  new Promise((resolve) => instance.onDidChangeModel(resolve));

export const findMonacoEditor = () =>
  screen.findAllByLabelText(/Editor content;/).then(([x]) => x.closest('.monaco-editor'));

export const findMonacoDiffEditor = () =>
  screen.findAllByLabelText(/Editor content;/).then(([x]) => x.closest('.monaco-diff-editor'));

export const findAndSetEditorValue = async (value) => {
  const editor = await findMonacoEditor();
  const uri = editor.getAttribute('data-uri');

  monacoEditor.getModel(uri).setValue(value);
};

export const getEditorValue = async () => {
  const editor = await findMonacoEditor();
  const uri = editor.getAttribute('data-uri');

  return monacoEditor.getModel(uri).getValue();
};

const findTreeBody = () => screen.findByTestId('ide-tree-body');

const findRootActions = () => screen.findByTestId('ide-root-actions');

const findFileRowContainer = (row = null) =>
  row ? Promise.resolve(row.parentElement) : findTreeBody();

const findFileChild = async (row, name, index = 0) => {
  const container = await findFileRowContainer(row);
  const children = await findAllByText(container, name, { selector: '.file-row-name' });

  return children
    .map((x) => x.closest('.file-row'))
    .find((x) => x.dataset.level === index.toString());
};

const openFileRow = (row) => {
  if (!row || isFolderRowOpen(row)) {
    return;
  }

  row.click();
};

export const findAndTraverseToPath = async (path, index = 0, row = null) => {
  if (!path) {
    return row;
  }

  const [name, ...restOfPath] = path.split('/');

  openFileRow(row);

  const child = await findFileChild(row, name, index);

  return findAndTraverseToPath(restOfPath.join('/'), index + 1, child);
};

const clickFileRowAction = (row, name) => {
  fireEvent.mouseOver(row);

  const dropdownButton = getByLabelText(row, 'Create new file or directory');
  dropdownButton.click();

  const dropdownAction = getByLabelText(dropdownButton.parentNode, name);
  dropdownAction.click();
};

const fillFileNameModal = async (value, submitText = 'Create file') => {
  const modal = await screen.findByTestId('ide-new-entry');

  const nameField = await findByTestId(modal, 'file-name-field');
  fireEvent.input(nameField, { target: { value } });

  const createButton = getByText(modal, submitText, { selector: 'button' });
  createButton.click();
};

const findAndClickRootAction = async (name) => {
  const container = await findRootActions();
  const button = getByLabelText(container, name);

  button.click();
};

/**
 * Drop leading "/-/ide" and file path from the current URL
 */
export const getBaseRoute = (url = window.location.pathname) =>
  url.replace(/^\/-\/ide/, '').replace(/\/-\/.*$/, '');

export const clickPreviewMarkdown = () => {
  screen.getByText('Preview Markdown').click();
};

export const openFile = async (path) => {
  const row = await findAndTraverseToPath(path);

  openFileRow(row);
};

export const waitForTabToOpen = (fileName) =>
  findByText(document.querySelector('.multi-file-edit-pane'), fileName);

export const createFile = async (path, content) => {
  const parentPath = path.split('/').slice(0, -1).join('/');

  const parentRow = await findAndTraverseToPath(parentPath);

  if (parentRow) {
    clickFileRowAction(parentRow, 'New file');
  } else {
    await findAndClickRootAction('New file');
  }

  await fillFileNameModal(path);
  await findAndSetEditorValue(content);
};

export const updateFile = async (path, content) => {
  await openFile(path);
  await findAndSetEditorValue(content);
};

export const getFilesList = () => {
  return screen.getAllByTestId('file-row-name-container').map((e) => e.textContent.trim());
};

export const deleteFile = async (path) => {
  const row = await findAndTraverseToPath(path);
  clickFileRowAction(row, 'Delete');
};

export const renameFile = async (path, newPath) => {
  const row = await findAndTraverseToPath(path);
  clickFileRowAction(row, 'Rename/Move');

  await fillFileNameModal(newPath, 'Rename file');
};

export const closeFile = async (path) => {
  const button = await screen.getByLabelText(`Close ${path}`, {
    selector: '.multi-file-tabs button',
  });

  button.click();
};

/**
 * Fill out and submit the commit form in the Web IDE
 *
 * @param {Object} options - Used to fill out the commit form in the IDE
 * @param {Boolean} options.newBranch - Flag for the "Create a new branch" radio.
 * @param {Boolean} options.newMR - Flag for the "Start a new merge request" checkbox.
 * @param {String} options.newBranchName - Value to put in the new branch name input field. The Web IDE supports leaving this field blank.
 */
export const commit = async ({ newBranch = false, newMR = false, newBranchName = '' } = {}) => {
  switchLeftSidebarTab('Commit');
  screen.getByTestId('begin-commit-button').click();

  if (!newBranch) {
    const option = await screen.findByLabelText(/Commit to .+ branch/);
    option.click();
  } else {
    const option = await screen.findByLabelText('Create a new branch');
    option.click();

    const branchNameInput = await screen.findByTestId('ide-new-branch-name');
    fireEvent.input(branchNameInput, { target: { value: newBranchName } });

    const mrCheck = await screen.findByLabelText('Start a new merge request');
    if (Boolean(mrCheck.checked) !== newMR) {
      mrCheck.click();
    }
  }

  screen.getByText('Commit').click();
};
