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

import { test } from './fixtures/DrupalSite';
import { Drupal } from './objects/Drupal';

/**
 * Tests the correct size of image is fetched.
 */
test.describe('Responsive Image', () => {
  test.beforeAll(
    'Setup test site with Drupal Canvas',
    async ({ browser, drupalSite }) => {
      const page = await browser.newPage();
      const drupal: Drupal = new Drupal({ page, drupalSite });
      await drupal.applyRecipe(`core/recipes/image_media_type`);
      await drupal.installModules(['canvas', 'canvas_test_sdc']);
      await page.close();
    },
  );

  test('Test responsive image generated by image srcset candidate template uri', async ({
    page,
    drupal,
    canvasEditor,
  }) => {
    await drupal.loginAsAdmin();
    await drupal.createCanvasPage('Homepage', '/homepage');
    await page.goto('/homepage');
    await canvasEditor.goToEditor();
    await canvasEditor.openLibraryPanel();
    await canvasEditor.addComponent({ id: 'sdc.canvas_test_sdc.image' });

    const frame = page
      .locator(
        '[data-testid="canvas-editor-frame-scaling"] [data-canvas-swap-active="true"]',
      )
      .contentFrame();
    await expect(frame.locator('img.image')).toBeVisible();
    await drupal.addMediaImage(
      '../../../fixtures/images/gracie-big.jpg',
      'A cute dog',
    );
    await page.waitForFunction(() => {
      const frame: HTMLIFrameElement = document.querySelector(
        '[data-testid="canvas-editor-frame-scaling"] [data-canvas-swap-active="true"]',
      );
      const img = frame?.contentWindow.document.querySelector('img.image');
      const imgSrc = img?.getAttribute('src');
      return imgSrc && imgSrc.includes('gracie');
    });
    frame.locator('img.image').waitFor({ state: 'visible' });
    await canvasEditor.preview();

    const previewIframe = page
      .locator('iframe[class^="_PagePreviewIframe"]')
      .contentFrame();
    const previewImg = await previewIframe.locator('img.image');
    const previewSrc = await previewImg.getAttribute('src');
    expect(previewSrc).toContain('gracie');

    const previewSrcset = await previewImg.getAttribute('srcset');
    const defaultWidths = [
      16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048,
    ];
    const matches = previewSrcset.match(/itok=[^&\s]+/g);
    expect(matches).toHaveLength(defaultWidths.length);
    defaultWidths.forEach((width) => {
      expect(previewSrcset).toContain(
        `/styles/canvas_parametrized_width--${width}`,
      );
      expect(previewSrcset).toContain(` ${width}w`);
    });

    // The default widths also contain 3840 but the source image is smaller so it shouldn't be generated.
    expect(previewSrcset).not.toContain(
      '/styles/canvas_parametrized_width--3840',
    );
    expect(previewSrcset).not.toContain(' 3840w');

    await previewIframe.locator('img.image').waitFor({ state: 'attached' });
    await page.waitForLoadState('networkidle');
    const src = previewIframe.locator('img.image');
    const currentSrc = await src.evaluate(
      (image: HTMLImageElement) => image.currentSrc,
    );
    await expect(currentSrc).toContain('gracie');
    await expect(currentSrc).toContain('/styles/canvas_parametrized_width--');
  });

  test('Test cards with responsive images', async ({
    drupal,
    canvasEditor,
    page,
  }) => {
    await page.route(
      'https://mdn.github.io/shared-assets/images/examples/balloons.jpg',
      async (route) => {
        await route.fulfill({
          path: './tests/modules/canvas_test_sdc/components/card/balloons.png',
          headers: {
            'content-type': 'image/png',
          },
        });
      },
    );

    await drupal.loginAsAdmin();
    await drupal.createCanvasPage('Cards', '/cards');
    await page.goto('/cards');
    await canvasEditor.goToEditor();
    await canvasEditor.openLibraryPanel();
    await canvasEditor.addComponent({ id: 'sdc.canvas_test_sdc.card' });
    await drupal.addMediaImage(
      '../../../fixtures/images/gracie-big.jpg',
      'A cute dog',
    );
    await canvasEditor.addComponent({
      id: 'sdc.canvas_test_sdc.card-with-local-image',
    });
    await canvasEditor.addComponent({
      id: 'sdc.canvas_test_sdc.card-with-remote-image',
    });
    await canvasEditor.addComponent({
      id: 'sdc.canvas_test_sdc.card-with-stream-wrapper-image',
    });
    await canvasEditor.preview();

    const previewIframe = page
      .locator('iframe[class^="_PagePreviewIframe"]')
      .contentFrame();
    const defaultWidths = [
      16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048,
    ];

    // Card whose image is populated by an image Media entity.
    const card = previewIframe.locator('.card img.card--image');
    await card.scrollIntoViewIfNeeded();
    const cardWidths = 15;
    expect(await card.getAttribute('src')).toContain('gracie');
    const cardSrcset = await card.getAttribute('srcset');
    expect(cardSrcset.match(/itok=[^&\s]+/g)).toHaveLength(cardWidths);
    defaultWidths.slice(0, cardWidths).forEach((width) => {
      expect(cardSrcset).toContain(
        `/styles/canvas_parametrized_width--${width}`,
      );
      expect(cardSrcset).toContain(` ${width}w`);
    });
    expect(cardSrcset).not.toContain(
      `/styles/canvas_parametrized_width--${defaultWidths[cardWidths + 1]}`,
    );
    expect(cardSrcset).not.toContain(`${defaultWidths[cardWidths + 1]}w`);
    await expect(card).toHaveAttribute('sizes', 'auto 50vw');
    await expect(card).toHaveAttribute('alt', 'A cute dog');
    await expect(card).toHaveAttribute('width', '3000');
    await expect(card).toHaveAttribute('height', '2595');
    await expect(card).toHaveAttribute('loading', 'eager');

    // Card with local image.
    const cardLocal = previewIframe.locator(
      '.card--with-local-image img.card--image',
    );
    await cardLocal.scrollIntoViewIfNeeded();
    expect(await cardLocal.getAttribute('src')).toContain(
      '/core/misc/druplicon.png',
    );
    await expect(cardLocal).not.toHaveAttribute('srcset');
    await expect(cardLocal).not.toHaveAttribute('sizes');
    await expect(cardLocal).toHaveAttribute('alt', 'A classic druplicon');
    await expect(cardLocal).toHaveAttribute('width', '88');
    await expect(cardLocal).toHaveAttribute('height', '100');
    await expect(cardLocal).toHaveAttribute('loading', 'lazy');

    // Card with remote image.
    const cardRemote = previewIframe.locator(
      '.card--with-remote-image img.card--image',
    );
    await cardRemote.scrollIntoViewIfNeeded();
    expect(await cardRemote.getAttribute('src')).toContain('balloons');
    await expect(cardRemote).not.toHaveAttribute('srcset');
    await expect(cardRemote).not.toHaveAttribute('sizes');
    await expect(cardRemote).toHaveAttribute('alt', 'Hot air balloons');
    await expect(cardRemote).toHaveAttribute('width', '640');
    await expect(cardRemote).toHaveAttribute('height', '427');
    await expect(cardRemote).toHaveAttribute('loading', 'lazy');

    // Card with stream wrapper image.
    const cardStreamWrapper = previewIframe.locator(
      '.card--with-stream-wrapper-image img.card--image',
    );
    await cardStreamWrapper.scrollIntoViewIfNeeded();
    const cardStreamWrapperWidths = 9;
    expect(await cardStreamWrapper.getAttribute('src')).toContain('balloons');
    const cardStreamWrapperSrcset =
      await cardStreamWrapper.getAttribute('srcset');
    expect(cardStreamWrapperSrcset.match(/itok=[^&\s]+/g)).toHaveLength(
      cardStreamWrapperWidths,
    );
    defaultWidths.slice(0, cardStreamWrapperWidths).forEach((width) => {
      expect(cardStreamWrapperSrcset).toContain(
        `/styles/canvas_parametrized_width--${width}`,
      );
      expect(cardStreamWrapperSrcset).toContain(` ${width}w`);
    });
    await expect(cardStreamWrapper).toHaveAttribute('sizes', 'auto 100vw');
    await expect(cardStreamWrapper).toHaveAttribute('alt', 'Hot air balloons');
    await expect(cardStreamWrapper).toHaveAttribute('width', '640');
    await expect(cardStreamWrapper).toHaveAttribute('height', '427');
    await expect(cardStreamWrapper).toHaveAttribute('loading', 'lazy');

    const images = previewIframe.getByTestId('card-component-image');
    await expect(images).toHaveCount(4);
    const allImages = await images.all();
    await Promise.all(allImages.map((img) => expect(img).toBeVisible()));

    // Pick image media from the library, verify that the `public://` stream wrapper URI is rewritten to a
    // browser-accessible URL.
    await canvasEditor.exitPreview();
    await canvasEditor.openLayersPanel();
    await canvasEditor.openComponent('Card with stream wrapper image');
    await drupal.addMediaImage(
      '../../../fixtures/images/gracie-big.jpg',
      'A cute dog',
    );
    // Ensure the editor frame is updated first.
    const frame = page
      .locator(
        '[data-testid="canvas-editor-frame-scaling"] [data-canvas-swap-active="true"]',
      )
      .contentFrame();
    await page.waitForFunction(() => {
      const frame: HTMLIFrameElement = document.querySelector(
        '[data-testid="canvas-editor-frame-scaling"] [data-canvas-swap-active="true"]',
      );
      const img = frame?.contentWindow.document.querySelector(
        '.card--with-stream-wrapper-image img.card--image',
      );
      const imgSrc = img?.getAttribute('src');
      return imgSrc && imgSrc.includes('gracie');
    });
    frame
      .locator('.card--with-stream-wrapper-image img.card--image')
      .waitFor({ state: 'visible' });
    // Then also inspect the page preview.
    await canvasEditor.preview();
    const updatedPreviewIframe = page
      .locator('iframe[class^="_PagePreviewIframe"]')
      .contentFrame();
    const cardStreamWrapperUpdatedSrc = updatedPreviewIframe.locator(
      '.card--with-stream-wrapper-image img.card--image',
    );
    await cardStreamWrapper.scrollIntoViewIfNeeded();
    expect(await cardStreamWrapperUpdatedSrc.getAttribute('src'))
      // The stream wrapper URI must be rewritten to an actually resolvable URL.
      // @see tests/modules/canvas_test_sdc/components/card-with-stream-wrapper-image/card-with-stream-wrapper-image.twig
      .toMatch(/\/sites\/simpletest\/\d+\/files\/.*gracie-big.*\.jpg/);
  });
});
