/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

// Test for following timeline tick items.
// * animation list header elements existence
// * tick labels elements existence
// * count and text of tick label elements changing by the sidebar width

const TimeScale = require("resource://devtools/client/inspector/animation/utils/timescale.js");
const {
  findOptimalTimeInterval,
} = require("resource://devtools/client/inspector/animation/utils/utils.js");

// Should be kept in sync with TIME_GRADUATION_MIN_SPACING in
// AnimationTimeTickList component.
const TIME_GRADUATION_MIN_SPACING = 40;

add_task(async function () {
  await pushPref("devtools.inspector.three-pane-enabled", false);
  await addTab(URL_ROOT + "doc_simple_animation.html");
  await removeAnimatedElementsExcept([".end-delay", ".negative-delay"]);
  const { animationInspector, inspector, panel } =
    await openAnimationInspector();
  const timeScale = new TimeScale(animationInspector.state.animations);

  info("Checking animation list header element existence");
  const listContainerEl = panel.querySelector(".animation-list-container");
  const listHeaderEl = listContainerEl.querySelector(".devtools-toolbar");
  ok(
    listHeaderEl,
    "The header element should be in animation list container element"
  );

  info("Checking time tick item elements existence");
  await assertTickLabels(timeScale, listContainerEl);
  const timelineTickItemLength =
    listContainerEl.querySelectorAll(".tick-label").length;

  info("Checking timeline tick item elements after enlarge sidebar width");
  await setSidebarWidth("100%", inspector);
  await assertTickLabels(timeScale, listContainerEl);
  Assert.less(
    timelineTickItemLength,
    listContainerEl.querySelectorAll(".tick-label").length,
    "The timeline tick item elements should increase"
  );
});

/**
 * Assert tick label's position and label.
 *
 * @param {TimeScale} - timeScale
 * @param {Element} - listContainerEl
 */
async function assertTickLabels(timeScale, listContainerEl) {
  const timelineTickListEl = listContainerEl.querySelector(".tick-labels");
  ok(
    timelineTickListEl,
    "The animation timeline tick list element should be in header"
  );

  const width = timelineTickListEl.offsetWidth;
  const animationDuration = timeScale.getDuration();
  const minTimeInterval =
    (TIME_GRADUATION_MIN_SPACING * animationDuration) / width;
  const interval = findOptimalTimeInterval(minTimeInterval);
  const shiftWidth = timeScale.zeroPositionTime % interval;
  const expectedTickItem =
    Math.ceil(animationDuration / interval) + (shiftWidth !== 0 ? 1 : 0);

  await waitUntil(
    () =>
      timelineTickListEl.querySelectorAll(".tick-label").length ===
      expectedTickItem
  );
  ok(true, "The expected number of timeline ticks were found");

  const timelineTickItemEls =
    timelineTickListEl.querySelectorAll(".tick-label");

  info("Make sure graduations are evenly distributed and show the right times");
  for (const [index, tickEl] of timelineTickItemEls.entries()) {
    const left = parseFloat(tickEl.style.marginInlineStart);
    let expectedPos =
      (((index - 1) * interval + shiftWidth) / animationDuration) * 100;
    if (shiftWidth !== 0 && index === 0) {
      expectedPos = 0;
    }
    is(
      Math.round(left),
      Math.round(expectedPos),
      `Graduation ${index} is positioned correctly`
    );

    // Note that the distancetoRelativeTime and formatTime functions are tested
    // separately in xpcshell test test_timeScale.js, so we assume that they
    // work here.
    const formattedTime = timeScale.formatTime(
      timeScale.distanceToRelativeTime(expectedPos, width)
    );
    is(
      tickEl.textContent,
      formattedTime,
      `Graduation ${index} has the right text content`
    );
  }
}
