// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { galata } from '@jupyterlab/galata';
import { expect, test } from '@jupyterlab/galata';

import type { Locator } from '@playwright/test';

const sidebarElementIds = {
  'left-sidebar': [
    'filebrowser',
    'jp-running-sessions',
    'table-of-contents',
    'extensionmanager.main-view'
  ],
  'right-sidebar': ['jp-property-inspector', 'jp-debugger-sidebar']
};

const sidebarIds: galata.SidebarTabId[] = sidebarElementIds[
  'left-sidebar'
].concat(sidebarElementIds['right-sidebar']);

type ShowSourcesCase = {
  name: string;
  screenshotSuffix: string;
  showSourcesInMainArea: boolean;
  expectSourcesPanel: boolean;
};
const showSourcesCases: ShowSourcesCase[] = [
  {
    name: 'showSourcesInMainArea = false',
    screenshotSuffix: '-sidebar',
    showSourcesInMainArea: false,
    expectSourcesPanel: true
  },
  {
    name: 'showSourcesInMainArea = true',
    screenshotSuffix: '-main-area',
    showSourcesInMainArea: true,
    expectSourcesPanel: false
  }
];

const DEBUGGER_SIDEBAR_ID = 'jp-debugger-sidebar';

test.use({
  mockState: true
});

/**
 * Add provided text as label on first tab in given tabbar.
 * By default we only have icons, but we should test for the
 * styling of labels which are used downstream (e.g. sidecar).
 */
async function mockLabelOnFirstTab(tabbar: Locator, text: string) {
  await tabbar
    .locator('.lm-TabBar-tabLabel')
    .first()
    .evaluate((node: HTMLElement, text: string) => {
      node.innerText = text;
    }, text);
}

test.describe('Sidebars (except debugger)', () => {
  sidebarIds
    .filter(id => id !== DEBUGGER_SIDEBAR_ID)
    .forEach(sidebarId => {
      test(`Open Sidebar tab ${sidebarId}`, async ({ page }) => {
        await page.sidebar.openTab(sidebarId);
        expect(await page.sidebar.isTabOpen(sidebarId)).toBe(true);

        const position = await page.sidebar.getTabPosition(sidebarId);
        const sidebar = page.sidebar.getContentPanelLocator(
          position ?? undefined
        );

        const imageName = `opened-sidebar-${sidebarId.replace('.', '-')}.png`;

        expect(await sidebar.screenshot()).toMatchSnapshot(imageName);
      });
    });
});

test.describe('Debugger sidebar', () => {
  for (const c of showSourcesCases) {
    test.describe(c.name, () => {
      test.use({
        mockSettings: {
          ...galata.DEFAULT_SETTINGS,
          '@jupyterlab/debugger-extension:main': {
            showSourcesInMainArea: c.showSourcesInMainArea
          }
        }
      });

      test(`Open Sidebar tab jp-debugger-sidebar`, async ({ page }) => {
        await page.sidebar.openTab(DEBUGGER_SIDEBAR_ID);
        expect(await page.sidebar.isTabOpen(DEBUGGER_SIDEBAR_ID)).toBe(true);

        const position = await page.sidebar.getTabPosition(DEBUGGER_SIDEBAR_ID);
        const sidebar = page.sidebar.getContentPanelLocator(
          position ?? undefined
        );

        const imageName =
          `opened-sidebar-${DEBUGGER_SIDEBAR_ID.replace('.', '-')}` +
          `${c.screenshotSuffix}.png`;

        expect(await sidebar.screenshot()).toMatchSnapshot(imageName);
      });
    });
  }
});

