Skip to content
// Vanilla JS frontend for the Notify Tauri desktop app
(function () {
  "use strict";

  // Elements
  const toastContainer = document.getElementById("toast-container");
  const profileBadge = document.getElementById("profile-badge");
  const serverStatusLabel = document.getElementById("server-status-label");
  const settingsModal = document.getElementById("settings-modal");
  const openSettingsBtn = document.getElementById("open-settings-btn");
  const closeModalX = document.getElementById("close-modal-x");
  const profilesList = document.getElementById("profiles-list");

  const btnHeart = document.getElementById("btn-heart");
  const btnCat = document.getElementById("btn-cat");
  const btnEmergency = document.getElementById("btn-emergency");

  const customForm = document.getElementById("custom-message-form");
  const customInput = document.getElementById("custom-message-input");
  const btnCustomSend = document.getElementById("btn-custom-send");

  const configForm = document.getElementById("config-form");
  const cfgProfileName = document.getElementById("cfg-profile-name");
  const cfgServerUrl = document.getElementById("cfg-server-url");
  const cfgServerPin = document.getElementById("cfg-server-pin");
  const btnTestConn = document.getElementById("btn-test-conn");
  const connTestResult = document.getElementById("conn-test-result");

  let currentConfig = null;
  let isSending = false;
  const sendFeedbackTimeouts = new WeakMap();

  // Invoke helper for Tauri v2
  async function invokeTauri(cmd, args = {}) {
    if (window.__TAURI__ && window.__TAURI__.core && window.__TAURI__.core.invoke) {
      return window.__TAURI__.core.invoke(cmd, args);
    }
    throw new Error("Tauri core API is unavailable in this environment");
  }

  // Toast notification
  function showToast(message, type = "info") {
    const alert = document.createElement("div");
    const alertTypeClass =
      type === "success"
        ? "alert-success"
        : type === "error"
        ? "alert-error"
        : "alert-info";

    alert.className = `alert ${alertTypeClass} shadow-md text-xs py-2 px-3 flex items-center gap-2 pointer-events-auto transition-all`;

    const icon =
      type === "success"
        ? `<svg xmlns="http://www.w3.org/2000/svg" class="size-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>`
        : type === "error"
        ? `<svg xmlns="http://www.w3.org/2000/svg" class="size-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>`
        : `<svg xmlns="http://www.w3.org/2000/svg" class="size-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>`;

    alert.innerHTML = `${icon}<span>${escapeHtml(message)}</span>`;
    toastContainer.appendChild(alert);

    setTimeout(() => {
      alert.style.opacity = "0";
      setTimeout(() => alert.remove(), 200);
    }, 3200);
  }

  function escapeHtml(str) {
    if (!str) return "";
    return String(str)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
  }

  function setButtonLoading(btn, loading) {
    if (!btn) return;
    const spinner = btn.querySelector(".loading");
    const label = btn.querySelector(".btn-label");
    if (loading) {
      btn.disabled = true;
      if (spinner) spinner.classList.remove("hidden");
      if (label) label.classList.add("opacity-50");
    } else {
      btn.disabled = false;
      if (spinner) spinner.classList.add("hidden");
      if (label) label.classList.remove("opacity-50");
    }
  }

  function showSendFeedback(kind) {
    const feedback = document.getElementById(
      kind === "notification" ? "custom-send-feedback" : "quick-send-feedback"
    );
    if (!feedback) return;

    clearTimeout(sendFeedbackTimeouts.get(feedback));
    feedback.textContent = "Sent ✓";
    feedback.classList.remove("send-feedback-visible", "send-feedback-error");
    void feedback.offsetWidth;
    feedback.classList.add("send-feedback-visible");
    sendFeedbackTimeouts.set(feedback, setTimeout(() => {
      feedback.textContent = "";
      feedback.classList.remove("send-feedback-visible");
    }, 1800));
  }

  // Render configuration & profiles list
  function renderConfig(state) {
    currentConfig = state;
    const activeName = state.active_profile_name || (state.active_server && state.active_server.name) || "default";
    profileBadge.textContent = activeName;

    const srv = state.active_server || {};
    const urlDisplay = srv.url || "No server configured";
    serverStatusLabel.textContent = urlDisplay;
    serverStatusLabel.title = `Server: ${urlDisplay}\nProfile: ${activeName}\nConfig Dir: ${state.config_dir}`;

    // Fill Modal form
    cfgProfileName.value = activeName;
    cfgServerUrl.value = srv.url || "";
    cfgServerPin.value = srv.pin || "";

    // Render Profiles list (from Omarchy ~/.config/notify)
    profilesList.innerHTML = "";
    if (!state.profiles || state.profiles.length === 0) {
      const emptyItem = document.createElement("div");
      emptyItem.className = "text-xs text-base-content/60 p-2 text-center";
      emptyItem.textContent = "No profiles found in ~/.config/notify. Create one below.";
      profilesList.appendChild(emptyItem);
    } else {
      state.profiles.forEach((p) => {
        const item = document.createElement("button");
        item.type = "button";
        const isActive = p.name === state.active_profile_name;
        item.className = `btn btn-xs justify-between w-full font-normal ${
          isActive ? "btn-neutral" : "btn-ghost"
        }`;

        const srvInfo = p.servers && p.servers[0] ? p.servers[0].url : "unspecified";
        item.innerHTML = `
          <span class="font-medium">${escapeHtml(p.name)}</span>
          <span class="text-xs opacity-60 truncate max-w-xs">${escapeHtml(srvInfo)}</span>
          ${isActive ? '<span class="badge badge-xs badge-success">active</span>' : ""}
        `;

        item.addEventListener("click", async () => {
          try {
            const updated = await invokeTauri("set_active_profile", { name: p.name });
            renderConfig(updated);
            showToast(`Switched to profile: ${p.name}`, "success");
            testCurrentConnection(false);
          } catch (e) {
            showToast(`Failed to switch profile: ${e}`, "error");
          }
        });

        profilesList.appendChild(item);
      });
    }
  }

  async function loadConfig() {
    try {
      const state = await invokeTauri("get_config");
      renderConfig(state);
      testCurrentConnection(false);
    } catch (e) {
      console.error("Failed to load config:", e);
      serverStatusLabel.textContent = "Failed to load config";
      showToast("Could not load ~/.config/notify config", "error");
    }
  }

  async function testCurrentConnection(silentOnSuccess = true) {
    try {
      const res = await invokeTauri("test_connection", { targetUrl: null, targetPin: null });
      if (res && res.ok) {
        serverStatusLabel.innerHTML = `<span class="text-success font-semibold">●</span> ${escapeHtml(currentConfig.active_server.url)}`;
        if (!silentOnSuccess) {
          showToast("Connected to server successfully", "success");
        }
      } else {
        const errMsg = res ? res.message : "Connection failed";
        serverStatusLabel.innerHTML = `<span class="text-error font-semibold">●</span> ${escapeHtml(currentConfig.active_server.url)} (${escapeHtml(errMsg)})`;
        if (!silentOnSuccess) {
          showToast(errMsg, "error");
        }
      }
    } catch (e) {
      serverStatusLabel.innerHTML = `<span class="text-error font-semibold">●</span> Offline / unreachable`;
      if (!silentOnSuccess) {
        showToast(String(e), "error");
      }
    }
  }

  // Send notification action
  async function handleSend(kind, message, btnEl) {
    if (isSending) return;
    isSending = true;
    setButtonLoading(btnEl, true);

    try {
      const result = await invokeTauri("send_notification", {
        kind: kind || null,
        message: message || null,
        targetUrl: null,
        targetPin: null,
      });

      if (result && result.success) {
        showSendFeedback(kind);
        if (customInput && kind === "notification") {
          customInput.value = "";
        }
      } else {
        showToast("Unexpected response from server", "error");
      }
    } catch (err) {
      console.error("Failed to send notification:", err);
      showToast(String(err || "Failed to send notification"), "error");
    } finally {
      setButtonLoading(btnEl, false);
      isSending = false;
    }
  }

  // Event Listeners
  btnHeart.addEventListener("click", () => {
    handleSend("heart", "Look at your phone", btnHeart);
  });

  btnCat.addEventListener("click", () => {
    handleSend("cat", "Missing you", btnCat);
  });

  btnEmergency.addEventListener("click", () => {
    handleSend("emergency", "Emergency", btnEmergency);
  });

  customForm.addEventListener("submit", (e) => {
    e.preventDefault();
    const val = customInput.value.trim();
    if (!val) {
      customInput.focus();
      return;
    }
    handleSend("notification", val, btnCustomSend);
  });

  // Modal open / close
  function openModal() {
    connTestResult.classList.add("hidden");
    settingsModal.showModal();
  }

  function closeModal() {
    settingsModal.close();
  }

  openSettingsBtn.addEventListener("click", openModal);
  profileBadge.addEventListener("click", openModal);
  serverStatusLabel.addEventListener("click", openModal);
  closeModalX.addEventListener("click", closeModal);

  // Test connection button inside modal
  btnTestConn.addEventListener("click", async () => {
    setButtonLoading(btnTestConn, true);
    connTestResult.className = "text-xs p-2 rounded hidden";

    const url = cfgServerUrl.value.trim();
    const pin = cfgServerPin.value.trim();

    try {
      const res = await invokeTauri("test_connection", { targetUrl: url, targetPin: pin });
      connTestResult.classList.remove("hidden");
      if (res && res.ok) {
        connTestResult.className = "text-xs p-2 rounded bg-success/20 text-success border border-success/30";
        connTestResult.textContent = `✓ ${res.message}`;
      } else {
        connTestResult.className = "text-xs p-2 rounded bg-error/20 text-error border border-error/30";
        connTestResult.textContent = `✕ ${res ? res.message : "Connection failed"}`;
      }
    } catch (e) {
      connTestResult.classList.remove("hidden");
      connTestResult.className = "text-xs p-2 rounded bg-error/20 text-error border border-error/30";
      connTestResult.textContent = `✕ ${String(e)}`;
    } finally {
      setButtonLoading(btnTestConn, false);
    }
  });

  // Save config form inside modal
  configForm.addEventListener("submit", async (e) => {
    e.preventDefault();
    const name = cfgProfileName.value.trim() || "notify";
    const url = cfgServerUrl.value.trim();
    const pin = cfgServerPin.value.trim();

    if (!url) {
      showToast("Server URL is required", "error");
      cfgServerUrl.focus();
      return;
    }

    try {
      const updated = await invokeTauri("save_server_config", {
        name,
        url,
        pin,
        makeActive: true,
      });
      renderConfig(updated);
      showToast(`Profile '${name}' saved and activated!`, "success");
      closeModal();
      testCurrentConnection(false);
    } catch (err) {
      showToast(`Failed to save config: ${err}`, "error");
    }
  });

  // Initialize on page load
  window.addEventListener("DOMContentLoaded", loadConfig);
})();