/**
 * LMN Site Monitor — admin-theme.js
 * v.0.7.0
 *
 * - Theme toggle (light / dark / system) persisted in localStorage
 * - Drag-and-drop sortable cards (native HTML5, no external libs)
 *   · Pinned cards are excluded from sorting
 *   · Order persisted via AJAX (wp_ajax_lmnsimo_save_order)
 * - Pin toggle via AJAX (wp_ajax_lmnsimo_toggle_pin)
 * - WHOIS / DNS modal via AJAX (wp_ajax_lmnsimo_whois)
 *
 * @package LMN_Site_Monitor
 */

(function () {
  "use strict";

  const cfg = window.LM_MONITOR_SETTINGS || {
    defaultTheme: "system",
    useLocalFont: 0,
    fontsBaseUrl: "",
    ajaxUrl: "",
    nonceSaveOrder: "",
    nonceTogglePin: "",
    nonceWhois: "",
    i18n: {
      pinned: "Pinned",
      pin: "Pin",
      unpin: "Unpin",
      loading: "Loading…",
      error: "Error retrieving WHOIS data.",
      whoisTitle: "WHOIS / DNS",
      close: "Close",
      noData: "No data available.",
    },
  };

  /* ============================================================
	   THEME
	   ============================================================ */

  const ROOT_SEL = ".lm-scope";

  function applyTheme(mode) {
    document.querySelectorAll(ROOT_SEL).forEach(function (el) {
      el.classList.remove("lm-dark", "lm-light");
      if (mode === "dark") {
        el.classList.add("lm-dark");
      }
      if (mode === "light") {
        el.classList.add("lm-light");
      }
    });
  }

  function sysPrefersDark() {
    return (
      window.matchMedia &&
      window.matchMedia("(prefers-color-scheme: dark)").matches
    );
  }

  function resolveMode(pref) {
    if (pref === "system" || !pref) {
      return sysPrefersDark() ? "dark" : "light";
    }
    return pref;
  }

  // Initial theme.
  var savedTheme =
    localStorage.getItem("lmSiteMonitorTheme") || cfg.defaultTheme || "system";
  applyTheme(resolveMode(savedTheme));

  // Listen for OS preference changes.
  if (window.matchMedia) {
    window
      .matchMedia("(prefers-color-scheme: dark)")
      .addEventListener("change", function () {
        if (!localStorage.getItem("lmSiteMonitorTheme")) {
          applyTheme(resolveMode(cfg.defaultTheme || "system"));
        }
      });
  }

  // Theme toggle buttons.
  document.addEventListener("click", function (e) {
    var btn = e.target.closest(".lm-theme-btn");
    if (!btn) {
      return;
    }
    var m = btn.getAttribute("data-mode");
    if (m === "system") {
      localStorage.removeItem("lmSiteMonitorTheme");
      applyTheme(resolveMode(cfg.defaultTheme || "system"));
    } else {
      localStorage.setItem("lmSiteMonitorTheme", m);
      applyTheme(m);
    }
  });

  /* ============================================================
	   AJAX HELPER
	   ============================================================ */

  /**
   * Small fetch wrapper for wp-admin AJAX.
   *
   * @param {string} action  WP AJAX action name.
   * @param {Object} data    Key/value pairs to send.
   * @returns {Promise<Object>} Parsed JSON response.
   */
  function wpAjax(action, data) {
    var body = new FormData();
    body.append("action", action);
    Object.keys(data).forEach(function (k) {
      var val = data[k];
      // Support arrays (e.g. order[]).
      if (Array.isArray(val)) {
        val.forEach(function (v) {
          body.append(k + "[]", v);
        });
      } else {
        body.append(k, val);
      }
    });
    return fetch(cfg.ajaxUrl, {
      method: "POST",
      body: body,
      credentials: "same-origin",
    }).then(function (r) {
      return r.json();
    });
  }

  /* ============================================================
	   DRAG AND DROP SORT
	   ============================================================ */

  var dragSrc = null; // card currently being dragged
  var saveTimer = null; // debounce AJAX save

  /**
   * Persists current card DOM order via AJAX.
   * Pinned cards are included in the payload so the server
   * can keep them at the top of their group.
   */
  function persistOrder() {
    if (!cfg.ajaxUrl || !cfg.nonceSaveOrder) {
      return;
    }
    var container = document.getElementById("lm-cards-sortable");
    if (!container) {
      return;
    }

    var order = [];
    container
      .querySelectorAll(".lm-card-item[data-id]")
      .forEach(function (card) {
        order.push(card.getAttribute("data-id"));
      });

    if (!order.length) {
      return;
    }

    clearTimeout(saveTimer);
    saveTimer = setTimeout(function () {
      wpAjax("lmnsimo_save_order", {
        nonce: cfg.nonceSaveOrder,
        order: order,
      }).catch(function () {
        /* silent fail — order will sync on next reload */
      });
    }, 600);
  }

  /**
   * Moves dragSrc card before or after the target card
   * depending on pointer position.
   *
   * @param {HTMLElement} target  Card under the pointer.
   * @param {number}      clientY Pointer Y coordinate.
   */
  function moveDragSrc(target, clientY) {
    if (!dragSrc || target === dragSrc) {
      return;
    }
    var rect = target.getBoundingClientRect();
    var middle = rect.top + rect.height / 2;
    if (clientY < middle) {
      target.parentNode.insertBefore(dragSrc, target);
    } else {
      target.parentNode.insertBefore(dragSrc, target.nextSibling);
    }
  }

  function initSortable() {
    var container = document.getElementById("lm-cards-sortable");
    if (!container) {
      return;
    }

    // Attach drag events to each non-pinned card via its handle.
    container.querySelectorAll(".lm-card-item").forEach(function (card) {
      attachDragToCard(card);
    });

    // Container-level events (dragover / drop / dragleave).
    container.addEventListener("dragover", function (e) {
      e.preventDefault();
      e.dataTransfer.dropEffect = "move";
      var target = e.target.closest(".lm-card-item");
      if (!target || target === dragSrc) {
        return;
      }
      // Visual feedback.
      container.querySelectorAll(".lm-card-item").forEach(function (c) {
        c.classList.remove("lm-drag-over");
      });
      target.classList.add("lm-drag-over");
      moveDragSrc(target, e.clientY);
    });

    container.addEventListener("dragleave", function (e) {
      var target = e.target.closest(".lm-card-item");
      if (target) {
        target.classList.remove("lm-drag-over");
      }
    });

    container.addEventListener("drop", function (e) {
      e.preventDefault();
      container.querySelectorAll(".lm-card-item").forEach(function (c) {
        c.classList.remove("lm-drag-over");
      });
      if (dragSrc) {
        dragSrc.classList.remove("lm-dragging");
        dragSrc = null;
        persistOrder();
      }
    });

    // Safety: clean up if drag ends outside container.
    document.addEventListener("dragend", function () {
      if (dragSrc) {
        dragSrc.classList.remove("lm-dragging");
        container.querySelectorAll(".lm-card-item").forEach(function (c) {
          c.classList.remove("lm-drag-over");
        });
        dragSrc = null;
        persistOrder();
      }
    });
  }

  /**
   * Attaches drag events to a single card.
   * Only cards that are NOT pinned get draggable=true.
   *
   * @param {HTMLElement} card
   */
  function attachDragToCard(card) {
    var isPinned = card.getAttribute("data-pinned") === "1";
    var handle = card.querySelector(".lm-drag-handle");

    if (isPinned) {
      card.removeAttribute("draggable");
      if (handle) {
        handle.style.cursor = "not-allowed";
      }
      return;
    }

    card.setAttribute("draggable", "true");

    card.addEventListener("dragstart", function (e) {
      // Only allow drag via handle.
      if (e.target.closest(".lm-drag-handle") || e.currentTarget === card) {
        dragSrc = card;
        card.classList.add("lm-dragging");
        e.dataTransfer.effectAllowed = "move";
        e.dataTransfer.setData("text/plain", card.getAttribute("data-id"));
      } else {
        e.preventDefault();
      }
    });

    card.addEventListener("dragend", function () {
      card.classList.remove("lm-dragging");
    });

    // Touch support (basic).
    attachTouchSort(card);
  }

  /* ---- Touch-based sorting (mobile) ---- */

  var touchSrc = null;
  var touchClone = null;
  var touchOffsetX = 0;
  var touchOffsetY = 0;

  function attachTouchSort(card) {
    var handle = card.querySelector(".lm-drag-handle");
    if (!handle) {
      return;
    }

    handle.addEventListener(
      "touchstart",
      function (e) {
        if (card.getAttribute("data-pinned") === "1") {
          return;
        }
        e.preventDefault();

        var touch = e.touches[0];
        var rect = card.getBoundingClientRect();
        touchOffsetX = touch.clientX - rect.left;
        touchOffsetY = touch.clientY - rect.top;
        touchSrc = card;

        // Create a visual clone.
        touchClone = card.cloneNode(true);
        touchClone.style.cssText = [
          "position:fixed",
          "z-index:99998",
          "pointer-events:none",
          "opacity:0.75",
          "width:" + rect.width + "px",
          "left:" + (touch.clientX - touchOffsetX) + "px",
          "top:" + (touch.clientY - touchOffsetY) + "px",
          "transition:none",
          "box-shadow:0 8px 32px rgba(0,0,0,.25)",
          "border-radius:12px",
        ].join(";");
        document.body.appendChild(touchClone);
        card.classList.add("lm-dragging");
      },
      { passive: false },
    );

    handle.addEventListener(
      "touchmove",
      function (e) {
        if (!touchSrc || !touchClone) {
          return;
        }
        e.preventDefault();
        var touch = e.touches[0];
        touchClone.style.left = touch.clientX - touchOffsetX + "px";
        touchClone.style.top = touch.clientY - touchOffsetY + "px";

        // Find card under finger.
        touchClone.style.display = "none";
        var el = document.elementFromPoint(touch.clientX, touch.clientY);
        touchClone.style.display = "";

        var target = el ? el.closest(".lm-card-item") : null;
        if (target && target !== touchSrc) {
          moveDragSrc(target, touch.clientY);
        }
      },
      { passive: false },
    );

    var endTouch = function () {
      if (!touchSrc) {
        return;
      }
      touchSrc.classList.remove("lm-dragging");
      if (touchClone && touchClone.parentNode) {
        touchClone.parentNode.removeChild(touchClone);
      }
      touchClone = null;
      touchSrc = null;
      persistOrder();
    };
    handle.addEventListener("touchend", endTouch);
    handle.addEventListener("touchcancel", endTouch);
  }

  /* ============================================================
	   PIN TOGGLE
	   ============================================================ */

  function initPinButtons() {
    document.addEventListener("click", function (e) {
      var btn = e.target.closest(".lm-pin-btn");
      if (!btn) {
        return;
      }
      if (!cfg.ajaxUrl || !cfg.nonceTogglePin) {
        return;
      }

      var id = btn.getAttribute("data-id");
      var isPinned = btn.classList.contains("is-pinned");
      var newPin = isPinned ? 0 : 1;

      // Optimistic UI update.
      updatePinUI(btn, id, newPin);

      wpAjax("lmnsimo_toggle_pin", {
        nonce: cfg.nonceTogglePin,
        id: id,
        pinned: newPin,
      })
        .then(function (res) {
          if (!res.success) {
            // Rollback on failure.
            updatePinUI(btn, id, isPinned ? 1 : 0);
          } else {
            // Move card to correct position in DOM.
            repositionPinnedCard(id, newPin);
            // Persist new order.
            persistOrder();
          }
        })
        .catch(function () {
          updatePinUI(btn, id, isPinned ? 1 : 0);
        });
    });
  }

  /**
   * Updates the button and card visuals after a pin toggle.
   *
   * @param {HTMLElement} btn
   * @param {string}      id
   * @param {number}      pinned  1 = pinned, 0 = unpinned.
   */
  function updatePinUI(btn, id, pinned) {
    var card = document.querySelector('.lm-card-item[data-id="' + id + '"]');
    if (!card) {
      return;
    }

    card.setAttribute("data-pinned", pinned ? "1" : "0");

    if (pinned) {
      card.classList.add("is-pinned");
      btn.classList.add("is-pinned");
      btn.setAttribute("aria-pressed", "true");
      btn.setAttribute("title", cfg.i18n.unpin || "Unpin");
      btn.innerHTML =
        '<svg width="13" height="13" viewBox="0 0 24 24" fill="#f59e0b" aria-hidden="true"><path d="M16 3H8v8L4 15h7v6h2v-6h7l-4-4V3z"/></svg>' +
        '<span class="lm-pin-label">' +
        escHtml(cfg.i18n.pinned || "Pinned") +
        "</span>";
      // Disable drag.
      card.removeAttribute("draggable");
      var handle = card.querySelector(".lm-drag-handle");
      if (handle) {
        handle.style.cursor = "not-allowed";
        handle.style.opacity = ".15";
        handle.style.pointerEvents = "none";
      }
    } else {
      card.classList.remove("is-pinned");
      btn.classList.remove("is-pinned");
      btn.setAttribute("aria-pressed", "false");
      btn.setAttribute("title", cfg.i18n.pin || "Pin to top");
      btn.innerHTML =
        '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M16 3H8v8L4 15h7v6h2v-6h7l-4-4V3z"/></svg>';
      // Re-enable drag.
      attachDragToCard(card);
    }
  }

  /**
   * Moves a card to the top (pinned) or below all pinned cards (unpinned).
   *
   * @param {string} id
   * @param {number} pinned
   */
  function repositionPinnedCard(id, pinned) {
    var container = document.getElementById("lm-cards-sortable");
    if (!container) {
      return;
    }

    var card = container.querySelector('.lm-card-item[data-id="' + id + '"]');
    if (!card) {
      return;
    }

    var allCards = Array.from(
      container.querySelectorAll(".lm-card-item[data-id]"),
    );

    if (pinned) {
      // Move to before the first non-pinned card (or to top).
      var firstUnpinned = allCards.find(function (c) {
        return c.getAttribute("data-pinned") !== "1" && c !== card;
      });
      if (firstUnpinned) {
        container.insertBefore(card, firstUnpinned);
      } else {
        container.insertBefore(card, container.firstElementChild);
      }
    } else {
      // Move to after the last pinned card.
      var pinnedCards = allCards.filter(function (c) {
        return c.getAttribute("data-pinned") === "1" && c !== card;
      });
      if (pinnedCards.length) {
        var lastPinned = pinnedCards[pinnedCards.length - 1];
        container.insertBefore(card, lastPinned.nextSibling);
      } else {
        container.insertBefore(card, container.firstElementChild);
      }
    }
  }

  /* ============================================================
	   WHOIS MODAL
	   ============================================================ */

  var whoisModal = null;
  var whoisContent = null;

  function initWhoisModal() {
    whoisModal = document.getElementById("lm-whois-modal");
    whoisContent = document.getElementById("lm-whois-content");
    var closeBtn = document.getElementById("lm-whois-close");

    if (!whoisModal) {
      return;
    }

    // Open on WHOIS button click.
    document.addEventListener("click", function (e) {
      var btn = e.target.closest(".lm-whois-btn");
      if (!btn) {
        return;
      }
      var url = btn.getAttribute("data-url");
      if (url) {
        openWhois(url);
      }
    });

    // Close on button.
    if (closeBtn) {
      closeBtn.addEventListener("click", closeWhois);
    }

    // Close on backdrop click.
    whoisModal.addEventListener("click", function (e) {
      if (e.target === whoisModal) {
        closeWhois();
      }
    });

    // Close on Escape.
    document.addEventListener("keydown", function (e) {
      if (e.key === "Escape" && whoisModal.classList.contains("is-open")) {
        closeWhois();
      }
    });
  }

  function openWhois(url) {
    if (!whoisModal || !whoisContent) {
      return;
    }

    // Show loading state.
    whoisContent.innerHTML =
      '<div class="lm-whois-loading">' +
      escHtml(cfg.i18n.loading || "Loading…") +
      "</div>";
    whoisModal.classList.add("is-open");
    document.body.style.overflow = "hidden";

    // Update dialog title with host.
    var titleEl = document.getElementById("lm-whois-title");
    if (titleEl) {
      var host = "";
      try {
        host = new URL(url).hostname;
      } catch (x) {
        host = url;
      }
      titleEl.textContent =
        (cfg.i18n.whoisTitle || "WHOIS / DNS") + " — " + host;
    }

    if (!cfg.ajaxUrl || !cfg.nonceWhois) {
      whoisContent.innerHTML =
        "<p>" + escHtml(cfg.i18n.error || "Error.") + "</p>";
      return;
    }

    wpAjax("lmnsimo_whois", {
      nonce: cfg.nonceWhois,
      url: url,
    })
      .then(function (res) {
        if (res.success && res.data) {
          renderWhoisData(res.data);
        } else {
          whoisContent.innerHTML =
            '<p style="color:#dc2626">' +
            escHtml(cfg.i18n.error || "Error retrieving WHOIS data.") +
            "</p>";
        }
      })
      .catch(function () {
        whoisContent.innerHTML =
          '<p style="color:#dc2626">' +
          escHtml(cfg.i18n.error || "Error retrieving WHOIS data.") +
          "</p>";
      });
  }

  function closeWhois() {
    if (!whoisModal) {
      return;
    }
    whoisModal.classList.remove("is-open");
    document.body.style.overflow = "";
  }

  /**
   * Renders the WHOIS/DNS data inside the modal.
   *
   * @param {Object} d  Data object returned by PHP ajax_whois().
   */
  function renderWhoisData(d) {
    if (!whoisContent) {
      return;
    }

    var rows = [];

    // IP (IPv4).
    rows.push(["IP (A)", d.ip && d.ip.length ? d.ip.join(", ") : "—"]);

    // IP (IPv6).
    if (d.ipv6 && d.ipv6.length) {
      rows.push(["IPv6 (AAAA)", d.ipv6.join(", ")]);
    }

    // TTL.
    rows.push([
      "TTL",
      d.ttl !== null && d.ttl !== undefined ? d.ttl + " s" : "—",
    ]);

    // DNS Provider (guessed from NS).
    rows.push(["DNS Provider", d.provider || "—"]);

    // NS records.
    if (d.ns && d.ns.length) {
      rows.push(["NS", d.ns.join("\n")]);
    }

    // MX records.
    if (d.mx && d.mx.length) {
      rows.push(["MX", d.mx.join("\n")]);
    }

    // SSL expiry.
    if (d.ssl_expiry) {
      var sslText = d.ssl_expiry;
      if (d.ssl_days !== null && d.ssl_days !== undefined) {
        sslText += " (" + d.ssl_days + " days)";
      }
      rows.push(["SSL expires", sslText]);
    }

    // Domain expiry.
    if (d.domain_expiry) {
      var domText = d.domain_expiry;
      if (d.domain_days !== null && d.domain_days !== undefined) {
        domText += " (" + d.domain_days + " days)";
      }
      rows.push(["Domain expires", domText]);
    }

    // Build table.
    var html = '<table class="lm-whois-table"><tbody>';
    rows.forEach(function (row) {
      var val = escHtml(String(row[1])).replace(/\n/g, "<br>");
      html += "<tr><td>" + escHtml(row[0]) + "</td><td>" + val + "</td></tr>";
    });
    html += "</tbody></table>";

    // Raw WHOIS (collapsible if present).
    if (d.whois_raw && d.whois_raw.trim()) {
      // Extract the most useful lines: Registrar, Expiry, Creation, Updated.
      var parsed = parseWhoisHighlights(d.whois_raw);
      if (parsed.length) {
        html += '<table class="lm-whois-table" style="margin-top:12px"><tbody>';
        parsed.forEach(function (row) {
          html +=
            "<tr><td>" +
            escHtml(row[0]) +
            "</td><td>" +
            escHtml(row[1]) +
            "</td></tr>";
        });
        html += "</tbody></table>";
      }

      // Full raw (collapsed by default).
      html += '<p class="lm-whois-raw-label">Raw WHOIS</p>';
      html +=
        '<pre class="lm-whois-raw">' + escHtml(d.whois_raw.trim()) + "</pre>";
    }

    whoisContent.innerHTML = html;
  }

  /**
   * Extracts key/value highlights from raw WHOIS text.
   * Returns an array of [label, value] pairs.
   *
   * @param  {string} raw
   * @returns {Array}
   */
  function parseWhoisHighlights(raw) {
    var patterns = [
      ["Registrar", /registrar:\s*(.+)/i],
      ["Created", /creation date:\s*(.+)/i],
      ["Updated", /updated date:\s*(.+)/i],
      [
        "Expires",
        /(?:registry expiry date|expir(?:y|ation) date|paid-till):\s*(.+)/i,
      ],
      ["Registrant", /registrant(?:\s+organization)?:\s*(.+)/i],
      ["Name servers", /name server:\s*(.+)/i],
      ["DNSSEC", /dnssec:\s*(.+)/i],
    ];

    var found = [];
    var seen = {};

    patterns.forEach(function (p) {
      var label = p[0];
      var pattern = p[1];
      var match = raw.match(pattern);
      if (match && match[1] && !seen[label]) {
        found.push([label, match[1].trim()]);
        seen[label] = true;
      }
    });

    return found;
  }

  /* ============================================================
	   UTILITIES
	   ============================================================ */

  /**
   * Minimal HTML escape to prevent XSS when setting innerHTML.
   *
   * @param  {string} str
   * @returns {string}
   */
  function escHtml(str) {
    return String(str)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
  }

  /* ============================================================
	   BOOT
	   ============================================================ */

  function boot() {
    initSortable();
    initPinButtons();
    initWhoisModal();
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", boot);
  } else {
    boot();
  }
})();
