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

"use strict";

// https rather than chrome to improve coverage
const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html";
const SIDEBAR_PREF = "devtools.styleeditor.showAtRulesSidebar";

const RESIZE_W = 300;
const RESIZE_H = 450;
const LABELS = [
  "not all",
  "all",
  "(max-width: 550px)",
  "(min-height: 300px) and (max-height: 320px)",
  "(max-width: 750px)",
  "",
  "print",
];
const LINE_NOS = [1, 7, 19, 25, 31, 34, 39];
const NEW_RULE = `
  @media (max-width: 750px) {
    div {
      color: blue;
      @layer {
        border-color: tomato;
      }
    }

    @media print {
      body {
        filter: grayscale(100%);
      }
    }
  }`;

waitForExplicitFinish();

add_task(async function () {
  // Enable @property rules
  await pushPref("layout.css.properties-and-values.enabled", true);

  const { ui } = await openStyleEditorForURL(TESTCASE_URI);

  is(ui.editors.length, 4, "correct number of editors");

  info("Test first plain css editor");
  const plainEditor = ui.editors[0];
  await openEditor(plainEditor);
  testPlainEditor(plainEditor);

  info("Test editor for inline sheet with @media rules");
  const inlineMediaEditor = ui.editors[3];
  await openEditor(inlineMediaEditor);
  await testInlineMediaEditor(ui, inlineMediaEditor);

  info("Test editor with @media rules");
  const mediaEditor = ui.editors[1];
  await openEditor(mediaEditor);
  await testMediaEditor(ui, mediaEditor);

  info("Test that sidebar hides when flipping pref");
  await testShowHide(ui, mediaEditor);

  info("Test adding a rule updates the list");
  await testMediaRuleAdded(ui, mediaEditor);

  info("Test resizing and seeing @media matching state change");
  const originalWidth = window.outerWidth;
  const originalHeight = window.outerHeight;

  const onMatchesChange = ui.once("at-rules-list-changed");
  window.resizeTo(RESIZE_W, RESIZE_H);
  await onMatchesChange;

  testMediaMatchChanged(mediaEditor);

  window.resizeTo(originalWidth, originalHeight);
});

function testPlainEditor(editor) {
  const sidebar = editor.details.querySelector(".stylesheet-sidebar");
  is(sidebar.hidden, true, "sidebar is hidden on editor without @media");
}

async function testInlineMediaEditor(ui, editor) {
  const sidebar = editor.details.querySelector(".stylesheet-sidebar");
  is(sidebar.hidden, false, "sidebar is showing on editor with @media");

  const entries = sidebar.querySelectorAll(".at-rule-label");
  is(entries.length, 7, "7 at-rules displayed in sidebar");

  await testRule({
    ui,
    editor,
    rule: entries[0],
    conditionText: "screen",
    matches: true,
    line: 2,
    type: "media",
  });

  await testRule({
    ui,
    editor,
    rule: entries[1],
    conditionText: "(display: flex)",
    line: 7,
    type: "support",
  });

  await testRule({
    ui,
    editor,
    rule: entries[2],
    conditionText: "(1px < height < 10000px)",
    matches: true,
    line: 8,
    type: "media",
  });

  await testRule({
    ui,
    editor,
    rule: entries[3],
    line: 16,
    type: "layer",
    layerName: "myLayer",
  });

  await testRule({
    ui,
    editor,
    rule: entries[4],
    conditionText: "(min-width: 1px)",
    line: 17,
    type: "container",
  });

  await testRule({
    ui,
    editor,
    rule: entries[5],
    conditionText: "selector(&)",
    line: 21,
    type: "support",
  });

  await testRule({
    ui,
    editor,
    rule: entries[6],
    line: 30,
    type: "property",
    propertyName: "--my-property",
  });
}

async function testMediaEditor(ui, editor) {
  const sidebar = editor.details.querySelector(".stylesheet-sidebar");
  is(sidebar.hidden, false, "sidebar is showing on editor with @media");

  const entries = [...sidebar.querySelectorAll(".at-rule-label")];
  is(entries.length, 4, "four @media rules displayed in sidebar");

  await testRule({
    ui,
    editor,
    rule: entries[0],
    conditionText: LABELS[0],
    matches: false,
    line: LINE_NOS[0],
  });
  await testRule({
    ui,
    editor,
    rule: entries[1],
    conditionText: LABELS[1],
    matches: true,
    line: LINE_NOS[1],
  });
  await testRule({
    ui,
    editor,
    rule: entries[2],
    conditionText: LABELS[2],
    matches: false,
    line: LINE_NOS[2],
  });
  await testRule({
    ui,
    editor,
    rule: entries[3],
    conditionText: LABELS[3],
    matches: false,
    line: LINE_NOS[3],
  });
}

