(function ($) {
  "use strict";

  var noteEntries = [];
  var observerTimer = null;
  var layoutTimer = null;
  var contextMenu = null;

  function i18n(key, fallback) {
    var data = window.polangerShieldRuntime || {};
    if (data.i18n && data.i18n[key]) {
      return data.i18n[key];
    }
    return fallback;
  }

  function escapeHtml(value) {
    return $("<div>").text(value || "").html();
  }

  function getRules() {
    var data = window.polangerShieldRuntime || {};
    return Array.isArray(data.rules) ? data.rules : [];
  }

  function getRuntime() {
    return window.polangerShieldRuntime || {};
  }

  function clamp(value, min, max) {
    return Math.min(Math.max(value, min), max);
  }

  function showToast(message, isError) {
    var toast = $('<div class="polanger-shield-toast" />').text(message || "");
    if (isError) {
      toast.addClass("is-error");
    }
    $("body").append(toast);
    setTimeout(function () {
      toast.addClass("is-visible");
    }, 10);
    setTimeout(function () {
      toast.removeClass("is-visible");
      setTimeout(function () {
        toast.remove();
      }, 220);
    }, 2200);
  }

  function getRect(el) {
    if (!el || typeof el.getBoundingClientRect !== "function") {
      return null;
    }

    var rect = el.getBoundingClientRect();
    return {
      left: rect.left,
      top: rect.top,
      right: rect.right,
      bottom: rect.bottom,
      width: rect.width,
      height: rect.height
    };
  }

  function expandRect(rect, padding) {
    if (!rect) {
      return null;
    }

    return {
      left: rect.left - padding,
      top: rect.top - padding,
      right: rect.right + padding,
      bottom: rect.bottom + padding
    };
  }

  function rectsOverlap(a, b) {
    if (!a || !b) {
      return false;
    }

    return !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom);
  }

  function scheduleLayoutRefresh() {
    clearTimeout(layoutTimer);
    layoutTimer = setTimeout(syncNotePositions, 30);
  }

  function hideContextMenu() {
    if (contextMenu) {
      contextMenu.remove();
      contextMenu = null;
    }
  }

  function positionContextMenu(event) {
    if (!contextMenu || !event) {
      return;
    }

    var margin = 10;
    var menuWidth = contextMenu.outerWidth() || 190;
    var menuHeight = contextMenu.outerHeight() || 120;
    var left = clamp(event.clientX + 10, margin, window.innerWidth - menuWidth - margin);
    var top = clamp(event.clientY + 10, margin, window.innerHeight - menuHeight - margin);

    contextMenu.css({
      left: Math.round(left) + "px",
      top: Math.round(top) + "px"
    });
  }

  function removeNoteEntry(entry) {
    if (!entry) {
      return;
    }

    if (entry.marker) {
      entry.marker.remove();
    }

    if (entry.popover) {
      entry.popover.remove();
    }

    if (entry.target && entry.target.__polangerShieldNoteRuleIds) {
      delete entry.target.__polangerShieldNoteRuleIds[entry.ruleId];
    }
  }

  function cleanupDetachedEntries() {
    noteEntries = noteEntries.filter(function (entry) {
      if (!entry || !entry.target || !entry.target.isConnected) {
        removeNoteEntry(entry);
        return false;
      }

      return true;
    });
  }

  function removeRuleNoteEntries(ruleId) {
    var targetRuleId = parseInt(ruleId, 10);

    noteEntries = noteEntries.filter(function (entry) {
      if (parseInt(entry.ruleId, 10) === targetRuleId) {
        removeNoteEntry(entry);
        return false;
      }

      return true;
    });
  }

  function hideAllPopovers(exceptEntry) {
    noteEntries.forEach(function (entry) {
      if (!entry || !entry.popover || entry === exceptEntry) {
        return;
      }
      entry.popover.classList.remove("is-open");
      entry.isOpen = false;
    });
  }

  function openContextMenu(rule, event) {
    var runtime = getRuntime();
    if (!runtime.canManage) {
      return;
    }

    hideContextMenu();
    contextMenu = $('<div class="polanger-shield-context-menu" />');
    var disableBtn = $('<button type="button" />').text(i18n("disableRule", "Disable Rule"));
    var editLink = $('<a />').attr("href", runtime.manageUrl + "&rule_id=" + rule.id).text(i18n("editRule", "Open Rule"));
    var viewLink = $('<a />').attr("href", runtime.manageUrl).text(i18n("viewDetails", "Manage Rules"));

    disableBtn.on("click", function () {
      $.post(runtime.ajaxUrl, {
        action: "polanger_shield_disable_rule",
        nonce: runtime.nonce,
        rule_id: rule.id
      }).done(function (response) {
        if (!response || !response.success) {
          showToast((response && response.data && response.data.message) || i18n("actionFailed", "Action failed."), true);
          return;
        }

        showToast((response.data && response.data.message) || i18n("ruleDisabled", "Rule disabled."));
        hideContextMenu();
        removeRuleNoteEntries(rule.id);
      }).fail(function () {
        showToast(i18n("actionFailed", "Action failed."), true);
      });
    });

    contextMenu.append(disableBtn, editLink, viewLink);
    $("body").append(contextMenu);
    positionContextMenu(event);
  }

  function isValidSelector(selector) {
    if (!selector) {
      return false;
    }

    try {
      document.querySelector(selector);
      return true;
    } catch (error) {
      return false;
    }
  }

  function installEarlyHideStyles() {
    var rules = getRules();
    if (!rules.length) {
      return;
    }

    var cssRules = [];
    rules.forEach(function (rule) {
      if (!rule || rule.ruleType !== "element_hide" || !rule.selector) {
        return;
      }
      if (!isValidSelector(rule.selector)) {
        return;
      }
      cssRules.push(rule.selector + "{display:none !important;}");
    });

    if (!cssRules.length) {
      return;
    }

    var style = document.createElement("style");
    style.setAttribute("id", "polanger-shield-early-hide");
    style.textContent = cssRules.join("\n");
    document.head.appendChild(style);
  }

  function applyHideRule(rule) {
    if (!rule.selector) {
      return;
    }

    try {
      var nodes = document.querySelectorAll(rule.selector);
      nodes.forEach(function (node) {
        node.classList.add("polanger-shield-hidden-element");
      });
    } catch (error) {
      // Invalid selectors are ignored.
    }
  }

  function getTargetStackIndex(entry) {
    var siblings = noteEntries.filter(function (candidate) {
      return candidate && candidate.target === entry.target;
    });

    return Math.max(0, siblings.indexOf(entry));
  }

  function positionPopover(entry, markerRect) {
    if (!entry || !entry.popover || !markerRect) {
      return;
    }

    var popover = entry.popover;
    popover.style.display = "block";

    var margin = 10;
    var width = popover.offsetWidth || 320;
    var height = popover.offsetHeight || 140;
    var left = markerRect.right + 12;
    var top = markerRect.top - 4;
    var triggerRect = getRect(document.getElementById("polanger-shield-trigger"));

    if (left + width > window.innerWidth - margin) {
      left = markerRect.left - width - 12;
    }

    if (left < margin) {
      left = margin;
    }

    if (top + height > window.innerHeight - margin) {
      top = window.innerHeight - height - margin;
    }

    if (top < margin) {
      top = margin;
    }

    var popoverRect = {
      left: left,
      top: top,
      right: left + width,
      bottom: top + height
    };

    if (rectsOverlap(expandRect(popoverRect, 8), expandRect(triggerRect, 8))) {
      var belowTop = clamp(markerRect.bottom + 12, margin, window.innerHeight - height - margin);
      var aboveTop = clamp(markerRect.top - height - 12, margin, window.innerHeight - height - margin);
      top = belowTop;

      if (rectsOverlap(
        expandRect({ left: left, top: top, right: left + width, bottom: top + height }, 8),
        expandRect(triggerRect, 8)
      )) {
        top = aboveTop;
      }
    }

    popover.style.left = Math.round(left) + "px";
    popover.style.top = Math.round(top) + "px";
  }

  function positionNoteEntry(entry) {
    if (!entry || !entry.target || !entry.marker || !entry.popover || !entry.target.isConnected) {
      removeNoteEntry(entry);
      return false;
    }

    var rect = getRect(entry.target);
    if (!rect || rect.width < 12 || rect.height < 12 || rect.bottom < 0 || rect.right < 0 || rect.top > window.innerHeight || rect.left > window.innerWidth) {
      entry.marker.style.display = "none";
      entry.popover.style.display = "none";
      return true;
    }

    entry.marker.style.display = "inline-flex";
    entry.popover.style.display = "block";

    var markerWidth = entry.marker.offsetWidth || 34;
    var markerHeight = entry.marker.offsetHeight || 34;
    var margin = 8;
    var topPadding = 6;
    var stackOffset = getTargetStackIndex(entry) * (markerHeight + 8);
    var left = clamp(rect.right - markerWidth - 8, margin, window.innerWidth - markerWidth - margin);
    var top = clamp(rect.top + 8 + stackOffset, topPadding, window.innerHeight - markerHeight - margin);
    var triggerRect = getRect(document.getElementById("polanger-shield-trigger"));
    var markerRect = {
      left: left,
      top: top,
      right: left + markerWidth,
      bottom: top + markerHeight
    };

    if (rectsOverlap(expandRect(markerRect, 8), expandRect(triggerRect, 10))) {
      var alternateLeft = clamp(rect.left + 8, margin, window.innerWidth - markerWidth - margin);
      var alternateTop = clamp(rect.bottom - markerHeight - 8 - stackOffset, topPadding, window.innerHeight - markerHeight - margin);
      var alternateRect = {
        left: alternateLeft,
        top: alternateTop,
        right: alternateLeft + markerWidth,
        bottom: alternateTop + markerHeight
      };

      if (!rectsOverlap(expandRect(alternateRect, 8), expandRect(triggerRect, 10))) {
        left = alternateLeft;
        top = alternateTop;
        markerRect = alternateRect;
      }
    }

    entry.marker.style.left = Math.round(left) + "px";
    entry.marker.style.top = Math.round(top) + "px";
    positionPopover(entry, markerRect);
    return true;
  }

  function syncNotePositions() {
    cleanupDetachedEntries();

    noteEntries = noteEntries.filter(function (entry) {
      return positionNoteEntry(entry);
    });
  }

  function buildPopoverHtml(rule) {
    return (
      '<div class="polanger-shield-note-popover__header">' +
        '<span class="polanger-shield-note-popover__badge">' +
          '<span class="dashicons dashicons-edit-page" aria-hidden="true"></span>' +
        "</span>" +
        "<div>" +
          '<span class="polanger-shield-note-popover__title">' + escapeHtml(i18n("noteTitle", "Internal Note")) + "</span>" +
          '<span class="polanger-shield-note-popover__hint">' + escapeHtml(i18n("noteHint", "Visible only to selected administrators.")) + "</span>" +
        "</div>" +
      "</div>" +
      '<div class="polanger-shield-note-popover__body">' + rule.note + "</div>"
    );
  }

  function attachNote(node, rule) {
    if (!node || !rule || !rule.id || !rule.note) {
      return;
    }

    if (!node.__polangerShieldNoteRuleIds) {
      node.__polangerShieldNoteRuleIds = {};
    }

    if (node.__polangerShieldNoteRuleIds[rule.id]) {
      return;
    }

    var marker = document.createElement("button");
    marker.type = "button";
    marker.className = "polanger-shield-note-marker";
    marker.setAttribute("data-rule-id", String(rule.id));
    marker.innerHTML = '<span class="dashicons dashicons-edit-page" aria-hidden="true"></span>';
    marker.setAttribute("aria-label", i18n("openNote", "Open note"));
    marker.setAttribute("title", i18n("noteLabel", "Note"));

    var popover = document.createElement("div");
    popover.className = "polanger-shield-note-popover";
    popover.setAttribute("data-rule-id", String(rule.id));
    popover.innerHTML = buildPopoverHtml(rule);

    var entry = {
      ruleId: rule.id,
      target: node,
      marker: marker,
      popover: popover,
      isOpen: false
    };

    marker.addEventListener("click", function (event) {
      event.preventDefault();
      event.stopPropagation();
      hideContextMenu();
      if (!entry.isOpen) {
        hideAllPopovers(entry);
      }
      entry.isOpen = !entry.isOpen;
      popover.classList.toggle("is-open", entry.isOpen);
      scheduleLayoutRefresh();
    });

    marker.addEventListener("contextmenu", function (event) {
      event.preventDefault();
      event.stopPropagation();
      hideAllPopovers();
      openContextMenu(rule, event);
    });

    popover.addEventListener("contextmenu", function (event) {
      event.preventDefault();
      event.stopPropagation();
      openContextMenu(rule, event);
    });

    document.body.appendChild(marker);
    document.body.appendChild(popover);
    node.__polangerShieldNoteRuleIds[rule.id] = true;
    noteEntries.push(entry);
    positionNoteEntry(entry);
  }

  function applyNoteRule(rule) {
    if (!rule.selector) {
      return;
    }

    try {
      var nodes = document.querySelectorAll(rule.selector);
      nodes.forEach(function (node) {
        attachNote(node, rule);
      });
    } catch (error) {
      // Ignore invalid selectors.
    }
  }

  function applyRules() {
    var rules = getRules();
    if (!rules.length) {
      return;
    }

    rules.forEach(function (rule) {
      if (!rule || !rule.ruleType) {
        return;
      }
      if (rule.ruleType === "element_hide") {
        applyHideRule(rule);
      } else if (rule.ruleType === "note") {
        applyNoteRule(rule);
      }
    });

    scheduleLayoutRefresh();
  }

  function setupObserver() {
    if (!window.MutationObserver || !document.body) {
      return;
    }

    var observer = new MutationObserver(function () {
      clearTimeout(observerTimer);
      observerTimer = setTimeout(function () {
        applyRules();
        scheduleLayoutRefresh();
      }, 260);
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true
    });
  }

  $(function () {
    $(document).on("click", function (event) {
      var $target = $(event.target);
      if (!$target.closest(".polanger-shield-note-marker, .polanger-shield-note-popover, .polanger-shield-context-menu").length) {
        hideAllPopovers();
        hideContextMenu();
      }
    });

    $(window).on("scroll resize", function () {
      hideContextMenu();
      scheduleLayoutRefresh();
    });

    $(window).on("keydown", function (event) {
      if (event.key === "Escape" || event.keyCode === 27) {
        hideAllPopovers();
        hideContextMenu();
      }
    });

    applyRules();
    setupObserver();
  });

  installEarlyHideStyles();
})(jQuery);