test('File Browser has no unused rules', async ({ page }) => {
  await page.sidebar.openTab('filebrowser');
  const clickMenuItem = async (command): Promise<void> => {
    const contextmenu = await page.menu.openContextMenuLocator(
      '.jp-DirListing-headerItem'
    );
    const item = await page.menu.getMenuItemLocatorInMenu(contextmenu, command);
    await item?.click();
  };
  await clickMenuItem('Show File Checkboxes');
  await clickMenuItem('Show File Size Column');
  await clickMenuItem('Show Date Created Column');

  await page.notebook.createNew('notebook.ipynb');

  const unusedRules = await page.style.findUnusedStyleRules({
    fragments: ['jp-DirListing', 'jp-FileBrowser'],
    exclude: [
      // active during renaming
      'jp-DirListing-editor',
      // hidden files
      '[data-is-dot]',
      // filtering results
      '.jp-DirListing-content mark',
      // only added after resizing
      'jp-DirListing-narrow',
      // used in "open file" dialog containing a file browser
      '.jp-Open-Dialog',
      // used for movable sections in file browser, not visible
      // when no sections are moved
      'jp-FileBrowser-accordion'
    ]
  });
  expect(unusedRules.length).toEqual(0);
});

test('Left light tabbar (with text)', async ({ page }) => {
  await page.theme.setLightTheme();
  const imageName = 'left-light-tabbar-with-text.png';
  const tabbar = page.sidebar.getTabBarLocator();
  await mockLabelOnFirstTab(tabbar, 'File Browser');
  expect(await tabbar.screenshot()).toMatchSnapshot(imageName.toLowerCase());
});

test('Right dark tabbar (with text)', async ({ page }) => {
  await page.theme.setDarkTheme();
  const imageName = 'right-dark-tabbar-with-text.png';
  const tabbar = page.sidebar.getTabBarLocator('right');
  await mockLabelOnFirstTab(tabbar, 'Property Inspector');
  expect(await tabbar.screenshot()).toMatchSnapshot(imageName.toLowerCase());
});

test('Move File Browser to right', async ({ page }) => {
  await page.sidebar.moveTabToRight('filebrowser');
  expect(await page.sidebar.getTabPosition('filebrowser')).toBe('right');
});

test('Open File Browser on right', async ({ page }) => {
  await page.sidebar.moveTabToRight('filebrowser');
  await page.sidebar.openTab('filebrowser');
  expect(await page.sidebar.isTabOpen('filebrowser')).toEqual(true);
});

test('Open Sidebar on right', async ({ page }) => {
  await page.sidebar.open('right');
  expect(await page.sidebar.isOpen('right')).toEqual(true);
});

test('Close Sidebar on right', async ({ page }) => {
  await page.sidebar.open('right');
  await page.menu.clickMenuItem('View>Appearance>Show Right Sidebar');
  expect(await page.sidebar.isOpen('right')).toEqual(false);
});

test('Capture File Browser on right', async ({ page }) => {
  await page.sidebar.moveTabToRight('filebrowser');
  await page.sidebar.openTab('filebrowser');

  let imageName = 'filebrowser-right.png';
  expect(await page.screenshot()).toMatchSnapshot(imageName);
});

test('Move Debugger to left', async ({ page }) => {
  await page.sidebar.moveTabToLeft('jp-debugger-sidebar');
  expect(await page.sidebar.getTabPosition('jp-debugger-sidebar')).toEqual(
    'left'
  );
});

test('Check Running Session button on sidebar has correct aria label and role', async ({
  page
}) => {
  await page.sidebar.open('left');
  const runningSessionsWidget = page.locator('#jp-running-sessions');
  const runningSessionsElementAriaLabel =
    await runningSessionsWidget.getAttribute('aria-label');
  const runningSessionsElementRole =
    await runningSessionsWidget.getAttribute('role');
  expect(runningSessionsElementAriaLabel).toEqual('Running Sessions section');
  expect(runningSessionsElementRole).toEqual('region');
});

test('Check Extension Manager button on sidebar has correct aria label and role', async ({
  page
}) => {
  await page.sidebar.open('left');
  const extensionManagerWidget = page.locator('#extensionmanager\\.main-view');
  const extensionManagerElementAriaLabel =
    await extensionManagerWidget.getAttribute('aria-label');
  const extensionManagerElementRole =
    await extensionManagerWidget.getAttribute('role');
  expect(extensionManagerElementAriaLabel).toEqual('Extension Manager section');
  expect(extensionManagerElementRole).toEqual('region');
});

