// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {test, type Page} from '@playwright/test';
import {PerfettoTestHelper} from './perfetto_ui_test_helper';
import {
  EVENT_LATENCY_TRACK,
  SCROLL_TIMELINE_TRACK,
  SCROLL_TIMELINE_V4_TRACK,
  type TrackSpec,
} from '../plugins/org.chromium.ChromeScrollJank/tracks';
import {Time, type time} from '../base/time';

let pth: PerfettoTestHelper;
let page: Page;

test.beforeEach(async ({browser}, _testInfo) => {
  page = await browser.newPage();
  pth = new PerfettoTestHelper(page);
  await pth.openTraceFile('chrome/scroll_m144.pftrace', {
    enablePlugins: 'org.chromium.ChromeScrollJank',
  });
});

test.afterEach(async () => await page.close());

async function selectPluginSlice(
  trackSpec: TrackSpec,
  name: string,
  ts: time,
): Promise<void> {
  // We cannot use `PerfettoTestHelper.searchSlice()` because omnibox search
  // results don't include track events (slices) created by plugins.
  //
  // We could theoretically emulate clicking on the relevant plugin slice with
  // the following code:
  //
  // ```
  // const coords = assertExists(await trk.boundingBox());
  // await page.mouse.click(coords.x + 823, coords.y + 120);
  // ```
  //
  // but it would break easily, in which case updating the coordinates manually
  // would be very tedious. So we do the following instead.
  await page.evaluate(
    async ({tableName, trackUri, name, ts}) => {
      const trace = self.app.trace!;

      // Step 1: Find the ID of the slice.
      const result = await trace.engine.query(`
        SELECT id
        FROM ${tableName}
        WHERE name = '${name}' AND ts = ${ts}
      `);
      if (result.numRows() > 1) {
        throw new Error('Multiple slices match');
      }
      const id = result.firstRow({id: Number()}).id;

      // Step 2: Select the slice.
      trace.selection.selectTrackEvent(trackUri, id);
    },
    {tableName: trackSpec.tableName, trackUri: trackSpec.uri, name, ts},
  );
}

test('event_latency_track', async () => {
  const trk = pth.locateTrack(
    'Chrome Scroll Jank/Chrome Scroll Input Latencies',
  );
  await trk.scrollIntoViewIfNeeded();
  await pth.waitForIdleAndScreenshot('track.png', {
    locator: page.locator('.pf-timeline-page__timeline'),
  });

  // Select the 'RendererCompositorQueueingDelay' stage within the first
  // janky EventLatency.
  await selectPluginSlice(
    EVENT_LATENCY_TRACK,
    'RendererCompositorQueueingDelay',
    Time.fromRaw(16784825798017n),
  );
  await trk.scrollIntoViewIfNeeded();
  await pth.waitForIdleAndScreenshot('details_panel_stage.png', {
    locator: page.locator('.pf-timeline-page__timeline'),
  });

  // Jump from the stage to the first janky EventLatency.
  await page.getByText('Parent EventLatency').click();
  await pth.waitForIdleAndScreenshot('details_panel_event_latency.png', {
    locator: page.locator('.pf-timeline-page__timeline'),
  });

  // Jump from the first janky EventLatency to the corresponding scroll update.
  await page.getByText('Corresponding scroll update').click();
  await pth.waitForIdleAndScreenshot(
    'details_panel_link_to_scroll_timeline.png',
    {locator: page.locator('.pf-timeline-page__timeline')},
  );

  // Go back to the first janky EventLatency and then jump the corresponding frame.
  await selectPluginSlice(
    EVENT_LATENCY_TRACK,
    'Janky EventLatency',
    Time.fromRaw(16784822412017n),
  );
  await page
    .getByText('Frame where this was the first presented EventLatency')
    .click();
  await pth.waitForIdleAndScreenshot(
    'details_panel_link_to_scroll_timeline_v4.png',
    {locator: page.locator('.pf-timeline-page__timeline')},
  );
});

