// Editor warning bar for files that reference uninstalled R packages.
// Addresses https://github.com/rstudio/rstudio/issues/15830 -- a per-file
// "Don't show for this file" dismissal that can be undone via the
// toggleDetectMissingPackages AppCommand.

import type { Locator, Page } from '@playwright/test';
import { test, expect } from '@fixtures/rstudio.fixture';
import { ConsolePaneActions } from '@actions/console_pane.actions';
import { useSuiteSandbox } from '@utils/sandbox';
import { writeAndOpenFile, closeAndDeleteSandboxFiles } from '@utils/files';
import { executeCommand, isCommandChecked, saveDocument } from '@utils/commands';

// InfoBar duplicates its message into a role="status" live-region for screen
// readers (see InfoBar.java setText -- live_ is populated after a short
// AriaLiveService.UI_ANNOUNCEMENT_DELAY). Scope locator queries to the
// visible label so getByText doesn't trip strict mode once the live region
// has caught up.
function bannerLocator(page: Page, pkg: string): Locator {
  return page
    .getByText(`Package ${pkg} required but is not installed.`)
    .and(page.locator(':not([role="status"])'));
}

// `fortunes` is on CRAN and has no Imports/Depends, so it isn't pulled in by
// any of REQUIRED_PACKAGES' transitive deps -- a reliable "real CRAN package
// the test session doesn't already have installed."
const TEST_PKG = 'fortunes';

test.describe('Missing-package banner', () => {
  const sandbox = useSuiteSandbox();
  let consoleActions: ConsolePaneActions;

  test.beforeAll(async ({ rstudioPage: page }) => {
    consoleActions = new ConsolePaneActions(page);
    await consoleActions.resetSourcePane();
    // discoverPackageDependencies filters to CRAN packages that aren't on
    // disk, so if a prior run or the shared R_LIBS_USER cache already has
    // the package the banner won't appear. Force a clean slate -- assert
    // the helper actually succeeded so a failure here surfaces as a clear
    // setup error rather than a 60s banner-visibility timeout downstream.
    const uninstalled = await consoleActions.uninstallPackage(TEST_PKG);
    expect(uninstalled, `${TEST_PKG} must be absent before the test runs`).toBe(true);
  });

  test.afterEach(async ({ rstudioPage: page }) => {
    await closeAndDeleteSandboxFiles(page, sandbox.dir, ['missing_packages.R']);
  });

  test('dismisses per-file and re-appears when toggleDetectMissingPackages is flipped back on',
    async ({ rstudioPage: page }) => {
      await writeAndOpenFile(
        page,
        sandbox.dir,
        'missing_packages.R',
        `library(${TEST_PKG})\n`,
      );

      // discoverPackageDependencies fires on save (and on
      // AvailablePackagesReadyEvent). Save forces the path we care about.
      await saveDocument(page);

      const banner = bannerLocator(page, TEST_PKG);
      // available.packages() may still be loading the first time the session
      // hits this path; the helper retries on AvailablePackagesReadyEvent,
      // so the visible-banner deadline is intentionally generous.
      await expect(banner).toBeVisible({ timeout: 60000 });
      expect(await isCommandChecked(page, 'toggleDetectMissingPackages')).toBe(true);

      // Per-file dismiss: banner disappears and the AppCommand picks up the
      // property change via the DocUpdateSentinel handler.
      await page.getByText("Don't show for this file").click();
      await expect(banner).toBeHidden({ timeout: 5000 });
      await expect
        .poll(() => isCommandChecked(page, 'toggleDetectMissingPackages'))
        .toBe(false);

      // Re-enable via the AppCommand: clears the suppression property and
      // re-runs discovery, so the banner returns. available.packages() is
      // cached by this point in the session, so this discovery is fast.
      await executeCommand(page, 'toggleDetectMissingPackages');
      await expect(banner).toBeVisible({ timeout: 10000 });
      expect(await isCommandChecked(page, 'toggleDetectMissingPackages')).toBe(true);
    });
});