test('Check File Browser button on sidebar has correct aria label and role', async ({
  page
}) => {
  await page.sidebar.open('left');
  const fileBrowserWidget = page.locator('#filebrowser');
  const fileBrowserElementAriaLabel =
    await fileBrowserWidget.getAttribute('aria-label');
  const fileBrowserElementRole = await fileBrowserWidget.getAttribute('role');
  expect(fileBrowserElementAriaLabel).toEqual('File Browser Section');
  expect(fileBrowserElementRole).toEqual('region');
});

test('Check Debugger button on sidebar has correct aria label and role', async ({
  page
}) => {
  await page.sidebar.open('right');
  const debuggerWidget = page.locator('#jp-debugger-sidebar');
  const debuggerElementAriaLabel =
    await debuggerWidget.getAttribute('aria-label');
  const debuggerElementRole = await debuggerWidget.getAttribute('role');
  expect(debuggerElementAriaLabel).toEqual('Debugger section');
  expect(debuggerElementRole).toEqual('region');
});

test('Check Table of Contents button on sidebar has correct aria label and role', async ({
  page
}) => {
  await page.sidebar.open('left');
  const tableOfContentsWidget = page.locator('#table-of-contents');
  const tableOfContentsElementAriaLabel =
    await tableOfContentsWidget.getAttribute('aria-label');
  const tableOfContentsElementRole =
    await tableOfContentsWidget.getAttribute('role');
  expect(tableOfContentsElementAriaLabel).toEqual('Table of Contents section');
  expect(tableOfContentsElementRole).toEqual('region');
});

const elementAriaLabels = {
  'jp-running-sessions': [
    'Open Tabs Section',
    'Kernels Section',
    'Language servers Section',
    'Recently Closed Section',
    'Workspaces Section',
    'Terminals Section'
  ],
  'jp-debugger-sidebar': [
    'Variables Section',
    'Callstack Section',
    'Breakpoints Section',
    'Source Section',
    'Kernel Sources Section'
  ],
  'extensionmanager.main-view': [
    'Warning Section',
    'Installed Section',
    'Discover Section'
  ]
};

test.describe('Sidebar keyboard navigation @a11y', () => {
  test.use({
    mockSettings: {
      ...galata.DEFAULT_SETTINGS,
      '@jupyterlab/debugger-extension:main': {
        showSourcesInMainArea: false
      }
    }
  });
  test.skip(
    ({ browserName }) => browserName === 'firefox',
    'Some cases fail on Firefox'
  );

  Object.keys(sidebarElementIds).forEach(sideBar => {
    test(`Open ${sideBar} via keyboard navigation`, async ({ page }) => {
      const keyValueArray: string[] = sidebarElementIds[sideBar];

      for (let dataId of keyValueArray) {
        await page.goto();
        await page.sidebar.close('right');
        await page.sidebar.close('left');
        await page.activity.keyToElement(
          `[data-id='${keyValueArray[0]}']`,
          'Tab'
        );

        await page.activity.keyToElement(`[data-id='${dataId}']`, 'ArrowDown');

        await page.keyboard.press('Enter');

        expect(await page.sidebar.isTabOpen(dataId)).toEqual(true);
      }
    });
  });

  Object.keys(elementAriaLabels).forEach(tabName => {
    test(`Open accordion panels ${tabName} via keyboard navigation`, async ({
      page
    }) => {
      await page.sidebar.openTab(tabName);

      const keyValueArray: string[] = elementAriaLabels[tabName];

      for (let ariaLabel of keyValueArray) {
        const elementLocator = page.locator(`[aria-label='${ariaLabel}']`);
        let initialState = await elementLocator.getAttribute('aria-expanded');

        await page.activity.keyToElement(`[aria-label='${ariaLabel}']`, 'Tab');
        await page.keyboard.press('Enter');
        let stateAfter = await elementLocator.getAttribute('aria-expanded');

        expect(initialState).not.toEqual(stateAfter);

        await page.keyboard.press('Enter');
        let finalState = await elementLocator.getAttribute('aria-expanded');

        expect(initialState).toEqual(finalState);
      }
    });
  });
});