function testMediaMatchChanged(editor) {
  const sidebar = editor.details.querySelector(".stylesheet-sidebar");

  const cond = sidebar.querySelectorAll(".at-rule-condition")[2];
  is(
    cond.textContent,
    "(max-width: 550px)",
    "third rule condition text is correct"
  );
  ok(
    !cond.classList.contains("media-condition-unmatched"),
    "media rule is now matched after resizing"
  );
}

async function testShowHide(ui, editor) {
  let sidebarChange = ui.once("at-rules-list-changed");
  Services.prefs.setBoolPref(SIDEBAR_PREF, false);
  await sidebarChange;

  const sidebar = editor.details.querySelector(".stylesheet-sidebar");
  is(sidebar.hidden, true, "sidebar is hidden after flipping pref");

  sidebarChange = ui.once("at-rules-list-changed");
  Services.prefs.clearUserPref(SIDEBAR_PREF);
  await sidebarChange;

  is(sidebar.hidden, false, "sidebar is showing after flipping pref back");
}

async function testMediaRuleAdded(ui, editor) {
  await editor.getSourceEditor();
  const sidebar = editor.details.querySelector(".stylesheet-sidebar");
  is(
    sidebar.querySelectorAll(".at-rule-label").length,
    4,
    "4 @media rules after changing text"
  );

  let text = editor.sourceEditor.getText();
  text += NEW_RULE;

  const listChange = ui.once("at-rules-list-changed");
  editor.sourceEditor.setText(text);
  await listChange;

  const entries = [...sidebar.querySelectorAll(".at-rule-label")];
  is(entries.length, 7, "7 @media rules after changing text");

  await testRule({
    ui,
    editor,
    rule: entries[4],
    conditionText: LABELS[4],
    matches: false,
    line: LINE_NOS[4],
  });

  await testRule({
    ui,
    editor,
    rule: entries[5],
    type: "layer",
    conditionText: LABELS[5],
    line: LINE_NOS[5],
  });

  await testRule({
    ui,
    editor,
    rule: entries[6],
    conditionText: LABELS[6],
    matches: false,
    line: LINE_NOS[6],
  });
}

/**
 * Run assertion on given rule
 *
 * @param {Object} options
 * @param {StyleEditorUI} options.ui
 * @param {StyleSheetEditor} options.editor: The editor the rule is displayed in
 * @param {Element} options.rule: The rule element in the media sidebar
 * @param {String} options.conditionText: at-rule condition text (for @media, @container, @support)
 * @param {Boolean} options.matches: Whether or not the document matches the rule
 * @param {String} options.layerName: Optional name of the @layer
 * @param {String} options.propertyName: Name of the @property if type is "property"
 * @param {Number} options.line: Line of the rule
 * @param {String} options.type: The type of the rule (container, layer, media, support, property ).
 *                               Defaults to "media".
 */
async function testRule({
  ui,
  editor,
  rule,
  conditionText = "",
  matches,
  layerName,
  propertyName,
  line,
  type = "media",
}) {
  const atTypeEl = rule.querySelector(".at-rule-type");
  let name;
  if (type === "layer") {
    name = layerName;
  } else if (type === "property") {
    name = propertyName;
  }
  is(
    atTypeEl.textContent,
    `@${type}\u00A0${name ? `${name}\u00A0` : ""}`,
    "label for at-rule type is correct"
  );

  const cond = rule.querySelector(".at-rule-condition");
  is(
    cond.textContent,
    conditionText,
    "condition label is correct for " + conditionText
  );

  if (type == "media") {
    const matched = !cond.classList.contains("media-condition-unmatched");
    ok(
      matches ? matched : !matched,
      "media rule is " + (matches ? "matched" : "unmatched")
    );
  }

  const ruleLine = rule.querySelector(".at-rule-line");
  is(ruleLine.textContent, ":" + line, "correct line number shown");

  info(
    "Check that clicking on the rule jumps to the expected position in the stylesheet"
  );
  rule.click();
  await waitFor(
    () =>
      ui.selectedEditor == editor &&
      editor.sourceEditor.getCursor().line == line - 1
  );
  ok(true, "Jumped to the expected location");
}

/* Helpers */

function openEditor(editor) {
  getLinkFor(editor).click();

  return editor.getSourceEditor();
}

function getLinkFor(editor) {
  return editor.summary.querySelector(".stylesheet-name");
}