test('scroll_timeline_track', async () => {
  const trk = pth.locateTrack('Chrome Scroll Jank/Chrome Scroll Timeline');
  await trk.scrollIntoViewIfNeeded();
  await pth.waitForIdleAndScreenshot('track.png', {
    locator: page.locator('.pf-timeline-page__timeline'),
  });

  // Select the 'GenerationToBrowserMain' stage within the first inertial scroll
  // update.
  await selectPluginSlice(
    SCROLL_TIMELINE_TRACK,
    'GenerationToBrowserMain',
    Time.fromRaw(16784307235017n),
  );
  await trk.scrollIntoViewIfNeeded();
  await pth.waitForIdleAndScreenshot('details_panel_stage.png', {
    locator: page.locator('.pf-timeline-page__timeline'),
  });

  // Jump from the stage to the first inertial scroll update.
  await page.getByText('Parent scroll update').click();
  await pth.waitForIdleAndScreenshot('details_panel_scroll_update.png', {
    locator: page.locator('.pf-timeline-page__timeline'),
  });

  // Jump from the first inertial scroll update to the corresponding EventLatency.
  await page.getByText('Corresponding EventLatency').click();
  await pth.waitForIdleAndScreenshot(
    'details_panel_link_to_event_latency.png',
    {
      locator: page.locator('.pf-timeline-page__timeline'),
    },
  );

  // Go back to the first inertial scroll update and then jump the corresponding
  // frame.
  await selectPluginSlice(
    SCROLL_TIMELINE_TRACK,
    'Inertial Scroll Update',
    Time.fromRaw(16784307235017n),
  );
  await page
    .getByText('Frame where this was the first presented scroll update')
    .click();
  await pth.waitForIdleAndScreenshot(
    'details_panel_link_to_scroll_timeline_v4.png',
    {locator: page.locator('.pf-timeline-page__timeline')},
  );
});

test('scroll_timeline_v4_track', async () => {
  const trk = pth.locateTrack('Chrome Scroll Jank/Chrome Scroll Timeline v4');
  await trk.scrollIntoViewIfNeeded();
  await pth.waitForIdleAndScreenshot('scroll_timeline_v4_track.png', {
    locator: page.locator('.pf-timeline-page__timeline'),
  });

  // Select the 'Real scroll update input generation' stage within the second
  // janky frame.
  await selectPluginSlice(
    SCROLL_TIMELINE_V4_TRACK,
    'Real scroll update input generation',
    Time.fromRaw(16784838286017n),
  );
  await trk.scrollIntoViewIfNeeded();
  await pth.waitForIdleAndScreenshot(
    'scroll_timeline_v4_details_panel_stage.png',
    {locator: page.locator('.pf-timeline-page__timeline')},
  );

  // Jump from the stage to the second janky frame.
  await page.getByText('Parent frame').click();
  await pth.waitForIdleAndScreenshot(
    'scroll_timeline_v4_details_panel_frame.png',
    {locator: page.locator('.pf-timeline-page__timeline')},
  );

  // Jump from the second janky frame to the corresponding EventLatency.
  await page.getByText('First EventLatency in this frame').click();
  await pth.waitForIdleAndScreenshot(
    'scroll_timeline_v4_details_panel_link_to_event_latency.png',
    {locator: page.locator('.pf-timeline-page__timeline')},
  );

  // Go back to the second janky frame and then jump the corresponding frame.
  await selectPluginSlice(
    SCROLL_TIMELINE_V4_TRACK,
    'Janky Frame',
    Time.fromRaw(16784838286017n),
  );
  await page.getByText('First scroll update in this frame').click();
  await pth.waitForIdleAndScreenshot(
    'scroll_timeline_v4_details_panel_link_to_scroll_timeline.png',
    {locator: page.locator('.pf-timeline-page__timeline')},
  );
});