test.describe('Running Sessions - Move Sections to File Browser', () => {
  const RUNNING_SECTION_TITLE_SELECTOR =
    '#jp-running-sessions .jp-AccordionPanel-title';
  const HOSTED_SECTION_TITLE_SELECTOR =
    '.jp-FileBrowser-accordion .jp-hosted-section';
  const MOVE_TO_FB_COMMAND_SELECTOR =
    '.lm-Menu-content .lm-Menu-item[data-command="apputils:move-section-to"]';
  const MOVE_BACK_COMMAND_SELECTOR =
    '.lm-Menu-content .lm-Menu-item[data-command="apputils:move-section-back"]';

  test.use({
    tmpPath: 'running-sessions-filebrowser-test'
  });

  test.beforeAll(async ({ request, tmpPath }) => {
    const contents = galata.newContentsHelper(request);
    await contents.uploadContent('hello', 'text', `${tmpPath}/test.txt`);
  });

  test.beforeEach(async ({ page, tmpPath }) => {
    await galata.Mock.freezeContentLastModified(page);
    await page.filebrowser.openDirectory(tmpPath);
  });

  test('should move sections to file browser, take screenshot, and move back via context menu', async ({
    page
  }) => {
    // Open a .txt file and a terminal so Open Tabs has entries
    await page.filebrowser.open('test.txt');
    await page.menu.clickMenuItem('File>New>Terminal');

    // Move "Open Tabs" to the file browser
    await page.sidebar.openTab('jp-running-sessions');
    const openTabsRunningTitle = page
      .locator(RUNNING_SECTION_TITLE_SELECTOR)
      .filter({ hasText: 'Open Tabs' });
    await openTabsRunningTitle.click({ button: 'right' });
    await page
      .locator(MOVE_TO_FB_COMMAND_SELECTOR)
      .filter({ hasText: 'File Browser' })
      .click();

    // Move "Terminals" to the file browser
    const terminalsRunningTitle = page
      .locator(RUNNING_SECTION_TITLE_SELECTOR)
      .filter({ hasText: 'Terminals' });
    await terminalsRunningTitle.click({ button: 'right' });
    await page
      .locator(MOVE_TO_FB_COMMAND_SELECTOR)
      .filter({ hasText: 'File Browser' })
      .click();

    // Switch to the file browser and verify both sections are hosted there
    await page.sidebar.openTab('filebrowser');
    const openTabsHostedTitle = page
      .locator(HOSTED_SECTION_TITLE_SELECTOR)
      .filter({ hasText: 'Open Tabs' });
    const terminalsHostedTitle = page
      .locator(HOSTED_SECTION_TITLE_SELECTOR)
      .filter({ hasText: 'Terminals' });
    await expect(openTabsHostedTitle).toBeVisible();
    await expect(terminalsHostedTitle).toBeVisible();

    // Screenshot with both sections visible in the file browser accordion
    const leftPanel = page.sidebar.getContentPanelLocator('left');
    expect(await leftPanel.screenshot()).toMatchSnapshot(
      'filebrowser-with-moved-sections.png'
    );

    // Move "Open Tabs" back via context menu
    await openTabsHostedTitle.click({ button: 'right' });
    await page
      .locator(MOVE_BACK_COMMAND_SELECTOR)
      .filter({ hasText: 'Running Sessions' })
      .click();
    await expect(openTabsHostedTitle).not.toBeVisible();

    // Move "Terminals" back via context menu
    await terminalsHostedTitle.click({ button: 'right' });
    await page
      .locator(MOVE_BACK_COMMAND_SELECTOR)
      .filter({ hasText: 'Running Sessions' })
      .click();
    await expect(terminalsHostedTitle).not.toBeVisible();

    // Both sections should be back in running sessions
    await page.sidebar.openTab('jp-running-sessions');
    await expect(openTabsRunningTitle).toBeVisible();
    await expect(terminalsRunningTitle).toBeVisible();
  });
});
