/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/* globals browser */

let availablePatches;

const portToAddon = (function () {
  let port;

  function connect() {
    port = browser.runtime.connect({ name: "AboutCompatTab" });
    port.onMessage.addListener(onMessageFromAddon);
    port.onDisconnect.addListener(e => {
      port = undefined;
    });
  }

  connect();

  async function send(message) {
    if (port) {
      return port.postMessage(message);
    }
    return Promise.reject("background script port disconnected");
  }

  return { send };
})();

const $ = function (sel) {
  return document.querySelector(sel);
};

const DOMContentLoadedPromise = new Promise(resolve => {
  document.addEventListener(
    "DOMContentLoaded",
    () => {
      resolve();
    },
    { once: true }
  );
});

Promise.all([
  browser.runtime.sendMessage("getAllInterventions"),
  DOMContentLoadedPromise,
]).then(([info]) => {
  document.body.addEventListener("click", async evt => {
    const ele = evt.target;
    if (ele.nodeName === "BUTTON") {
      const row = ele.closest("[data-id]");
      if (row) {
        evt.preventDefault();
        ele.disabled = true;
        const id = row.getAttribute("data-id");
        try {
          await browser.runtime.sendMessage({ command: "toggle", id });
        } catch (_) {
          ele.disabled = false;
        }
      }
    } else if (ele.classList.contains("tab")) {
      document.querySelectorAll(".tab").forEach(tab => {
        tab.classList.remove("active");
      });
      ele.classList.add("active");
    }
  });

  availablePatches = info;
  redraw();
});

async function onMessageFromAddon(msg) {
  const alsoShowHidden = location.hash === "#all";

  await DOMContentLoadedPromise;

  if ("interventionsChanged" in msg) {
    redrawTable($("#interventions"), msg.interventionsChanged, alsoShowHidden);
  }

  if ("overridesChanged" in msg) {
    redrawTable($("#overrides"), msg.overridesChanged, alsoShowHidden);
  }

  if ("shimsChanged" in msg) {
    updateShimTables(msg.shimsChanged, alsoShowHidden);
  }

  const id = msg.toggling || msg.toggled;
  const button = $(`[data-id="${id}"] button`);
  if (!button) {
    return;
  }
  const active = msg.active;
  document.l10n.setAttributes(
    button,
    active ? "label-disable" : "label-enable"
  );
  button.disabled = !!msg.toggling;
}

function redraw() {
  if (!availablePatches) {
    return;
  }
  const { overrides, interventions, shims } = availablePatches;
  const alsoShowHidden = location.hash === "#all";
  redrawTable($("#overrides"), overrides, alsoShowHidden);
  redrawTable($("#interventions"), interventions, alsoShowHidden);
  updateShimTables(shims, alsoShowHidden);
}

function clearTableAndAddMessage(table, msgId) {
  table.querySelectorAll("tr").forEach(tr => {
    tr.remove();
  });

  const tr = document.createElement("tr");
  tr.className = "message";
  tr.id = msgId;

  const td = document.createElement("td");
  td.setAttribute("colspan", "3");
  document.l10n.setAttributes(td, msgId);
  tr.appendChild(td);

  table.appendChild(tr);
}

function hideMessagesOnTable(table) {
  table.querySelectorAll("tr.message").forEach(tr => {
    tr.remove();
  });
}

function updateShimTables(shimsChanged, alsoShowHidden) {
  const tables = document.querySelectorAll("table.shims");
  if (!tables.length) {
    return;
  }

  for (const { bug, disabledReason, hidden, id, name, type } of shimsChanged) {
    // if any shim is disabled by global pref, all of them are. just show the
    // "disabled in about:config" message on each shim table in that case.
    if (disabledReason === "globalPref") {
      for (const table of tables) {
        clearTableAndAddMessage(table, "text-disabled-in-about-config");
      }
      return;
    }

    // otherwise, find which table the shim belongs in. if there is none,
    // ignore the shim (we're not showing it on the UI for whatever reason).
    const table = document.querySelector(`table.shims#${type}`);
    if (!table) {
      continue;
    }

    // similarly, skip shims hidden from the UI (only for testing, etc).
    if (!alsoShowHidden && hidden) {
      continue;
    }

    // also, hide the shim if it is disabled because it is not meant for this
    // platform, release (etc) rather than being disabled by pref/about:compat
    const notApplicable =
      disabledReason &&
      disabledReason !== "pref" &&
      disabledReason !== "session";
    if (!alsoShowHidden && notApplicable) {
      continue;
    }

    // create an updated table-row for the shim
    const tr = document.createElement("tr");
    tr.setAttribute("data-id", id);

    let td = document.createElement("td");
    td.innerText = name;
    tr.appendChild(td);

    td = document.createElement("td");
    const a = document.createElement("a");
    a.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug}`;
    document.l10n.setAttributes(a, "label-more-information", { bug });
    a.target = "_blank";
    td.appendChild(a);
    tr.appendChild(td);

    td = document.createElement("td");
    tr.appendChild(td);
    const button = document.createElement("button");
    document.l10n.setAttributes(
      button,
      disabledReason ? "label-enable" : "label-disable"
    );
    td.appendChild(button);

    // is it already in the table?
    const row = table.querySelector(`tr[data-id="${id}"]`);
    if (row) {
      row.replaceWith(tr);
    } else {
      table.appendChild(tr);
    }
  }

  for (const table of tables) {
    if (!table.querySelector("tr:not(.message)")) {
      // no shims? then add a message that none are available for this platform/config
      clearTableAndAddMessage(table, `text-no-${table.id}`);
    } else {
      // otherwise hide any such message, since we have shims on the list
      hideMessagesOnTable(table);
    }
  }
}

function redrawTable(table, data, alsoShowHidden) {
  const df = document.createDocumentFragment();
  table.querySelectorAll("tr").forEach(tr => {
    tr.remove();
  });

  let noEntriesMessage;
  if (data === false) {
    noEntriesMessage = "text-disabled-in-about-config";
  } else if (data.length === 0) {
    noEntriesMessage = `text-no-${table.id}`;
  }

  if (noEntriesMessage) {
    const tr = document.createElement("tr");
    df.appendChild(tr);

    const td = document.createElement("td");
    td.setAttribute("colspan", "3");
    document.l10n.setAttributes(td, noEntriesMessage);
    tr.appendChild(td);

    table.appendChild(df);
    return;
  }

  for (const row of data) {
    if (row.hidden && !alsoShowHidden) {
      continue;
    }

    const tr = document.createElement("tr");
    tr.setAttribute("data-id", row.id);
    df.appendChild(tr);

    let td = document.createElement("td");
    td.innerText = row.domain;
    tr.appendChild(td);

    td = document.createElement("td");
    const a = document.createElement("a");
    const bug = row.bug;
    a.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug}`;
    document.l10n.setAttributes(a, "label-more-information", { bug });
    a.target = "_blank";
    td.appendChild(a);
    tr.appendChild(td);

    td = document.createElement("td");
    tr.appendChild(td);
    const button = document.createElement("button");
    document.l10n.setAttributes(
      button,
      row.active ? "label-disable" : "label-enable"
    );
    td.appendChild(button);
  }
  table.appendChild(df);
}

window.onhashchange = redraw;
